From 9be72ffb4706cc2352ecb07c539c771348b5e1a4 Mon Sep 17 00:00:00 2001 From: Damian Zhaoying Date: Thu, 24 Oct 2013 03:39:07 -0300 Subject: [PATCH 01/17] add spanish translation for prefs editor open log in windows option --- .../skins/default/xui/es/panel_preferences_ascent_chat.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/newview/skins/default/xui/es/panel_preferences_ascent_chat.xml b/indra/newview/skins/default/xui/es/panel_preferences_ascent_chat.xml index f6790ada7..fc16bc69d 100644 --- a/indra/newview/skins/default/xui/es/panel_preferences_ascent_chat.xml +++ b/indra/newview/skins/default/xui/es/panel_preferences_ascent_chat.xml @@ -71,6 +71,7 @@ + From b34a8db34a9f88b27121a5e7c83583086e8edc97 Mon Sep 17 00:00:00 2001 From: Inusaito Sayori Date: Wed, 30 Oct 2013 08:37:20 -0400 Subject: [PATCH 02/17] Modernize constructor of LLPreviewSound and cure crash signature 314 --- indra/newview/llpreviewsound.cpp | 58 +++++++++++++++----------------- indra/newview/llpreviewsound.h | 1 + 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/indra/newview/llpreviewsound.cpp b/indra/newview/llpreviewsound.cpp index 163ba6056..7992ee558 100644 --- a/indra/newview/llpreviewsound.cpp +++ b/indra/newview/llpreviewsound.cpp @@ -60,10 +60,36 @@ const F32 SOUND_GAIN = 1.0f; LLPreviewSound::LLPreviewSound(const std::string& name, const LLRect& rect, const std::string& title, const LLUUID& item_uuid, const LLUUID& object_uuid) : LLPreview( name, rect, title, item_uuid, object_uuid) +, mIsCopyable(false) { LLUICtrlFactory::getInstance()->buildFloater(this,"floater_preview_sound.xml"); + setTitle(title); + + if (!getHost()) + { + LLRect curRect = getRect(); + translate(rect.mLeft - curRect.mLeft, rect.mTop - curRect.mTop); + } +} + +// virtual +BOOL LLPreviewSound::postBuild() +{ + const LLInventoryItem* item = getItem(); + if (item) + { + getChild("desc")->setValue(item->getDescription()); + mIsCopyable = (item->getPermissions().getCreator() == gAgentID); + if (gAudiop) + // + // that thing above doesn't actually start a sound transfer, so I will do it + if (LLAudioSource* asp = gAgentAvatarp->getAudioSource(gAgentID)) + asp->preload(item->getAssetUUID()); // preload the sound + // + } + childSetAction("Sound play btn",&LLPreviewSound::playSound,this); childSetAction("Sound audition btn",&LLPreviewSound::auditionSound,this); // @@ -77,40 +103,10 @@ LLPreviewSound::LLPreviewSound(const std::string& name, const LLRect& rect, cons button = getChild("Sound audition btn"); button->setSoundFlags(LLView::SILENT); - const LLInventoryItem* item = getItem(); - - mIsCopyable = false; - if(item) - { - const LLPermissions& perm = item->getPermissions(); - mIsCopyable = (perm.getCreator() == gAgent.getID()); - } - childSetCommitCallback("desc", LLPreview::onText, this); - childSetText("desc", item->getDescription()); getChild("desc")->setPrevalidate(&LLLineEditor::prevalidatePrintableNotPipe); - - // preload the sound - if(item && gAudiop) - { - // - // that thing above doesn't actually start a sound transfer, so I will do it - LLAudioSource *asp = gAgentAvatarp->getAudioSource(gAgent.getID()); - if(asp) - { - asp->preload(item->getAssetUUID()); - } - // - } - - setTitle(title); - - if (!getHost()) - { - LLRect curRect = getRect(); - translate(rect.mLeft - curRect.mLeft, rect.mTop - curRect.mTop); - } + return LLPreview::postBuild(); } // static diff --git a/indra/newview/llpreviewsound.h b/indra/newview/llpreviewsound.h index 6d8928c97..51a89d9df 100644 --- a/indra/newview/llpreviewsound.h +++ b/indra/newview/llpreviewsound.h @@ -65,6 +65,7 @@ public: // protected: + /*virtual*/ BOOL postBuild(); virtual const char *getTitleName() const { return "Sound"; } // virtual BOOL canSaveAs() const; From 73cb43e71f40ed69e73946f291b324d4f826e91b Mon Sep 17 00:00:00 2001 From: Inusaito Sayori Date: Wed, 30 Oct 2013 08:40:47 -0400 Subject: [PATCH 03/17] [Warnings] Fix ..\..\libhacd\hacdRaycastMesh.cpp(109): warning C4334: '<<' : result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?) All the other involved types are of size_t, so static_cast 1 should work here. --- indra/libhacd/hacdRaycastMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/libhacd/hacdRaycastMesh.cpp b/indra/libhacd/hacdRaycastMesh.cpp index 5edd9adc3..2f84bbc11 100644 --- a/indra/libhacd/hacdRaycastMesh.cpp +++ b/indra/libhacd/hacdRaycastMesh.cpp @@ -106,7 +106,7 @@ namespace HACD m_nMaxNodes = 0; for(size_t k = 0; k < maxDepth; k++) { - m_nMaxNodes += (1 << maxDepth); + m_nMaxNodes += (static_cast(1) << maxDepth); } m_nodes = new RMNode[m_nMaxNodes]; RMNode & root = m_nodes[AddNode()]; From ba1a29aae2b4374cd3d1750d4976669f8096ebad Mon Sep 17 00:00:00 2001 From: Inusaito Sayori Date: Fri, 1 Nov 2013 02:56:21 -0400 Subject: [PATCH 04/17] French Translation updates! Provided by Nomade Zhao --- .../skins/default/xui/fr/panel_preferences_ascent_chat.xml | 6 ++++++ .../skins/default/xui/fr/panel_preferences_general.xml | 1 + .../skins/default/xui/fr/panel_preferences_voice.xml | 2 ++ 3 files changed, 9 insertions(+) diff --git a/indra/newview/skins/default/xui/fr/panel_preferences_ascent_chat.xml b/indra/newview/skins/default/xui/fr/panel_preferences_ascent_chat.xml index fe2713675..41c80ba67 100644 --- a/indra/newview/skins/default/xui/fr/panel_preferences_ascent_chat.xml +++ b/indra/newview/skins/default/xui/fr/panel_preferences_ascent_chat.xml @@ -37,11 +37,13 @@ + + @@ -91,6 +93,10 @@ Et aussi : #n Pour le nom classique, #d Pour le Display, #r Pour la SLURL de vô + + Excepté ceux venant de : + + diff --git a/indra/newview/skins/default/xui/fr/panel_preferences_general.xml b/indra/newview/skins/default/xui/fr/panel_preferences_general.xml index e5b200812..6b5e1ab5c 100644 --- a/indra/newview/skins/default/xui/fr/panel_preferences_general.xml +++ b/indra/newview/skins/default/xui/fr/panel_preferences_general.xml @@ -15,6 +15,7 @@ Display Names(w/Username) Display Names only + Titres de groupe : diff --git a/indra/newview/skins/default/xui/fr/panel_preferences_voice.xml b/indra/newview/skins/default/xui/fr/panel_preferences_voice.xml index 57a39dc88..16a485bae 100644 --- a/indra/newview/skins/default/xui/fr/panel_preferences_voice.xml +++ b/indra/newview/skins/default/xui/fr/panel_preferences_voice.xml @@ -2,9 +2,11 @@ Le chat vocal n'est pas disponible + Ecouter depuis la position de la caméra Ecouter depuis la position de l'avatar + Entendre la voice de maniere égale pour tout le monde. Appuyer pour parler From 3934e0c9d30ca16aa6a25a3e2494f26144bb782f Mon Sep 17 00:00:00 2001 From: Inusaito Sayori Date: Fri, 1 Nov 2013 11:18:12 -0400 Subject: [PATCH 05/17] Fix Issue 1163: Mouselook zoom (silly control group confusion) --- indra/newview/lltoolcomp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/lltoolcomp.cpp b/indra/newview/lltoolcomp.cpp index 0651458c7..3f755dbf2 100644 --- a/indra/newview/lltoolcomp.cpp +++ b/indra/newview/lltoolcomp.cpp @@ -824,7 +824,7 @@ BOOL LLToolCompGun::handleRightMouseDown(S32 x, S32 y, MASK mask) } else mStartFOV = LLViewerCamera::getInstance()->getDefaultFOV(); - mTargetFOV = gSavedPerAccountSettings.getF32("zmm_mlfov"); + mTargetFOV = gSavedSettings.getF32("zmm_mlfov"); mTimerFOV.start(); return TRUE; @@ -836,7 +836,7 @@ BOOL LLToolCompGun::handleScrollWheel(S32 x, S32 y, S32 clicks) { mStartFOV = LLViewerCamera::getInstance()->getDefaultFOV(); - gSavedPerAccountSettings.setF32( + gSavedSettings.setF32( "zmm_mlfov", mTargetFOV = clicks > 0 ? llclamp(mTargetFOV += (0.05f * clicks), 0.1f, 3.0f) : From 2d421a27e1e228bc658c6283e81d11903423bfce Mon Sep 17 00:00:00 2001 From: Inusaito Sayori Date: Fri, 1 Nov 2013 16:56:55 -0400 Subject: [PATCH 06/17] Let Ctrl/Shift + scrollwheel changing the camera/focus offsets affect the current preset's setting, not the back preset. --- indra/newview/llagentcamera.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 3b81192f9..5cd4c780e 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -2023,13 +2023,21 @@ LLVector3 LLAgentCamera::getCameraOffsetInitial() } // Adds change to vector CachedControl, vec, at idx -template +template void change_vec(const T& change, LLCachedControl& vec, const U32& idx = VZ) { Vec changed(vec); changed[idx] += change; vec = changed; } +// Same as above, but for ControlVariables +template +void change_vec(const T& change, LLPointer& vec, const U32& idx = VZ) +{ + Vec changed(vec->get()); + changed[idx] += change; + vec->set(changed.getValue()); +} //----------------------------------------------------------------------------- // handleScrollWheel() @@ -2072,13 +2080,11 @@ void LLAgentCamera::handleScrollWheel(S32 clicks) const F32 change(static_cast(clicks) * 0.1f); if (mask & MASK_SHIFT) { - static LLCachedControl focus_offset("FocusOffsetRearView"); - change_vec(change, focus_offset); + change_vec(change, mFocusOffsetInitial[mCameraPreset]); } if (mask & MASK_CONTROL) { - static LLCachedControl camera_offset("CameraOffsetRearView"); - change_vec(change, camera_offset); + change_vec(change, mCameraOffsetInitial[mCameraPreset]); } return; } From 1f587d1ad1e125b4c7088aefa3d9f5199b082925 Mon Sep 17 00:00:00 2001 From: Latif Khalifa Date: Fri, 1 Nov 2013 22:34:55 +0100 Subject: [PATCH 07/17] Don't crash when trying to export linked parts Fixes issue #1176 --- indra/newview/awavefront.cpp | 7 +++++++ indra/newview/daeexport.cpp | 19 +++++++++++-------- .../skins/default/xui/en-us/notifications.xml | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/indra/newview/awavefront.cpp b/indra/newview/awavefront.cpp index 4af106c12..ee83dbb3d 100644 --- a/indra/newview/awavefront.cpp +++ b/indra/newview/awavefront.cpp @@ -275,6 +275,13 @@ namespace { if (LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection()) { + if (!selection->getFirstRootObject()) + { + if (gSavedSettings.getBOOL("OBJExportNotifyFailed")) + LLNotificationsUtil::add("ExportFailed"); + return true; + } + WavefrontSaver* wfsaver = new WavefrontSaver; // deleted in callback wfsaver->offset = -selection->getFirstRootObject()->getRenderPosition(); S32 total = 0; diff --git a/indra/newview/daeexport.cpp b/indra/newview/daeexport.cpp index 8c8aa3641..7768aa541 100644 --- a/indra/newview/daeexport.cpp +++ b/indra/newview/daeexport.cpp @@ -272,7 +272,9 @@ public: void addSelectedObjects() { - if (LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection()) + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + + if (selection && selection->getFirstRootObject()) { mSaver.mOffset = -selection->getFirstRootObject()->getRenderPosition(); mObjectName = selection->getFirstRootNode()->mName; @@ -285,14 +287,15 @@ public: if (!node->getObject()->getVolume() || !DAEExportUtil::canExportNode(node)) continue; mSaver.add(node->getObject(), node->mName); } + } - if (mSaver.mObjects.empty()) - { - LLNotificationsUtil::add("ExportFailed"); - close(); - return; - } - + if (mSaver.mObjects.empty()) + { + LLNotificationsUtil::add("ExportFailed"); + close(); + } + else + { mSaver.updateTextureInfo(); mNumTextures = mSaver.mTextures.size(); mNumExportableTextures = getNumExportableTextures(); diff --git a/indra/newview/skins/default/xui/en-us/notifications.xml b/indra/newview/skins/default/xui/en-us/notifications.xml index 1bcef0294..ec7196b58 100644 --- a/indra/newview/skins/default/xui/en-us/notifications.xml +++ b/indra/newview/skins/default/xui/en-us/notifications.xml @@ -5869,7 +5869,7 @@ Builds with extended hollow or extended hole size do not render properly on othe icon="alertmodal.tga" name="ExportFailed" type="alertmodal"> -Bad permissions for the exported object. Export aborted. +None of the selected objects are exportable. Export aborted. Date: Fri, 1 Nov 2013 17:35:12 -0400 Subject: [PATCH 08/17] Feature Request: Add Camera preset switching functionality (part of quicksettings) Hitting Escape will reset to the normal Back preset Adds new Cam_Preset_*_(On|Off).png textures, these should probably be reskinned, any volunteers? --- indra/newview/llviewermenu.cpp | 11 +++++++ .../default/textures/Cam_Preset_Back_Off.png | Bin 0 -> 49285 bytes .../default/textures/Cam_Preset_Back_On.png | Bin 0 -> 50855 bytes .../default/textures/Cam_Preset_Front_Off.png | Bin 0 -> 50394 bytes .../default/textures/Cam_Preset_Front_On.png | Bin 0 -> 50127 bytes .../default/textures/Cam_Preset_Side_Off.png | Bin 0 -> 49268 bytes .../default/textures/Cam_Preset_Side_On.png | Bin 0 -> 50209 bytes .../en-us/wlfPanel_AdvSettings_expanded.xml | 14 +++++++-- indra/newview/wlfPanel_AdvSettings.cpp | 28 ++++++++++++++++++ indra/newview/wlfPanel_AdvSettings.h | 2 ++ 10 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 indra/newview/skins/default/textures/Cam_Preset_Back_Off.png create mode 100644 indra/newview/skins/default/textures/Cam_Preset_Back_On.png create mode 100644 indra/newview/skins/default/textures/Cam_Preset_Front_Off.png create mode 100644 indra/newview/skins/default/textures/Cam_Preset_Front_On.png create mode 100644 indra/newview/skins/default/textures/Cam_Preset_Side_Off.png create mode 100644 indra/newview/skins/default/textures/Cam_Preset_Side_On.png diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index dcb7a4eb7..e32525a9d 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -189,6 +189,7 @@ #include "llvovolume.h" #include "hippogridmanager.h" +#include "wlfPanel_AdvSettings.h" void toggle_search_floater(); @@ -4122,6 +4123,16 @@ void reset_view_final( BOOL proceed ) } gAgentCamera.switchCameraPreset(CAMERA_PRESET_REAR_VIEW); + if (wlfPanel_AdvSettings::instanceExists()) // Fix up the buttons on the wlf panel to match the preset switch + { + wlfPanel_AdvSettings& inst(wlfPanel_AdvSettings::instance()); + if (inst.isExpanded()) + { + inst.getChildView("Rear")->setValue(true); + inst.getChildView("Front")->setValue(false); + inst.getChildView("Group")->setValue(false); + } + } gAgentCamera.resetView(TRUE, TRUE); gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); diff --git a/indra/newview/skins/default/textures/Cam_Preset_Back_Off.png b/indra/newview/skins/default/textures/Cam_Preset_Back_Off.png new file mode 100644 index 0000000000000000000000000000000000000000..00158a7bc2fe1161f878c54bc416764e8621a9b7 GIT binary patch literal 49285 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 d5$Rcz%eESEGtib5_SBzW`0O`r-fp literal 0 HcmV?d00001 diff --git a/indra/newview/skins/default/textures/Cam_Preset_Front_On.png b/indra/newview/skins/default/textures/Cam_Preset_Front_On.png new file mode 100644 index 0000000000000000000000000000000000000000..bc8c4db04df3a265a78a6d12b1f58af77cedae5f GIT binary patch literal 50127 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∈-a?yC! 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 Upz&06CuWxbHyi|(5Da{d&PqzP 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 - - + + Cam Preset: + + + + diff --git a/indra/newview/wlfPanel_AdvSettings.cpp b/indra/newview/wlfPanel_AdvSettings.cpp index 18613c496..9ed6ea18e 100644 --- a/indra/newview/wlfPanel_AdvSettings.cpp +++ b/indra/newview/wlfPanel_AdvSettings.cpp @@ -47,6 +47,7 @@ #include "llfloaterwindlight.h" #include "llfloaterwater.h" +#include "llagentcamera.h" #include "lldaycyclemanager.h" #include "llenvmanager.h" #include "llwaterparammanager.h" @@ -58,6 +59,7 @@ wlfPanel_AdvSettings::wlfPanel_AdvSettings() : mExpanded(false) setVisible(false); setIsChrome(TRUE); setFocusRoot(TRUE); + mCommitCallbackRegistrar.add("Wlf.ChangeCameraPreset", boost::bind(&wlfPanel_AdvSettings::onChangeCameraPreset, this, _1, _2)); if(rlv_handler_t::isEnabled()) gRlvHandler.setBehaviourToggleCallback(boost::bind(&wlfPanel_AdvSettings::onRlvBehaviorChange, this, _1, _2)); } @@ -184,6 +186,14 @@ BOOL wlfPanel_AdvSettings::postBuild() mTimeSlider->setCommitCallback(boost::bind(&wlfPanel_AdvSettings::onChangeDayTime, this, _2)); updateTimeSlider(); updateRlvVisibility(); + + const U32 preset(gSavedSettings.getU32("CameraPreset")); + if (preset == CAMERA_PRESET_REAR_VIEW) + getChildView("Rear")->setValue(true); + else if (preset == CAMERA_PRESET_FRONT_VIEW) + getChildView("Front")->setValue(true); + else if (preset == CAMERA_PRESET_GROUP_VIEW) + getChildView("Group")->setValue(true); } return TRUE; } @@ -203,6 +213,24 @@ void wlfPanel_AdvSettings::onClickExpandBtn(void* user_data) gSavedSettings.setBOOL("wlfAdvSettingsPopup",!gSavedSettings.getBOOL("wlfAdvSettingsPopup")); } +void wlfPanel_AdvSettings::onChangeCameraPreset(LLUICtrl* ctrl, const LLSD& param) +{ + if (!ctrl->getValue()) // One of these must be set at all times + { + ctrl->setValue(true); + return; + } + + // 0 is rear, 1 is front, 2 is group + const ECameraPreset preset((param.asInteger() == 0) ? CAMERA_PRESET_REAR_VIEW : (param.asInteger() == 1) ? CAMERA_PRESET_FRONT_VIEW : CAMERA_PRESET_GROUP_VIEW); + // Turn off the other preset indicators + if (preset != CAMERA_PRESET_REAR_VIEW) getChildView("Rear")->setValue(false); + if (preset != CAMERA_PRESET_FRONT_VIEW) getChildView("Front")->setValue(false); + if (preset != CAMERA_PRESET_GROUP_VIEW) getChildView("Group")->setValue(false); + // Actually switch the camera + gAgentCamera.switchCameraPreset(preset); +} + void wlfPanel_AdvSettings::onUseRegionSettings(const LLSD& value) { LLEnvManagerNew::instance().setUseRegionSettings(value.asBoolean(), gSavedSettings.getBOOL("PhoenixInterpolateSky")); diff --git a/indra/newview/wlfPanel_AdvSettings.h b/indra/newview/wlfPanel_AdvSettings.h index ea7c65d01..413b30cd6 100644 --- a/indra/newview/wlfPanel_AdvSettings.h +++ b/indra/newview/wlfPanel_AdvSettings.h @@ -57,8 +57,10 @@ public: static void updateClass(); static void onClickExpandBtn(void* user_data); + void onChangeCameraPreset(LLUICtrl* ctrl, const LLSD& param); void onChangeWWPresetName(const LLSD& value); void onChangeWLPresetName(const LLSD& value); + const bool& isExpanded() const { return mExpanded; } protected: void build(); From 96e8f37c81b49b739a99d2094c8805f89a11053f Mon Sep 17 00:00:00 2001 From: Inusaito Sayori Date: Fri, 1 Nov 2013 17:43:32 -0400 Subject: [PATCH 09/17] The rest of some touchups to wlf xml for nicer alignment of elements --- .../en-us/wlfPanel_AdvSettings_expanded.xml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/indra/newview/skins/default/xui/en-us/wlfPanel_AdvSettings_expanded.xml b/indra/newview/skins/default/xui/en-us/wlfPanel_AdvSettings_expanded.xml index 5d2109ad2..d4ee3c863 100644 --- a/indra/newview/skins/default/xui/en-us/wlfPanel_AdvSettings_expanded.xml +++ b/indra/newview/skins/default/xui/en-us/wlfPanel_AdvSettings_expanded.xml @@ -12,17 +12,17 @@ - - - - - - From af8f8bb040b2b385eda05c79748ba45c7fbb2218 Mon Sep 17 00:00:00 2001 From: Shyotl Date: Sat, 2 Nov 2013 01:20:05 -0500 Subject: [PATCH 11/17] Mats. --- indra/llmath/llvolume.cpp | 6 +- indra/llmessage/llpartdata.cpp | 288 +++-- indra/llmessage/llpartdata.h | 61 +- indra/llprimitive/llprimitive.cpp | 12 +- indra/llrender/llglslshader.cpp | 5 + indra/llrender/llrender.cpp | 25 + indra/llrender/llrender.h | 9 + indra/llrender/llshadermgr.cpp | 21 +- indra/llrender/llshadermgr.h | 15 +- indra/llrender/llvertexbuffer.cpp | 14 +- indra/llrender/llvertexbuffer.h | 1 + indra/newview/CMakeLists.txt | 2 + .../shaders/class1/deferred/alphaF.glsl | 584 ++++++++- .../deferred/alphaNonIndexedNoColorF.glsl | 92 -- .../class1/deferred/alphaSkinnedV.glsl | 144 --- .../shaders/class1/deferred/alphaV.glsl | 170 ++- .../class1/deferred/attachmentShadowV.glsl | 5 + .../class1/deferred/avatarAlphaNoColorV.glsl | 2 + .../shaders/class1/deferred/avatarAlphaV.glsl | 153 --- .../shaders/class1/deferred/avatarF.glsl | 17 +- .../shaders/class1/deferred/blurLightF.glsl | 35 +- .../shaders/class1/deferred/bumpF.glsl | 13 +- .../shaders/class1/deferred/cloudsF.glsl | 5 - .../class1/deferred/diffuseAlphaMaskF.glsl | 13 +- .../deferred/diffuseAlphaMaskIndexedF.glsl | 15 +- .../deferred/diffuseAlphaMaskNoColorF.glsl | 13 +- .../shaders/class1/deferred/diffuseF.glsl | 13 +- .../class1/deferred/diffuseIndexedF.glsl | 20 +- .../shaders/class1/deferred/emissiveF.glsl | 2 +- .../shaders/class1/deferred/fullbrightF.glsl | 135 +- ...NonIndexedF.glsl => fullbrightShinyF.glsl} | 65 +- .../class1/deferred/fullbrightShinyV.glsl | 67 + .../shaders/class1/deferred/fullbrightV.glsl | 11 +- .../shaders/class1/deferred/fxaaF.glsl | 1 + .../shaders/class1/deferred/giF.glsl | 190 --- .../shaders/class1/deferred/impostorF.glsl | 47 +- .../shaders/class1/deferred/materialF.glsl | 788 +++++++++++ .../shaders/class1/deferred/materialV.glsl | 145 +++ .../class1/deferred/multiPointLightF.glsl | 99 +- .../class1/deferred/multiSpotLightF.glsl | 137 +- .../shaders/class1/deferred/pointLightF.glsl | 69 +- .../shaders/class1/deferred/pointLightV.glsl | 2 +- .../deferred/postDeferredGammaCorrect.glsl | 67 + .../shaders/class1/deferred/skyF.glsl | 8 +- .../shaders/class1/deferred/softenLightF.glsl | 207 ++- .../shaders/class1/deferred/spotLightF.glsl | 166 ++- .../shaders/class1/deferred/srgb.glsl | 46 + .../shaders/class1/deferred/srgb_mac.glsl | 54 + .../shaders/class1/deferred/starsF.glsl | 7 +- .../class1/deferred/sunLightSSAOF.glsl | 33 +- .../shaders/class1/deferred/terrainF.glsl | 13 +- .../shaders/class1/deferred/treeF.glsl | 13 +- .../shaders/class1/deferred/underWaterF.glsl | 157 +++ .../shaders/class1/deferred/waterF.glsl | 71 +- .../shaders/class1/deferred/waterV.glsl | 2 +- .../shaders/class1/environment/waterV.glsl | 14 +- .../class1/interface/downsampleDepthF.glsl | 2 - .../interface/downsampleDepthRectF.glsl | 2 - .../lighting/lightAlphaMaskNonIndexedF.glsl | 4 +- .../lighting/lightFullbrightAlphaMaskF.glsl | 6 +- .../class1/lighting/lightFullbrightF.glsl | 6 + .../lightFullbrightNonIndexedAlphaMaskF.glsl | 8 +- .../lightFullbrightWaterAlphaMaskF.glsl | 2 +- .../shaders/class2/deferred/alphaF.glsl | 168 --- .../class2/deferred/alphaNonIndexedF.glsl | 182 --- .../deferred/alphaNonIndexedNoColorF.glsl | 187 --- .../class2/deferred/alphaSkinnedV.glsl | 155 --- .../shaders/class2/deferred/alphaV.glsl | 150 --- .../shaders/class2/deferred/avatarAlphaV.glsl | 153 --- .../class2/deferred/multiSpotLightF.glsl | 163 ++- .../shaders/class2/deferred/softenLightF.glsl | 265 +++- .../shaders/class2/deferred/spotLightF.glsl | 149 ++- .../shaders/class2/deferred/sunLightF.glsl | 89 +- .../class2/deferred/sunLightSSAOF.glsl | 91 +- indra/newview/lldrawable.cpp | 51 +- indra/newview/lldrawable.h | 2 + indra/newview/lldrawpool.cpp | 32 + indra/newview/lldrawpool.h | 20 + indra/newview/lldrawpoolalpha.cpp | 274 ++-- indra/newview/lldrawpoolalpha.h | 9 +- indra/newview/lldrawpoolavatar.cpp | 369 +++++- indra/newview/lldrawpoolavatar.h | 69 +- indra/newview/lldrawpoolbump.cpp | 18 +- indra/newview/lldrawpoolmaterials.cpp | 224 ++++ indra/newview/lldrawpoolmaterials.h | 75 ++ indra/newview/lldrawpoolsimple.cpp | 309 ++++- indra/newview/lldrawpoolsimple.h | 53 + indra/newview/lldrawpoolwater.cpp | 13 +- indra/newview/llface.cpp | 523 +++++--- indra/newview/llface.h | 65 +- indra/newview/llface.inl | 36 +- indra/newview/llmaterialmgr.cpp | 91 +- indra/newview/llmaterialmgr.h | 2 + indra/newview/llspatialpartition.cpp | 41 +- indra/newview/llspatialpartition.h | 16 + indra/newview/llviewerdisplay.cpp | 5 + indra/newview/llviewerobject.cpp | 260 +++- indra/newview/llviewerobject.h | 31 +- indra/newview/llviewerpartsim.cpp | 43 +- indra/newview/llviewerpartsim.h | 15 +- indra/newview/llviewerpartsource.cpp | 75 +- indra/newview/llviewerpartsource.h | 3 +- indra/newview/llviewerregion.cpp | 42 + indra/newview/llviewerregion.h | 10 +- indra/newview/llviewershadermgr.cpp | 421 +++++- indra/newview/llviewershadermgr.h | 64 +- indra/newview/llviewertexture.cpp | 216 ++-- indra/newview/llviewertexture.h | 23 +- indra/newview/llviewertexturelist.cpp | 1 + indra/newview/llvoclouds.cpp | 1 + indra/newview/llvoclouds.h | 1 + indra/newview/llvograss.cpp | 7 +- indra/newview/llvograss.h | 1 + indra/newview/llvopartgroup.cpp | 418 ++++-- indra/newview/llvopartgroup.h | 23 +- indra/newview/llvosky.cpp | 2 +- indra/newview/llvovolume.cpp | 400 +++++- indra/newview/llvovolume.h | 1 + indra/newview/pipeline.cpp | 1150 +++++++++++++++-- indra/newview/pipeline.h | 52 +- .../skins/default/textures/flatnormal.tga | Bin 0 -> 92 bytes 121 files changed, 8416 insertions(+), 3292 deletions(-) delete mode 100644 indra/newview/app_settings/shaders/class1/deferred/alphaNonIndexedNoColorF.glsl delete mode 100644 indra/newview/app_settings/shaders/class1/deferred/alphaSkinnedV.glsl delete mode 100644 indra/newview/app_settings/shaders/class1/deferred/avatarAlphaV.glsl rename indra/newview/app_settings/shaders/class1/deferred/{alphaNonIndexedF.glsl => fullbrightShinyF.glsl} (51%) create mode 100644 indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyV.glsl delete mode 100644 indra/newview/app_settings/shaders/class1/deferred/giF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/materialF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/materialV.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/srgb.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/srgb_mac.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/underWaterF.glsl delete mode 100644 indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl delete mode 100644 indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl delete mode 100644 indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl delete mode 100644 indra/newview/app_settings/shaders/class2/deferred/alphaSkinnedV.glsl delete mode 100644 indra/newview/app_settings/shaders/class2/deferred/alphaV.glsl delete mode 100644 indra/newview/app_settings/shaders/class2/deferred/avatarAlphaV.glsl create mode 100644 indra/newview/lldrawpoolmaterials.cpp create mode 100644 indra/newview/lldrawpoolmaterials.h create mode 100644 indra/newview/skins/default/textures/flatnormal.tga diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 26767841a..0df12e38d 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -166,7 +166,8 @@ void calc_tangent_from_triangle( F32 rd = s1*t2-s2*t1; - float r = ((rd*rd) > FLT_EPSILON) ? 1.0F / rd : 1024.f; //some made up large ratio for division by zero + float r = ((rd*rd) > FLT_EPSILON) ? (1.0f / rd) + : ((rd > 0.0f) ? 1024.f : -1024.f); //some made up large ratio for division by zero llassert(llfinite(r)); llassert(!llisnan(r)); @@ -6801,7 +6802,8 @@ void CalculateTangentArray(U32 vertexCount, const LLVector4a *vertex, const LLVe F32 rd = s1*t2-s2*t1; - float r = ((rd*rd) > FLT_EPSILON) ? 1.0F / rd : 1024.f; //some made up large ratio for division by zero + float r = ((rd*rd) > FLT_EPSILON) ? (1.0f / rd) + : ((rd > 0.0f) ? 1024.f : -1024.f); //some made up large ratio for division by zero llassert(llfinite(r)); llassert(!llisnan(r)); diff --git a/indra/llmessage/llpartdata.cpp b/indra/llmessage/llpartdata.cpp index de369dc3c..41a0310ce 100644 --- a/indra/llmessage/llpartdata.cpp +++ b/indra/llmessage/llpartdata.cpp @@ -37,53 +37,46 @@ -const S32 PS_PART_DATA_BLOCK_SIZE = 4 + 2 + 4 + 4 + 2 + 2; // 18 -const S32 PS_DATA_BLOCK_SIZE = 68 + PS_PART_DATA_BLOCK_SIZE; // 68 + 18 = 86 +const S32 PS_PART_DATA_GLOW_SIZE = 2; +const S32 PS_PART_DATA_BLEND_SIZE = 2; +const S32 PS_LEGACY_PART_DATA_BLOCK_SIZE = 4 + 2 + 4 + 4 + 2 + 2; //18 +const S32 PS_SYS_DATA_BLOCK_SIZE = 68; +const S32 PS_MAX_DATA_BLOCK_SIZE = PS_SYS_DATA_BLOCK_SIZE+ + PS_LEGACY_PART_DATA_BLOCK_SIZE + + PS_PART_DATA_BLEND_SIZE + + PS_PART_DATA_GLOW_SIZE+ + 8; //two S32 size fields + +const S32 PS_LEGACY_DATA_BLOCK_SIZE = PS_SYS_DATA_BLOCK_SIZE + PS_LEGACY_PART_DATA_BLOCK_SIZE; + + +const U32 PART_DATA_MASK = LLPartData::LL_PART_DATA_GLOW | LLPartData::LL_PART_DATA_BLEND; + const F32 MAX_PART_SCALE = 4.f; -BOOL LLPartData::pack(LLDataPacker &dp) +bool LLPartData::hasGlow() const { - LLColor4U coloru; - dp.packU32(mFlags, "pdflags"); - dp.packFixed(mMaxAge, "pdmaxage", FALSE, 8, 8); - coloru.setVec(mStartColor); - dp.packColor4U(coloru, "pdstartcolor"); - coloru.setVec(mEndColor); - dp.packColor4U(coloru, "pdendcolor"); - dp.packFixed(mStartScale.mV[0], "pdstartscalex", FALSE, 3, 5); - dp.packFixed(mStartScale.mV[1], "pdstartscaley", FALSE, 3, 5); - dp.packFixed(mEndScale.mV[0], "pdendscalex", FALSE, 3, 5); - dp.packFixed(mEndScale.mV[1], "pdendscaley", FALSE, 3, 5); - return TRUE; + return mStartGlow > 0.f || mEndGlow > 0.f; } -LLSD LLPartData::asLLSD() const +bool LLPartData::hasBlendFunc() const { - LLSD sd = LLSD(); - sd["pdflags"] = ll_sd_from_U32(mFlags); - sd["pdmaxage"] = mMaxAge; - sd["pdstartcolor"] = ll_sd_from_color4(mStartColor); - sd["pdendcolor"] = ll_sd_from_color4(mEndColor); - sd["pdstartscale"] = ll_sd_from_vector2(mStartScale); - sd["pdendscale"] = ll_sd_from_vector2(mEndScale); - return sd; + return mBlendFuncSource != LLPartData::LL_PART_BF_SOURCE_ALPHA || mBlendFuncDest != LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA; } -bool LLPartData::fromLLSD(LLSD& sd) +S32 LLPartData::getSize() const { - mFlags = ll_U32_from_sd(sd["pdflags"]); - mMaxAge = (F32)sd["pdmaxage"].asReal(); - mStartColor = ll_color4_from_sd(sd["pdstartcolor"]); - mEndColor = ll_color4_from_sd(sd["pdendcolor"]); - mStartScale = ll_vector2_from_sd(sd["pdstartscale"]); - mEndScale = ll_vector2_from_sd(sd["pdendscale"]); - return true; + S32 size = PS_LEGACY_PART_DATA_BLOCK_SIZE; + if (hasGlow()) size += PS_PART_DATA_GLOW_SIZE; + if (hasBlendFunc()) size += PS_PART_DATA_BLEND_SIZE; + + return size; } -BOOL LLPartData::unpack(LLDataPacker &dp) +BOOL LLPartData::unpackLegacy(LLDataPacker &dp) { LLColor4U coloru; @@ -98,9 +91,70 @@ BOOL LLPartData::unpack(LLDataPacker &dp) dp.unpackFixed(mStartScale.mV[1], "pdstartscaley", FALSE, 3, 5); dp.unpackFixed(mEndScale.mV[0], "pdendscalex", FALSE, 3, 5); dp.unpackFixed(mEndScale.mV[1], "pdendscaley", FALSE, 3, 5); + + mStartGlow = 0.f; + mEndGlow = 0.f; + mBlendFuncSource = LLPartData::LL_PART_BF_SOURCE_ALPHA; + mBlendFuncDest = LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA; + return TRUE; } +BOOL LLPartData::unpack(LLDataPacker &dp) +{ + S32 size = 0; + dp.unpackS32(size, "partsize"); + + unpackLegacy(dp); + size -= PS_LEGACY_PART_DATA_BLOCK_SIZE; + + if (mFlags & LL_PART_DATA_GLOW) + { + if (size < PS_PART_DATA_GLOW_SIZE) return FALSE; + + U8 tmp_glow = 0; + dp.unpackU8(tmp_glow,"pdstartglow"); + mStartGlow = tmp_glow / 255.f; + dp.unpackU8(tmp_glow,"pdendglow"); + mEndGlow = tmp_glow / 255.f; + + size -= PS_PART_DATA_GLOW_SIZE; + } + else + { + mStartGlow = 0.f; + mEndGlow = 0.f; + } + + if (mFlags & LL_PART_DATA_BLEND) + { + if (size < PS_PART_DATA_BLEND_SIZE) return FALSE; + dp.unpackU8(mBlendFuncSource,"pdblendsource"); + dp.unpackU8(mBlendFuncDest,"pdblenddest"); + size -= PS_PART_DATA_BLEND_SIZE; + } + else + { + mBlendFuncSource = LLPartData::LL_PART_BF_SOURCE_ALPHA; + mBlendFuncDest = LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA; + } + + if (size > 0) + { //leftover bytes, unrecognized parameters + U8 feh = 0; + while (size > 0) + { //read remaining bytes in block + dp.unpackU8(feh, "whippang"); + size--; + } + + //this particle system won't display properly, better to not show anything + return FALSE; + } + + + return TRUE; +} void LLPartData::setFlags(const U32 flags) { @@ -148,6 +202,18 @@ void LLPartData::setEndAlpha(const F32 alpha) mEndColor.mV[3] = alpha; } +// static +bool LLPartData::validBlendFunc(S32 func) +{ + if (func >= 0 + && func < LL_PART_BF_COUNT + && func != UNSUPPORTED_DEST_ALPHA + && func != UNSUPPORTED_ONE_MINUS_DEST_ALPHA) + { + return true; + } + return false; +} LLPartSysData::LLPartSysData() { @@ -160,6 +226,10 @@ LLPartSysData::LLPartSysData() mPartData.mStartScale = LLVector2(1.f, 1.f); mPartData.mEndScale = LLVector2(1.f, 1.f); mPartData.mMaxAge = 10.0; + mPartData.mBlendFuncSource = LLPartData::LL_PART_BF_SOURCE_ALPHA; + mPartData.mBlendFuncDest = LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA; + mPartData.mStartGlow = 0.f; + mPartData.mEndGlow = 0.f; mMaxAge = 0.0; mStartAge = 0.0; @@ -175,38 +245,7 @@ LLPartSysData::LLPartSysData() mNumParticles = 0; } - -BOOL LLPartSysData::pack(LLDataPacker &dp) -{ - dp.packU32(mCRC, "pscrc"); - dp.packU32(mFlags, "psflags"); - dp.packU8(mPattern, "pspattern"); - dp.packFixed(mMaxAge, "psmaxage", FALSE, 8, 8); - dp.packFixed(mStartAge, "psstartage", FALSE, 8, 8); - dp.packFixed(mInnerAngle, "psinnerangle", FALSE, 3, 5); - dp.packFixed(mOuterAngle, "psouterangle", FALSE, 3, 5); - dp.packFixed(mBurstRate, "psburstrate", FALSE, 8, 8); - dp.packFixed(mBurstRadius, "psburstradius", FALSE, 8, 8); - dp.packFixed(mBurstSpeedMin, "psburstspeedmin", FALSE, 8, 8); - dp.packFixed(mBurstSpeedMax, "psburstspeedmax", FALSE, 8, 8); - dp.packU8(mBurstPartCount, "psburstpartcount"); - - dp.packFixed(mAngularVelocity.mV[0], "psangvelx", TRUE, 8, 7); - dp.packFixed(mAngularVelocity.mV[1], "psangvely", TRUE, 8, 7); - dp.packFixed(mAngularVelocity.mV[2], "psangvelz", TRUE, 8, 7); - - dp.packFixed(mPartAccel.mV[0], "psaccelx", TRUE, 8, 7); - dp.packFixed(mPartAccel.mV[1], "psaccely", TRUE, 8, 7); - dp.packFixed(mPartAccel.mV[2], "psaccelz", TRUE, 8, 7); - - dp.packUUID(mPartImageID, "psuuid"); - dp.packUUID(mTargetUUID, "pstargetuuid"); - mPartData.pack(dp); - return TRUE; -} - - -BOOL LLPartSysData::unpack(LLDataPacker &dp) +BOOL LLPartSysData::unpackSystem(LLDataPacker &dp) { dp.unpackU32(mCRC, "pscrc"); dp.unpackU32(mFlags, "psflags"); @@ -232,20 +271,58 @@ BOOL LLPartSysData::unpack(LLDataPacker &dp) dp.unpackUUID(mPartImageID, "psuuid"); dp.unpackUUID(mTargetUUID, "pstargetuuid"); - mPartData.unpack(dp); return TRUE; } +BOOL LLPartSysData::unpackLegacy(LLDataPacker &dp) +{ + unpackSystem(dp); + mPartData.unpackLegacy(dp); + + return TRUE; +} + +BOOL LLPartSysData::unpack(LLDataPacker &dp) +{ + // syssize is currently unused. Adding now when modifying the 'version to make extensible in the future + S32 size = 0; + dp.unpackS32(size, "syssize"); + + if (size != PS_SYS_DATA_BLOCK_SIZE) + { //unexpected size, this viewer doesn't know how to parse this particle system + + //skip to LLPartData block + U8 feh = 0; + + for (U32 i = 0; i < size; ++i) + { + dp.unpackU8(feh, "whippang"); + } + + dp.unpackS32(size, "partsize"); + //skip LLPartData block + for (U32 i = 0; i < size; ++i) + { + dp.unpackU8(feh, "whippang"); + } + return FALSE; + } + + unpackSystem(dp); + + return mPartData.unpack(dp); +} + std::ostream& operator<<(std::ostream& s, const LLPartSysData &data) { - s << "Flags: " << std::hex << data.mFlags << std::dec; - s << " Pattern: " << std::hex << (U32) data.mPattern << std::dec << "\n"; + s << "Flags: " << std::hex << data.mFlags; + s << " Pattern: " << std::hex << (U32) data.mPattern << "\n"; s << "Age: [" << data.mStartAge << ", " << data.mMaxAge << "]\n"; s << "Angle: [" << data.mInnerAngle << ", " << data.mOuterAngle << "]\n"; s << "Burst Rate: " << data.mBurstRate << "\n"; s << "Burst Radius: " << data.mBurstRadius << "\n"; s << "Burst Speed: [" << data.mBurstSpeedMin << ", " << data.mBurstSpeedMax << "]\n"; - s << "Burst Part Count: " << std::hex << (U32) data.mBurstPartCount << std::dec << "\n"; + s << "Burst Part Count: " << std::hex << (U32) data.mBurstPartCount << "\n"; s << "Angular Velocity: " << data.mAngularVelocity << "\n"; s << "Accel: " << data.mPartAccel; return s; @@ -253,7 +330,7 @@ std::ostream& operator<<(std::ostream& s, const LLPartSysData &data) BOOL LLPartSysData::isNullPS(const S32 block_num) { - U8 ps_data_block[PS_DATA_BLOCK_SIZE]; + U8 ps_data_block[PS_MAX_DATA_BLOCK_SIZE]; U32 crc; S32 size; @@ -264,14 +341,28 @@ BOOL LLPartSysData::isNullPS(const S32 block_num) { return TRUE; } - else if (size != PS_DATA_BLOCK_SIZE) + + if (size > PS_MAX_DATA_BLOCK_SIZE) { - llwarns << "PSBlock is wrong size for particle system data - got " << size << ", expecting " << PS_DATA_BLOCK_SIZE << llendl; + //size is too big, newer particle version unsupported return TRUE; } - gMessageSystem->getBinaryData("ObjectData", "PSBlock", ps_data_block, PS_DATA_BLOCK_SIZE, block_num, PS_DATA_BLOCK_SIZE); - LLDataPackerBinaryBuffer dp(ps_data_block, PS_DATA_BLOCK_SIZE); + gMessageSystem->getBinaryData("ObjectData", "PSBlock", ps_data_block, size, block_num, PS_MAX_DATA_BLOCK_SIZE); + + LLDataPackerBinaryBuffer dp(ps_data_block, size); + if (size > PS_LEGACY_DATA_BLOCK_SIZE) + { + // non legacy systems pack a size before the CRC + S32 tmp = 0; + dp.unpackS32(tmp, "syssize"); + + if (tmp > PS_SYS_DATA_BLOCK_SIZE) + { //unknown system data block size, don't know how to parse it, treat as NULL + return TRUE; + } + } + dp.unpackU32(crc, "crc"); if (crc == 0) @@ -281,50 +372,37 @@ BOOL LLPartSysData::isNullPS(const S32 block_num) return FALSE; } - -//static -BOOL LLPartSysData::packNull() -{ - U8 ps_data_block[PS_DATA_BLOCK_SIZE]; - gMessageSystem->addBinaryData("PSBlock", ps_data_block, 0); - return TRUE; -} - - -BOOL LLPartSysData::packBlock() -{ - U8 ps_data_block[PS_DATA_BLOCK_SIZE]; - - LLDataPackerBinaryBuffer dp(ps_data_block, PS_DATA_BLOCK_SIZE); - pack(dp); - - // Add to message - gMessageSystem->addBinaryData("PSBlock", ps_data_block, PS_DATA_BLOCK_SIZE); - - return TRUE; -} - - BOOL LLPartSysData::unpackBlock(const S32 block_num) { - U8 ps_data_block[PS_DATA_BLOCK_SIZE]; + U8 ps_data_block[PS_MAX_DATA_BLOCK_SIZE]; // Check size of block S32 size = gMessageSystem->getSize("ObjectData", block_num, "PSBlock"); - if (size != PS_DATA_BLOCK_SIZE) + if (size > PS_MAX_DATA_BLOCK_SIZE) { - llwarns << "PSBlock is wrong size for particle system data - got " << size << ", expecting " << PS_DATA_BLOCK_SIZE << llendl; + // Larger packets are newer and unsupported return FALSE; } // Get from message - gMessageSystem->getBinaryData("ObjectData", "PSBlock", ps_data_block, PS_DATA_BLOCK_SIZE, block_num, PS_DATA_BLOCK_SIZE); + gMessageSystem->getBinaryData("ObjectData", "PSBlock", ps_data_block, size, block_num, PS_MAX_DATA_BLOCK_SIZE); - LLDataPackerBinaryBuffer dp(ps_data_block, PS_DATA_BLOCK_SIZE); - unpack(dp); + LLDataPackerBinaryBuffer dp(ps_data_block, size); - return TRUE; + if (size == PS_LEGACY_DATA_BLOCK_SIZE) + { + return unpackLegacy(dp); + } + else + { + return unpack(dp); + } +} + +bool LLPartSysData::isLegacyCompatible() const +{ + return !mPartData.hasGlow() && !mPartData.hasBlendFunc(); } void LLPartSysData::clampSourceParticleRate() diff --git a/indra/llmessage/llpartdata.h b/indra/llmessage/llpartdata.h index a4ef058b3..ed5c1a6ac 100644 --- a/indra/llmessage/llpartdata.h +++ b/indra/llmessage/llpartdata.h @@ -70,7 +70,12 @@ enum LLPSScriptFlags LLPS_SRC_TARGET_UUID, LLPS_SRC_OMEGA, LLPS_SRC_ANGLE_BEGIN, - LLPS_SRC_ANGLE_END + LLPS_SRC_ANGLE_END, + + LLPS_PART_BLEND_FUNC_SOURCE, + LLPS_PART_BLEND_FUNC_DEST, + LLPS_PART_START_GLOW, + LLPS_PART_END_GLOW }; @@ -83,11 +88,13 @@ public: mParameter(0.f) { } + BOOL unpackLegacy(LLDataPacker &dp); BOOL unpack(LLDataPacker &dp); + BOOL pack(LLDataPacker &dp); - LLSD asLLSD() const; - operator LLSD() const {return asLLSD(); } - bool fromLLSD(LLSD& sd); + + bool hasGlow() const; + bool hasBlendFunc() const; // Masks for the different particle flags enum @@ -102,17 +109,39 @@ public: LL_PART_TARGET_LINEAR_MASK = 0x80, // Particle uses a direct linear interpolation LL_PART_EMISSIVE_MASK = 0x100, // Particle is "emissive", instead of being lit LL_PART_BEAM_MASK = 0x200, // Particle is a "beam" connecting source and target + LL_PART_RIBBON_MASK = 0x400, // Particles are joined together into one continuous triangle strip // Not implemented yet! //LL_PART_RANDOM_ACCEL_MASK = 0x100, // Particles have random acceleration //LL_PART_RANDOM_VEL_MASK = 0x200, // Particles have random velocity shifts" //LL_PART_TRAIL_MASK = 0x400, // Particles have historical "trails" + //sYSTEM SET FLAGS + LL_PART_DATA_GLOW = 0x10000, + LL_PART_DATA_BLEND = 0x20000, + // Viewer side use only! LL_PART_HUD = 0x40000000, LL_PART_DEAD_MASK = 0x80000000, }; + enum + { + LL_PART_BF_ONE = 0, + LL_PART_BF_ZERO = 1, + LL_PART_BF_DEST_COLOR = 2, + LL_PART_BF_SOURCE_COLOR = 3, + LL_PART_BF_ONE_MINUS_DEST_COLOR = 4, + LL_PART_BF_ONE_MINUS_SOURCE_COLOR = 5, + UNSUPPORTED_DEST_ALPHA = 6, + LL_PART_BF_SOURCE_ALPHA = 7, + UNSUPPORTED_ONE_MINUS_DEST_ALPHA = 8, + LL_PART_BF_ONE_MINUS_SOURCE_ALPHA = 9, + LL_PART_BF_COUNT = 10 + }; + + static bool validBlendFunc(S32 func); + void setFlags(const U32 flags); void setMaxAge(const F32 max_age); void setStartScale(const F32 xs, F32 ys); @@ -126,6 +155,9 @@ public: friend class LLPartSysData; friend class LLViewerPartSourceScript; +private: + S32 getSize() const; + // These are public because I'm really lazy... public: U32 mFlags; // Particle state/interpolators in effect @@ -137,6 +169,12 @@ public: LLVector3 mPosOffset; // Offset from source if using FOLLOW_SOURCE F32 mParameter; // A single floating point parameter + + F32 mStartGlow; + F32 mEndGlow; + + U8 mBlendFuncSource; + U8 mBlendFuncDest; }; @@ -146,15 +184,13 @@ public: LLPartSysData(); BOOL unpack(LLDataPacker &dp); - BOOL pack(LLDataPacker &dp); - - + BOOL unpackLegacy(LLDataPacker &dp); BOOL unpackBlock(const S32 block_num); - BOOL packBlock(); - - static BOOL packNull(); + static BOOL isNullPS(const S32 block_num); // Returns FALSE if this is a "NULL" particle system (i.e. no system) + bool isLegacyCompatible() const; + // Different masks for effects on the source enum { @@ -187,7 +223,12 @@ public: void clampSourceParticleRate(); friend std::ostream& operator<<(std::ostream& s, const LLPartSysData &data); // Stream a + + S32 getdataBlockSize() const; +private: + BOOL unpackSystem(LLDataPacker &dp); + public: // Public because I'm lazy.... diff --git a/indra/llprimitive/llprimitive.cpp b/indra/llprimitive/llprimitive.cpp index 36969d27c..bdf15cb94 100644 --- a/indra/llprimitive/llprimitive.cpp +++ b/indra/llprimitive/llprimitive.cpp @@ -38,6 +38,8 @@ #include "lldatapacker.h" #include "llsdutil_math.h" #include "llprimtexturelist.h" +#include "imageids.h" +#include "llmaterialid.h" /** * exported constants @@ -1062,7 +1064,7 @@ S32 LLPrimitive::unpackTEField(U8 *cur_ptr, U8 *buffer_end, U8 *data_ptr, U8 dat while ((cur_ptr < buffer_end) && (*cur_ptr != 0)) { -// llinfos << "TE exception" << llendl; + LL_DEBUGS("TEFieldDecode") << "TE exception" << LL_ENDL; i = 0; while (*cur_ptr & 0x80) { @@ -1077,14 +1079,16 @@ S32 LLPrimitive::unpackTEField(U8 *cur_ptr, U8 *buffer_end, U8 *data_ptr, U8 dat if (i & 0x01) { htonmemcpy(data_ptr+(j*data_size),cur_ptr,type,data_size); -// char foo[64]; -// sprintf(foo,"%x %x",*(data_ptr+(j*data_size)), *(data_ptr+(j*data_size)+1)); -// llinfos << "Assigning " << foo << " to face " << j << llendl; + LL_DEBUGS("TEFieldDecode") << "Assigning " ; + char foo[64]; + sprintf(foo,"%x %x",*(data_ptr+(j*data_size)), *(data_ptr+(j*data_size)+1)); + LL_CONT << foo << " to face " << j << LL_ENDL; } i = i >> 1; } cur_ptr += data_size; } + llassert(cur_ptr <= buffer_end); // buffer underrun return (S32)(cur_ptr - start_loc); } diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index 7dcce3e76..974bbb3b5 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -159,6 +159,11 @@ BOOL LLGLSLShader::createShader(std::vector * attributes, // Create program mProgramObject = glCreateProgramObjectARB(); +#if LL_DARWIN + // work-around missing mix(vec3,vec3,bvec3) + mDefines["OLD_SELECT"] = "1"; +#endif + //compile new source vector< pair >::iterator fileIter = mShaderFiles.begin(); for ( ; fileIter != mShaderFiles.end(); fileIter++ ) diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index 782562886..85c5a0cb5 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -1075,6 +1075,15 @@ LLRender::~LLRender() void LLRender::init() { + if (sGLCoreProfile && !LLVertexBuffer::sUseVAO) + { //bind a dummy vertex array object so we're core profile compliant +#ifdef GL_ARB_vertex_array_object + U32 ret; + glGenVertexArrays(1, &ret); + glBindVertexArray(ret); +#endif + } + llassert_always(mBuffer.isNull()) ; stop_glerror(); mBuffer = new LLVertexBuffer(immediate_mask, 0); @@ -2295,6 +2304,22 @@ void LLRender::diffuseColor4ubv(const U8* c) } } +void LLRender::diffuseColor4ub(U8 r, U8 g, U8 b, U8 a) +{ + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + llassert(!LLGLSLShader::sNoFixedFunction || shader != NULL); + + if (shader) + { + shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, r/255.f, g/255.f, b/255.f, a/255.f); + } + else + { + glColor4ub(r,g,b,a); + } +} + + void LLRender::debugTexUnits(void) { LL_INFOS("TextureUnit") << "Active TexUnit: " << mCurrTextureUnitIndex << LL_ENDL; diff --git a/indra/llrender/llrender.h b/indra/llrender/llrender.h index 2ca28c669..66786d6ee 100644 --- a/indra/llrender/llrender.h +++ b/indra/llrender/llrender.h @@ -262,6 +262,14 @@ class LLRender friend class LLTexUnit; public: + enum eTexIndex + { + DIFFUSE_MAP = 0, + NORMAL_MAP, + SPECULAR_MAP, + NUM_TEXTURE_CHANNELS, + }; + typedef enum { TRIANGLES = 0, TRIANGLE_STRIP, @@ -390,6 +398,7 @@ public: void diffuseColor4f(F32 r, F32 g, F32 b, F32 a); void diffuseColor4fv(const F32* c); void diffuseColor4ubv(const U8* c); + void diffuseColor4ub(U8 r, U8 g, U8 b, U8 a); void vertexBatchPreTransformed(LLVector4a* verts, S32 vert_count); void vertexBatchPreTransformed(LLVector4a* verts, LLVector2* uvs, S32 vert_count); diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp index c4223da93..87d1cfd40 100644 --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -534,8 +534,11 @@ GLhandleARB LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shade range = mShaderObjects.equal_range(filename); for (std::multimap::iterator it = range.first; it != range.second;++it) { - if((*it).second.mLevel == shader_level && (*it).second.mType == type) + if((*it).second.mLevel == shader_level && (*it).second.mType == type && (*it).second.mDefinitions == (defines ? *defines : std::map())) + { + llinfos << "Loading cached shader for " << filename << llendl; return (*it).second.mHandle; + } } GLenum error = GL_NO_ERROR; @@ -713,6 +716,8 @@ GLhandleARB LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shade } */ + text[count++] = strdup("#define HAS_DIFFUSE_LOOKUP 1\n"); + //uniform declartion for (S32 i = 0; i < texture_index_channels; ++i) { @@ -770,6 +775,10 @@ GLhandleARB LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shade llerrs << "Indexed texture rendering requires GLSL 1.30 or later." << llendl; } } + else + { + text[count++] = strdup("#define HAS_DIFFUSE_LOOKUP 0\n"); + } //copy file into memory while( fgets((char *)buff, 1024, file) != NULL && count < LL_ARRAY_SIZE(text) ) @@ -918,7 +927,7 @@ GLhandleARB LLShaderMgr::loadShaderFile(const std::string& filename, S32 & shade if (ret) { // Add shader file to map - mShaderObjects.insert(make_pair(filename,CachedObjectInfo(ret,try_gpu_class,type))); + mShaderObjects.insert(make_pair(filename,CachedObjectInfo(ret,try_gpu_class,type,defines))); shader_level = try_gpu_class; } else @@ -1129,6 +1138,7 @@ void LLShaderMgr::initAttribsAndUniforms() mReservedUniforms.push_back("minimum_alpha"); + mReservedUniforms.push_back("emissive_brightness"); mReservedUniforms.push_back("shadow_matrix"); mReservedUniforms.push_back("env_mat"); @@ -1191,6 +1201,12 @@ void LLShaderMgr::initAttribsAndUniforms() mReservedUniforms.push_back("projectionMap"); mReservedUniforms.push_back("norm_mat"); + mReservedUniforms.push_back("global_gamma"); + mReservedUniforms.push_back("texture_gamma"); + + mReservedUniforms.push_back("specular_color"); + mReservedUniforms.push_back("env_intensity"); + mReservedUniforms.push_back("matrixPalette"); mReservedUniforms.push_back("screenTex"); @@ -1230,6 +1246,7 @@ void LLShaderMgr::initAttribsAndUniforms() mReservedUniforms.push_back("alpha_ramp"); mReservedUniforms.push_back("origin"); + mReservedUniforms.push_back("display_gamma"); llassert(mReservedUniforms.size() == END_RESERVED_UNIFORMS); std::set dupe_check; diff --git a/indra/llrender/llshadermgr.h b/indra/llrender/llshadermgr.h index 6d4582ec3..c13485b71 100644 --- a/indra/llrender/llshadermgr.h +++ b/indra/llrender/llshadermgr.h @@ -111,6 +111,7 @@ public: GLOW_DELTA, MINIMUM_ALPHA, + EMISSIVE_BRIGHTNESS, DEFERRED_SHADOW_MATRIX, DEFERRED_ENV_MAT, @@ -168,7 +169,14 @@ public: DEFERRED_PROJECTION, DEFERRED_NORM_MATRIX, + GLOBAL_GAMMA, + TEXTURE_GAMMA, + + SPECULAR_COLOR, + ENVIRONMENT_INTENSITY, + AVATAR_MATRIX, + WATER_SCREENTEX, WATER_SCREENDEPTH, WATER_REFTEX, @@ -204,7 +212,9 @@ public: TERRAIN_DETAIL2, TERRAIN_DETAIL3, TERRAIN_ALPHARAMP, + SHINY_ORIGIN, +DISPLAY_GAMMA, END_RESERVED_UNIFORMS } eGLSLReservedUniforms; @@ -228,11 +238,12 @@ public: public: struct CachedObjectInfo { - CachedObjectInfo(GLhandleARB handle, U32 level, GLenum type) : - mHandle(handle), mLevel(level), mType(type) {} + CachedObjectInfo(GLhandleARB handle, U32 level, GLenum type, std::map *definitions) : + mHandle(handle), mLevel(level), mType(type), mDefinitions(definitions ? *definitions : std::map()){} GLhandleARB mHandle; //Actual handle of the opengl shader object. U32 mLevel; //Level /might/ not be needed, but it's stored to ensure there's no change in behavior. GLenum mType; //GL_VERTEX_SHADER_ARB or GL_FRAGMENT_SHADER_ARB. Tracked because some utility shaders can be loaded as both types (carefully). + std::map mDefinitions; }; // Map of shader names to compiled std::multimap mShaderObjects; //Singu Note: Packing more info here. Doing such provides capability to skip unneeded duplicate loading.. diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index f5d8bef9c..0c59d99c0 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -2035,7 +2035,10 @@ bool LLVertexBuffer::getTexCoord1Strider(LLStrider& strider, S32 inde { return VertexBufferStrider::get(*this, strider, index, count, map_range); } - +bool LLVertexBuffer::getTexCoord2Strider(LLStrider& strider, S32 index, S32 count, bool map_range) +{ + return VertexBufferStrider::get(*this, strider, index, count, map_range); +} bool LLVertexBuffer::getNormalStrider(LLStrider& strider, S32 index, S32 count, bool map_range) { return VertexBufferStrider::get(*this, strider, index, count, map_range); @@ -2371,7 +2374,8 @@ void LLVertexBuffer::setupVertexBuffer(U32 data_mask) if (data_mask & MAP_COLOR) { S32 loc = TYPE_COLOR; - void* ptr = (void*)(base + mOffsets[TYPE_COLOR]); + //bind emissive instead of color pointer if emissive is present + void* ptr = (data_mask & MAP_EMISSIVE) ? (void*)(base + mOffsets[TYPE_EMISSIVE]) : (void*)(base + mOffsets[TYPE_COLOR]); glVertexAttribPointerARB(loc, 4, GL_UNSIGNED_BYTE, GL_TRUE, LLVertexBuffer::sTypeSize[TYPE_COLOR], ptr); } if (data_mask & MAP_EMISSIVE) @@ -2379,6 +2383,12 @@ void LLVertexBuffer::setupVertexBuffer(U32 data_mask) S32 loc = TYPE_EMISSIVE; void* ptr = (void*)(base + mOffsets[TYPE_EMISSIVE]); glVertexAttribPointerARB(loc, 4, GL_UNSIGNED_BYTE, GL_TRUE, LLVertexBuffer::sTypeSize[TYPE_EMISSIVE], ptr); + + if (!(data_mask & MAP_COLOR)) + { //map emissive to color channel when color is not also being bound to avoid unnecessary shader swaps + loc = TYPE_COLOR; + glVertexAttribPointerARB(loc, 4, GL_UNSIGNED_BYTE, GL_TRUE, LLVertexBuffer::sTypeSize[TYPE_EMISSIVE], ptr); + } } if (data_mask & MAP_WEIGHT) { diff --git a/indra/llrender/llvertexbuffer.h b/indra/llrender/llvertexbuffer.h index 0025937ba..ea5aca583 100644 --- a/indra/llrender/llvertexbuffer.h +++ b/indra/llrender/llvertexbuffer.h @@ -246,6 +246,7 @@ public: bool getIndexStrider(LLStrider& strider, S32 index=0, S32 count = -1, bool map_range = false); bool getTexCoord0Strider(LLStrider& strider, S32 index=0, S32 count = -1, bool map_range = false); bool getTexCoord1Strider(LLStrider& strider, S32 index=0, S32 count = -1, bool map_range = false); + bool getTexCoord2Strider(LLStrider& strider, S32 index=0, S32 count = -1, bool map_range = false); bool getNormalStrider(LLStrider& strider, S32 index=0, S32 count = -1, bool map_range = false); bool getTangentStrider(LLStrider& strider, S32 index=0, S32 count = -1, bool map_range = false); bool getTangentStrider(LLStrider& strider, S32 index=0, S32 count = -1, bool map_range = false); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index d7dbce179..edb6c8c9d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -153,6 +153,7 @@ set(viewer_SOURCE_FILES lldrawpoolavatar.cpp lldrawpoolbump.cpp lldrawpoolground.cpp + lldrawpoolmaterials.cpp lldrawpoolsimple.cpp lldrawpoolsky.cpp lldrawpoolterrain.cpp @@ -669,6 +670,7 @@ set(viewer_HEADER_FILES lldrawpoolalpha.h lldrawpoolavatar.h lldrawpoolbump.h + lldrawpoolmaterials.h lldrawpoolground.h lldrawpoolsimple.h lldrawpoolsky.h diff --git a/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl b/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl index dd87ddb33..1aa3261fe 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/alphaF.glsl @@ -25,31 +25,429 @@ #extension GL_ARB_texture_rectangle : enable +#define INDEXED 1 +#define NON_INDEXED 2 +#define NON_INDEXED_NO_COLOR 3 + #ifdef DEFINE_GL_FRAGCOLOR out vec4 frag_color; #else #define frag_color gl_FragColor #endif -uniform sampler2DRect depthMap; +//uniform float display_gamma; +uniform vec4 gamma; +uniform vec4 lightnorm; +uniform vec4 sunlight_color; +uniform vec4 ambient; +uniform vec4 blue_horizon; +uniform vec4 blue_density; +uniform float haze_horizon; +uniform float haze_density; +uniform float cloud_shadow; +uniform float density_multiplier; +uniform float distance_multiplier; +uniform float max_y; +uniform vec4 glow; +uniform float scene_light_strength; +uniform mat3 env_mat; +uniform mat3 ssao_effect_mat; -vec4 diffuseLookup(vec2 texcoord); +uniform vec3 sun_dir; -uniform vec2 screen_res; +#if HAS_SHADOW +uniform sampler2DShadow shadowMap0; +uniform sampler2DShadow shadowMap1; +uniform sampler2DShadow shadowMap2; +uniform sampler2DShadow shadowMap3; -vec3 atmosLighting(vec3 light); -vec3 scaleSoftClip(vec3 light); +uniform vec2 shadow_res; + +uniform mat4 shadow_matrix[6]; +uniform vec4 shadow_clip; +uniform float shadow_bias; + +#endif + +#ifdef USE_DIFFUSE_TEX +uniform sampler2D diffuseMap; +#endif -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; VARYING vec3 vary_fragcoord; VARYING vec3 vary_position; -VARYING vec3 vary_pointlight_col; - -VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; +VARYING vec3 vary_norm; + +#ifdef USE_VERTEX_COLOR +VARYING vec4 vertex_color; +#endif + +vec3 vary_PositionEye; +vec3 vary_SunlitColor; +vec3 vary_AmblitColor; +vec3 vary_AdditiveColor; +vec3 vary_AtmosAttenuation; uniform mat4 inv_proj; +uniform vec2 screen_res; + +uniform vec4 light_position[8]; +uniform vec3 light_direction[8]; +uniform vec3 light_attenuation[8]; +uniform vec3 light_diffuse[8]; + +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lte); +#endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + +vec3 calcDirectionalLight(vec3 n, vec3 l) +{ + float a = max(dot(n,l),0.0); + a = pow(a, 1.0/1.3); + return vec3(a,a,a); +} + +vec3 calcPointLightOrSpotLight(vec3 light_col, vec3 diffuse, vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float fa, float is_pointlight) +{ + //get light vector + vec3 lv = lp.xyz-v; + + //get distance + float d = length(lv); + + float da = 1.0; + + vec3 col = vec3(0); + + if (d > 0.0 && la > 0.0 && fa > 0.0) + { + //normalize light vector + lv = normalize(lv); + + //distance attenuation + float dist = d/la; + float dist_atten = clamp(1.0-(dist-1.0*(1.0-fa))/fa, 0.0, 1.0); + dist_atten *= dist_atten; + dist_atten *= 2.0; + + // spotlight coefficient. + float spot = max(dot(-ln, lv), is_pointlight); + da *= spot*spot; // GL_SPOT_EXPONENT=2 + + //angular attenuation + da *= max(dot(n, lv), 0.0); + + float lit = max(da * dist_atten,0.0); + + col = light_col * lit * diffuse; + + // no spec for alpha shader... + } + + return max(col, vec3(0.0,0.0,0.0)); +} + +#if HAS_SHADOW +float pcfShadow(sampler2DShadow shadowMap, vec4 stc) +{ + stc.xyz /= stc.w; + stc.z += shadow_bias; + + stc.x = floor(stc.x*shadow_res.x + fract(stc.y*shadow_res.y*12345))/shadow_res.x; // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here + + float cs = shadow2D(shadowMap, stc.xyz).x; + float shadow = cs; + + shadow += shadow2D(shadowMap, stc.xyz+vec3(2.0/shadow_res.x, 1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(1.0/shadow_res.x, -1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-1.0/shadow_res.x, 1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-2.0/shadow_res.x, -1.5/shadow_res.y, 0.0)).x; + + return shadow*0.2; +} +#endif + +#ifdef WATER_FOG +uniform vec4 waterPlane; +uniform vec4 waterFogColor; +uniform float waterFogDensity; +uniform float waterFogKS; + +vec4 applyWaterFogDeferred(vec3 pos, vec4 color) +{ + //normalize view vector + vec3 view = normalize(pos); + float es = -(dot(view, waterPlane.xyz)); + + //find intersection point with water plane and eye vector + + //get eye depth + float e0 = max(-waterPlane.w, 0.0); + + vec3 int_v = waterPlane.w > 0.0 ? view * waterPlane.w/es : vec3(0.0, 0.0, 0.0); + + //get object depth + float depth = length(pos - int_v); + + //get "thickness" of water + float l = max(depth, 0.1); + + float kd = waterFogDensity; + float ks = waterFogKS; + vec4 kc = waterFogColor; + + float F = 0.98; + + float t1 = -kd * pow(F, ks * e0); + float t2 = kd + ks * es; + float t3 = pow(F, t2*l) - 1.0; + + float L = min(t1/t2*t3, 1.0); + + float D = pow(0.98, l*kd); + + color.rgb = color.rgb * D + kc.rgb * L; + color.a = kc.a + color.a; + + return color; +} +#endif + +vec3 getSunlitColor() +{ + return vary_SunlitColor; +} +vec3 getAmblitColor() +{ + return vary_AmblitColor; +} +vec3 getAdditiveColor() +{ + return vary_AdditiveColor; +} +vec3 getAtmosAttenuation() +{ + return vary_AtmosAttenuation; +} + +void setPositionEye(vec3 v) +{ + vary_PositionEye = v; +} + +void setSunlitColor(vec3 v) +{ + vary_SunlitColor = v; +} + +void setAmblitColor(vec3 v) +{ + vary_AmblitColor = v; +} + +void setAdditiveColor(vec3 v) +{ + vary_AdditiveColor = v; +} + +void setAtmosAttenuation(vec3 v) +{ + vary_AtmosAttenuation = v; +} + +void calcAtmospherics(vec3 inPositionEye, float ambFactor) { + + vec3 P = inPositionEye; + setPositionEye(P); + + vec3 tmpLightnorm = lightnorm.xyz; + + vec3 Pn = normalize(P); + float Plen = length(P); + + vec4 temp1 = vec4(0); + vec3 temp2 = vec3(0); + vec4 blue_weight; + vec4 haze_weight; + vec4 sunlight = sunlight_color; + vec4 light_atten; + + //sunlight attenuation effect (hue and brightness) due to atmosphere + //this is used later for sunlight modulation at various altitudes + light_atten = (blue_density + vec4(haze_density * 0.25)) * (density_multiplier * max_y); + //I had thought blue_density and haze_density should have equal weighting, + //but attenuation due to haze_density tends to seem too strong + + temp1 = blue_density + vec4(haze_density); + blue_weight = blue_density / temp1; + haze_weight = vec4(haze_density) / temp1; + + //(TERRAIN) compute sunlight from lightnorm only (for short rays like terrain) + temp2.y = max(0.0, tmpLightnorm.y); + temp2.y = 1. / temp2.y; + sunlight *= exp( - light_atten * temp2.y); + + // main atmospheric scattering line integral + temp2.z = Plen * density_multiplier; + + // Transparency (-> temp1) + // ATI Bugfix -- can't store temp1*temp2.z*distance_multiplier in a variable because the ati + // compiler gets confused. + temp1 = exp(-temp1 * temp2.z * distance_multiplier); + + //final atmosphere attenuation factor + setAtmosAttenuation(temp1.rgb); + + //compute haze glow + //(can use temp2.x as temp because we haven't used it yet) + temp2.x = dot(Pn, tmpLightnorm.xyz); + temp2.x = 1. - temp2.x; + //temp2.x is 0 at the sun and increases away from sun + temp2.x = max(temp2.x, .03); //was glow.y + //set a minimum "angle" (smaller glow.y allows tighter, brighter hotspot) + temp2.x *= glow.x; + //higher glow.x gives dimmer glow (because next step is 1 / "angle") + temp2.x = pow(temp2.x, glow.z); + //glow.z should be negative, so we're doing a sort of (1 / "angle") function + + //add "minimum anti-solar illumination" + temp2.x += .25; + + //increase ambient when there are more clouds + vec4 tmpAmbient = ambient + (vec4(1.) - ambient) * cloud_shadow * 0.5; + + /* decrease value and saturation (that in HSV, not HSL) for occluded areas + * // for HSV color/geometry used here, see http://gimp-savvy.com/BOOK/index.html?node52.html + * // The following line of code performs the equivalent of: + * float ambAlpha = tmpAmbient.a; + * float ambValue = dot(vec3(tmpAmbient), vec3(0.577)); // projection onto <1/rt(3), 1/rt(3), 1/rt(3)>, the neutral white-black axis + * vec3 ambHueSat = vec3(tmpAmbient) - vec3(ambValue); + * tmpAmbient = vec4(RenderSSAOEffect.valueFactor * vec3(ambValue) + RenderSSAOEffect.saturationFactor *(1.0 - ambFactor) * ambHueSat, ambAlpha); + */ + tmpAmbient = vec4(mix(ssao_effect_mat * tmpAmbient.rgb, tmpAmbient.rgb, ambFactor), tmpAmbient.a); + + //haze color + setAdditiveColor( + vec3(blue_horizon * blue_weight * (sunlight*(1.-cloud_shadow) + tmpAmbient) + + (haze_horizon * haze_weight) * (sunlight*(1.-cloud_shadow) * temp2.x + + tmpAmbient))); + + //brightness of surface both sunlight and ambient + setSunlitColor(vec3(sunlight * .5)); + setAmblitColor(vec3(tmpAmbient * .25)); + setAdditiveColor(getAdditiveColor() * vec3(1.0 - temp1)); +} + +vec3 atmosLighting(vec3 light) +{ + light *= getAtmosAttenuation().r; + light += getAdditiveColor(); + return (2.0 * light); +} + +vec3 atmosTransport(vec3 light) { + light *= getAtmosAttenuation().r; + light += getAdditiveColor() * 2.0; + return light; +} +vec3 atmosGetDiffuseSunlightColor() +{ + return getSunlitColor(); +} + +vec3 scaleDownLight(vec3 light) +{ + return (light / vec3(scene_light_strength, scene_light_strength, scene_light_strength)); +} + +vec3 scaleUpLight(vec3 light) +{ + return (light * vec3(scene_light_strength, scene_light_strength, scene_light_strength)); +} + +vec3 atmosAmbient(vec3 light) +{ + return getAmblitColor() + (light * vec3(0.5f, 0.5f, 0.5f)); +} + +vec3 atmosAffectDirectionalLight(float lightIntensity) +{ + return getSunlitColor() * vec3(lightIntensity, lightIntensity, lightIntensity); +} + +vec3 scaleSoftClip(vec3 light) +{ + //soft clip effect: + vec3 zeroes = vec3(0.0f, 0.0f, 0.0f); + vec3 ones = vec3(1.0f, 1.0f, 1.0f); + + light = ones - clamp(light, zeroes, ones); + light = ones - pow(light, gamma.xxx); + + return light; +} + +vec3 fullbrightAtmosTransport(vec3 light) { + float brightness = dot(light.rgb, vec3(0.33333)); + + return mix(atmosTransport(light.rgb), light.rgb + getAdditiveColor().rgb, brightness * brightness); +} + +vec3 fullbrightScaleSoftClip(vec3 light) +{ + //soft clip effect: + return light; +} void main() { @@ -58,16 +456,172 @@ void main() vec4 pos = vec4(vary_position, 1.0); - vec4 diff= diffuseLookup(vary_texcoord0.xy); + float shadow = 1.0; - vec4 col = vec4(vary_ambient + vary_directional.rgb, vertex_color.a); - vec4 color = diff * col; +#if HAS_SHADOW + vec4 spos = pos; + + if (spos.z > -shadow_clip.w) + { + shadow = 0.0; + + vec4 lpos; + + vec4 near_split = shadow_clip*-0.75; + vec4 far_split = shadow_clip*-1.25; + vec4 transition_domain = near_split-far_split; + float weight = 0.0; + + if (spos.z < near_split.z) + { + lpos = shadow_matrix[3]*spos; + + float w = 1.0; + w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; + shadow += pcfShadow(shadowMap3, lpos)*w; + weight += w; + shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0); + } + + if (spos.z < near_split.y && spos.z > far_split.z) + { + lpos = shadow_matrix[2]*spos; + + float w = 1.0; + w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; + w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; + shadow += pcfShadow(shadowMap2, lpos)*w; + weight += w; + } + + if (spos.z < near_split.x && spos.z > far_split.y) + { + lpos = shadow_matrix[1]*spos; + + float w = 1.0; + w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; + w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; + shadow += pcfShadow(shadowMap1, lpos)*w; + weight += w; + } + + if (spos.z > far_split.x) + { + lpos = shadow_matrix[0]*spos; + + float w = 1.0; + w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; + + shadow += pcfShadow(shadowMap0, lpos)*w; + weight += w; + } + + + shadow /= weight; + } + else + { + shadow = 1.0; + } +#endif + +#ifdef USE_INDEXED_TEX + vec4 diff = diffuseLookup(vary_texcoord0.xy); +#else + vec4 diff = texture2D(diffuseMap,vary_texcoord0.xy); +#endif + +#ifdef FOR_IMPOSTOR + vec4 color; + color.rgb = diff.rgb; + +#ifdef USE_VERTEX_COLOR + float final_alpha = diff.a * vertex_color.a; + diff.rgb *= vertex_color.rgb; +#else + float final_alpha = diff.a; +#endif + + // Insure we don't pollute depth with invis pixels in impostor rendering + // + if (final_alpha < 0.01) + { + discard; + } +#else + +#ifdef USE_VERTEX_COLOR + float final_alpha = diff.a * vertex_color.a; + diff.rgb *= vertex_color.rgb; +#else + float final_alpha = diff.a; +#endif + + + vec4 gamma_diff = diff; + diff.rgb = srgb_to_linear(diff.rgb); + + vec3 norm = vary_norm; + + calcAtmospherics(pos.xyz, 1.0); + + vec2 abnormal = encode_normal(norm.xyz); + norm.xyz = decode_normal(abnormal.xy); + + float da = dot(norm.xyz, sun_dir.xyz); + + float final_da = da; + final_da = min(final_da, shadow); + final_da = max(final_da, 0.0f); + final_da = min(final_da, 1.0f); + final_da = pow(final_da, 1.0/1.3); + + vec4 color = vec4(0,0,0,0); + + color.rgb = atmosAmbient(color.rgb); + color.a = final_alpha; + + float ambient = abs(da); + ambient *= 0.5; + ambient *= ambient; + ambient = (1.0-ambient); + + color.rgb *= ambient; + color.rgb += atmosAffectDirectionalLight(final_da); + color.rgb *= gamma_diff.rgb; + + //color.rgb = mix(diff.rgb, color.rgb, final_alpha); color.rgb = atmosLighting(color.rgb); - color.rgb = scaleSoftClip(color.rgb); - color.rgb += diff.rgb * vary_pointlight_col.rgb; + vec4 light = vec4(0,0,0,0); + + color.rgb = srgb_to_linear(color.rgb); + + #define LIGHT_LOOP(i) light.rgb += calcPointLightOrSpotLight(light_diffuse[i].rgb, diff.rgb, pos.xyz, norm, light_position[i], light_direction[i].xyz, light_attenuation[i].x, light_attenuation[i].y, light_attenuation[i].z); + + LIGHT_LOOP(1) + LIGHT_LOOP(2) + LIGHT_LOOP(3) + LIGHT_LOOP(4) + LIGHT_LOOP(5) + LIGHT_LOOP(6) + LIGHT_LOOP(7) + + // keep it linear + // + color.rgb += light.rgb; + + // straight to display gamma, we're post-deferred + // + color.rgb = linear_to_srgb(color.rgb); + +#ifdef WATER_FOG + color = applyWaterFogDeferred(pos.xyz, color); +#endif + +#endif frag_color = color; } diff --git a/indra/newview/app_settings/shaders/class1/deferred/alphaNonIndexedNoColorF.glsl b/indra/newview/app_settings/shaders/class1/deferred/alphaNonIndexedNoColorF.glsl deleted file mode 100644 index 1113a9845..000000000 --- a/indra/newview/app_settings/shaders/class1/deferred/alphaNonIndexedNoColorF.glsl +++ /dev/null @@ -1,92 +0,0 @@ -/** - * @file alphaNonIndexedNoColorF.glsl - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2005, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#extension GL_ARB_texture_rectangle : enable - -#ifdef DEFINE_GL_FRAGCOLOR -out vec4 frag_color; -#else -#define frag_color gl_FragColor -#endif - -uniform float minimum_alpha; - -uniform sampler2DRect depthMap; -uniform sampler2D diffuseMap; - -uniform vec2 screen_res; - -vec3 atmosLighting(vec3 light); -vec3 scaleSoftClip(vec3 light); - -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; -VARYING vec3 vary_fragcoord; -VARYING vec3 vary_position; -VARYING vec3 vary_pointlight_col; -VARYING vec2 vary_texcoord0; - -uniform mat4 inv_proj; - -vec4 getPosition(vec2 pos_screen) -{ - float depth = texture2DRect(depthMap, pos_screen.xy).a; - vec2 sc = pos_screen.xy*2.0; - sc /= screen_res; - sc -= vec2(1.0,1.0); - vec4 ndc = vec4(sc.x, sc.y, 2.0*depth-1.0, 1.0); - vec4 pos = inv_proj * ndc; - pos /= pos.w; - pos.w = 1.0; - return pos; -} - -void main() -{ - vec2 frag = vary_fragcoord.xy/vary_fragcoord.z*0.5+0.5; - frag *= screen_res; - - vec4 pos = vec4(vary_position, 1.0); - - vec4 diff= texture2D(diffuseMap,vary_texcoord0.xy); - - if (diff.a < minimum_alpha) - { - discard; - } - - vec4 col = vec4(vary_ambient + vary_directional.rgb, 1.0); - vec4 color = diff * col; - - - color.rgb = atmosLighting(color.rgb); - - color.rgb = scaleSoftClip(color.rgb); - - color.rgb += diff.rgb * vary_pointlight_col.rgb; - - frag_color = color; -} - diff --git a/indra/newview/app_settings/shaders/class1/deferred/alphaSkinnedV.glsl b/indra/newview/app_settings/shaders/class1/deferred/alphaSkinnedV.glsl deleted file mode 100644 index 5a0e8ff68..000000000 --- a/indra/newview/app_settings/shaders/class1/deferred/alphaSkinnedV.glsl +++ /dev/null @@ -1,144 +0,0 @@ -/** - * @file alphaSkinnedV.glsl - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -uniform mat4 projection_matrix; -uniform mat4 modelview_matrix; - -ATTRIBUTE vec3 position; -ATTRIBUTE vec3 normal; -ATTRIBUTE vec4 diffuse_color; -ATTRIBUTE vec2 texcoord0; - -mat4 getObjectSkinnedTransform(); -void calcAtmospherics(vec3 inPositionEye); - -float calcDirectionalLight(vec3 n, vec3 l); - -vec3 atmosAmbient(vec3 light); -vec3 atmosAffectDirectionalLight(float lightIntensity); - -VARYING vec3 vary_position; -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; -VARYING vec3 vary_fragcoord; -VARYING vec3 vary_pointlight_col; -VARYING vec4 vertex_color; -VARYING vec2 vary_texcoord0; - - -uniform float near_clip; - -uniform vec4 light_position[8]; -uniform vec3 light_direction[8]; -uniform vec3 light_attenuation[8]; -uniform vec3 light_diffuse[8]; - -float calcDirectionalLight(vec3 n, vec3 l) -{ - float a = max(dot(n,l),0.0); - return a; -} - -float calcPointLightOrSpotLight(vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float fa, float is_pointlight) -{ - //get light vector - vec3 lv = lp.xyz-v; - - //get distance - float d = dot(lv,lv); - - float da = 0.0; - - if (d > 0.0 && la > 0.0 && fa > 0.0) - { - //normalize light vector - lv = normalize(lv); - - //distance attenuation - float dist2 = d/la; - da = clamp(1.0-(dist2-1.0*(1.0-fa))/fa, 0.0, 1.0); - - // spotlight coefficient. - float spot = max(dot(-ln, lv), is_pointlight); - da *= spot*spot; // GL_SPOT_EXPONENT=2 - - //angular attenuation - da *= max(dot(n, lv), 0.0); - } - - return da; -} - -void main() -{ - vary_texcoord0 = texcoord0; - - vec4 pos; - vec3 norm; - - mat4 trans = getObjectSkinnedTransform(); - trans = modelview_matrix * trans; - - pos = trans * vec4(position.xyz, 1.0); - - norm = position.xyz + normal.xyz; - norm = normalize(( trans*vec4(norm, 1.0) ).xyz-pos.xyz); - - vec4 frag_pos = projection_matrix * pos; - gl_Position = frag_pos; - - vary_position = pos.xyz; - - calcAtmospherics(pos.xyz); - - vec4 col = vec4(0.0, 0.0, 0.0, diffuse_color.a); - - // Collect normal lights - col.rgb += light_diffuse[2].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[2], light_direction[2], light_attenuation[2].x, light_attenuation[2].y, light_attenuation[2].z); - col.rgb += light_diffuse[3].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[3], light_direction[3], light_attenuation[3].x, light_attenuation[3].y, light_attenuation[3].z); - col.rgb += light_diffuse[4].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[4], light_direction[4], light_attenuation[4].x, light_attenuation[4].y, light_attenuation[4].z); - col.rgb += light_diffuse[5].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[5], light_direction[5], light_attenuation[5].x, light_attenuation[5].y, light_attenuation[5].z); - col.rgb += light_diffuse[6].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[6], light_direction[6], light_attenuation[6].x, light_attenuation[6].y, light_attenuation[6].z); - col.rgb += light_diffuse[7].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[7], light_direction[7], light_attenuation[7].x, light_attenuation[7].y, light_attenuation[7].z); - - vary_pointlight_col = col.rgb*diffuse_color.rgb; - - col.rgb = vec3(0,0,0); - - // Add windlight lights - col.rgb = atmosAmbient(vec3(0.)); - - vary_ambient = col.rgb*diffuse_color.rgb; - vary_directional = diffuse_color.rgb*atmosAffectDirectionalLight(max(calcDirectionalLight(norm, light_position[0].xyz), (1.0-diffuse_color.a)*(1.0-diffuse_color.a))); - - col.rgb = min(col.rgb*diffuse_color.rgb, 1.0); - - vertex_color = col; - - - - vary_fragcoord.xyz = frag_pos.xyz + vec3(0,0,near_clip); -} - - diff --git a/indra/newview/app_settings/shaders/class1/deferred/alphaV.glsl b/indra/newview/app_settings/shaders/class1/deferred/alphaV.glsl index cf38a2f4f..b40785bbd 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/alphaV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/alphaV.glsl @@ -23,126 +23,118 @@ * $/LicenseInfo$ */ +#define INDEXED 1 +#define NON_INDEXED 2 +#define NON_INDEXED_NO_COLOR 3 + uniform mat3 normal_matrix; uniform mat4 texture_matrix0; +uniform mat4 projection_matrix; uniform mat4 modelview_matrix; uniform mat4 modelview_projection_matrix; ATTRIBUTE vec3 position; + +#ifdef USE_INDEXED_TEX void passTextureIndex(); +#endif + ATTRIBUTE vec3 normal; + +#ifdef USE_VERTEX_COLOR ATTRIBUTE vec4 diffuse_color; +#endif + ATTRIBUTE vec2 texcoord0; -vec4 calcLighting(vec3 pos, vec3 norm, vec4 color, vec4 baseCol); -void calcAtmospherics(vec3 inPositionEye); +#ifdef HAS_SKIN +mat4 getObjectSkinnedTransform(); +#else +#ifdef IS_AVATAR_SKIN +mat4 getSkinnedTransform(); +#endif +#endif -float calcDirectionalLight(vec3 n, vec3 l); - -vec3 atmosAmbient(vec3 light); -vec3 atmosAffectDirectionalLight(float lightIntensity); -vec3 scaleDownLight(vec3 light); -vec3 scaleUpLight(vec3 light); - -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; VARYING vec3 vary_fragcoord; VARYING vec3 vary_position; -VARYING vec3 vary_pointlight_col; +#ifdef USE_VERTEX_COLOR VARYING vec4 vertex_color; -VARYING vec2 vary_texcoord0; +#endif +VARYING vec2 vary_texcoord0; +VARYING vec3 vary_norm; uniform float near_clip; -uniform float shadow_offset; -uniform float shadow_bias; - -uniform vec4 light_position[8]; -uniform vec3 light_direction[8]; -uniform vec3 light_attenuation[8]; -uniform vec3 light_diffuse[8]; - -float calcDirectionalLight(vec3 n, vec3 l) -{ - float a = max(dot(n,l),0.0); - return a; -} - -float calcPointLightOrSpotLight(vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float fa, float is_pointlight) -{ - //get light vector - vec3 lv = lp.xyz-v; - - //get distance - float d = dot(lv,lv); - - float da = 0.0; - - if (d > 0.0 && la > 0.0 && fa > 0.0) - { - //normalize light vector - lv = normalize(lv); - - //distance attenuation - float dist2 = d/la; - da = clamp(1.0-(dist2-1.0*(1.0-fa))/fa, 0.0, 1.0); - - // spotlight coefficient. - float spot = max(dot(-ln, lv), is_pointlight); - da *= spot*spot; // GL_SPOT_EXPONENT=2 - - //angular attenuation - da *= max(dot(n, lv), 0.0); - } - - return da; -} void main() { + vec4 pos; + vec3 norm; + //transform vertex +#ifdef HAS_SKIN + mat4 trans = getObjectSkinnedTransform(); + trans = modelview_matrix * trans; + + pos = trans * vec4(position.xyz, 1.0); + + norm = position.xyz + normal.xyz; + norm = normalize((trans * vec4(norm, 1.0)).xyz - pos.xyz); + vec4 frag_pos = projection_matrix * pos; + gl_Position = frag_pos; +#else + +#ifdef IS_AVATAR_SKIN + mat4 trans = getSkinnedTransform(); + vec4 pos_in = vec4(position.xyz, 1.0); + pos.x = dot(trans[0], pos_in); + pos.y = dot(trans[1], pos_in); + pos.z = dot(trans[2], pos_in); + pos.w = 1.0; + + norm.x = dot(trans[0].xyz, normal); + norm.y = dot(trans[1].xyz, normal); + norm.z = dot(trans[2].xyz, normal); + norm = normalize(norm); + + vec4 frag_pos = projection_matrix * pos; + gl_Position = frag_pos; +#else + norm = normalize(normal_matrix * normal); vec4 vert = vec4(position.xyz, 1.0); - passTextureIndex(); - vec4 pos = (modelview_matrix * vert); + pos = (modelview_matrix * vert); gl_Position = modelview_projection_matrix*vec4(position.xyz, 1.0); +#endif +#endif + +#ifdef USE_INDEXED_TEX + passTextureIndex(); vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy; +#else + vary_texcoord0 = texcoord0; +#endif - vec3 norm = normalize(normal_matrix * normal); - - float dp_directional_light = max(0.0, dot(norm, light_position[0].xyz)); - vary_position = pos.xyz + light_position[0].xyz * (1.0-dp_directional_light)*shadow_offset; - - calcAtmospherics(pos.xyz); + vary_norm = norm; + vary_position = pos.xyz; - //vec4 color = calcLighting(pos.xyz, norm, diffuse_color, vec4(0.)); - vec4 col = vec4(0.0, 0.0, 0.0, diffuse_color.a); +#ifdef USE_VERTEX_COLOR + vertex_color = diffuse_color; +#endif + +#ifdef HAS_SKIN + vary_fragcoord.xyz = frag_pos.xyz + vec3(0,0,near_clip); +#else - // Collect normal lights - col.rgb += light_diffuse[2].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[2], light_direction[2], light_attenuation[2].x, light_attenuation[2].y, light_attenuation[2].z); - col.rgb += light_diffuse[3].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[3], light_direction[3], light_attenuation[3].x, light_attenuation[3].y, light_attenuation[3].z); - col.rgb += light_diffuse[4].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[4], light_direction[4], light_attenuation[4].x, light_attenuation[4].y, light_attenuation[4].z); - col.rgb += light_diffuse[5].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[5], light_direction[5], light_attenuation[5].x, light_attenuation[5].y, light_attenuation[5].z); - col.rgb += light_diffuse[6].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[6], light_direction[6], light_attenuation[6].x, light_attenuation[6].y, light_attenuation[6].z); - col.rgb += light_diffuse[7].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[7], light_direction[7], light_attenuation[7].x, light_attenuation[7].y, light_attenuation[7].z); - - vary_pointlight_col = col.rgb*diffuse_color.rgb; - col.rgb = vec3(0,0,0); - - // Add windlight lights - col.rgb = atmosAmbient(vec3(0.)); - - vary_ambient = col.rgb*diffuse_color.rgb; - vary_directional.rgb = diffuse_color.rgb*atmosAffectDirectionalLight(max(calcDirectionalLight(norm, light_position[0].xyz), (1.0-diffuse_color.a)*(1.0-diffuse_color.a))); - - col.rgb = col.rgb*diffuse_color.rgb; - - vertex_color = col; - - - +#ifdef IS_AVATAR_SKIN + vary_fragcoord.xyz = pos.xyz + vec3(0,0,near_clip); +#else pos = modelview_projection_matrix * vert; vary_fragcoord.xyz = pos.xyz + vec3(0,0,near_clip); +#endif +#endif + } + diff --git a/indra/newview/app_settings/shaders/class1/deferred/attachmentShadowV.glsl b/indra/newview/app_settings/shaders/class1/deferred/attachmentShadowV.glsl index 81961d774..3f90600ac 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/attachmentShadowV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/attachmentShadowV.glsl @@ -39,7 +39,12 @@ void main() mat = modelview_matrix * mat; vec3 pos = (mat*vec4(position.xyz, 1.0)).xyz; + vec4 p = projection_matrix * vec4(pos, 1.0); +#if !DEPTH_CLAMP p.z = max(p.z, -p.w+0.01); gl_Position = p; +#else + gl_Position = p; +#endif } diff --git a/indra/newview/app_settings/shaders/class1/deferred/avatarAlphaNoColorV.glsl b/indra/newview/app_settings/shaders/class1/deferred/avatarAlphaNoColorV.glsl index 5f395801e..c8ddefac2 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/avatarAlphaNoColorV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/avatarAlphaNoColorV.glsl @@ -47,6 +47,7 @@ VARYING vec3 vary_directional; VARYING vec3 vary_fragcoord; VARYING vec3 vary_pointlight_col; VARYING vec2 vary_texcoord0; +VARYING vec3 vary_norm; uniform float near_clip; @@ -112,6 +113,7 @@ void main() norm.y = dot(trans[1].xyz, normal); norm.z = dot(trans[2].xyz, normal); norm = normalize(norm); + vary_norm = norm; vec4 frag_pos = projection_matrix * pos; gl_Position = frag_pos; diff --git a/indra/newview/app_settings/shaders/class1/deferred/avatarAlphaV.glsl b/indra/newview/app_settings/shaders/class1/deferred/avatarAlphaV.glsl deleted file mode 100644 index d6149fcc3..000000000 --- a/indra/newview/app_settings/shaders/class1/deferred/avatarAlphaV.glsl +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @file avatarAlphaV.glsl - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -uniform mat4 projection_matrix; - -ATTRIBUTE vec3 position; -ATTRIBUTE vec3 normal; -ATTRIBUTE vec2 texcoord0; - -vec4 calcLighting(vec3 pos, vec3 norm, vec4 color, vec4 baseCol); -mat4 getSkinnedTransform(); -void calcAtmospherics(vec3 inPositionEye); - -float calcDirectionalLight(vec3 n, vec3 l); -float calcPointLightOrSpotLight(vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float is_pointlight); - -vec3 atmosAmbient(vec3 light); -vec3 atmosAffectDirectionalLight(float lightIntensity); -vec3 scaleDownLight(vec3 light); -vec3 scaleUpLight(vec3 light); - -VARYING vec3 vary_position; -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; -VARYING vec3 vary_fragcoord; -VARYING vec3 vary_pointlight_col; -VARYING vec4 vertex_color; -VARYING vec2 vary_texcoord0; - - -uniform float near_clip; - -uniform vec4 color; - -uniform vec4 light_position[8]; -uniform vec3 light_direction[8]; -uniform vec3 light_attenuation[8]; -uniform vec3 light_diffuse[8]; - -float calcDirectionalLight(vec3 n, vec3 l) -{ - float a = max(dot(n,l),0.0); - return a; -} - -float calcPointLightOrSpotLight(vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float fa, float is_pointlight) -{ - //get light vector - vec3 lv = lp.xyz-v; - - //get distance - float d = dot(lv,lv); - - float da = 0.0; - - if (d > 0.0 && la > 0.0 && fa > 0.0) - { - //normalize light vector - lv = normalize(lv); - - //distance attenuation - float dist2 = d/la; - da = clamp(1.0-(dist2-1.0*(1.0-fa))/fa, 0.0, 1.0); - - // spotlight coefficient. - float spot = max(dot(-ln, lv), is_pointlight); - da *= spot*spot; // GL_SPOT_EXPONENT=2 - - //angular attenuation - da *= max(dot(n, lv), 0.0); - } - - return da; -} - -void main() -{ - vary_texcoord0 = texcoord0; - - vec4 pos; - vec3 norm; - - mat4 trans = getSkinnedTransform(); - vec4 pos_in = vec4(position.xyz, 1.0); - pos.x = dot(trans[0], pos_in); - pos.y = dot(trans[1], pos_in); - pos.z = dot(trans[2], pos_in); - pos.w = 1.0; - - norm.x = dot(trans[0].xyz, normal); - norm.y = dot(trans[1].xyz, normal); - norm.z = dot(trans[2].xyz, normal); - norm = normalize(norm); - - vec4 frag_pos = projection_matrix * pos; - gl_Position = frag_pos; - - vary_position = pos.xyz; - - calcAtmospherics(pos.xyz); - - vec4 col = vec4(0.0, 0.0, 0.0, 1.0); - - // Collect normal lights - col.rgb += light_diffuse[2].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[2], light_direction[2], light_attenuation[2].x, light_attenuation[2].y, light_attenuation[2].z); - col.rgb += light_diffuse[3].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[3], light_direction[3], light_attenuation[3].x, light_attenuation[3].y, light_attenuation[3].z); - col.rgb += light_diffuse[4].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[4], light_direction[4], light_attenuation[4].x, light_attenuation[4].y, light_attenuation[4].z); - col.rgb += light_diffuse[5].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[5], light_direction[5], light_attenuation[5].x, light_attenuation[5].y, light_attenuation[5].z); - col.rgb += light_diffuse[6].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[6], light_direction[6], light_attenuation[6].x, light_attenuation[6].y, light_attenuation[6].z); - col.rgb += light_diffuse[7].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[7], light_direction[7], light_attenuation[7].x, light_attenuation[7].y, light_attenuation[7].z); - - vary_pointlight_col = col.rgb*color.rgb; - - col.rgb = vec3(0,0,0); - - // Add windlight lights - col.rgb = atmosAmbient(vec3(0.)); - - vary_ambient = col.rgb*color.rgb; - vary_directional = color.rgb*atmosAffectDirectionalLight(max(calcDirectionalLight(norm, light_position[0].xyz), 0.0)); - - col.rgb = col.rgb * color.rgb; - - vertex_color = col; - - - - vary_fragcoord.xyz = frag_pos.xyz + vec3(0,0,near_clip); -} - - diff --git a/indra/newview/app_settings/shaders/class1/deferred/avatarF.glsl b/indra/newview/app_settings/shaders/class1/deferred/avatarF.glsl index cf57a33d9..662c762bc 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/avatarF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/avatarF.glsl @@ -31,25 +31,22 @@ out vec4 frag_data[3]; uniform sampler2D diffuseMap; +uniform float minimum_alpha; + VARYING vec3 vary_normal; VARYING vec2 vary_texcoord0; -vec3 pack(vec3 norm) +vec2 encode_normal(vec3 n) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - float p = sqrt(8.0*norm.z+8.0); - return vec3(norm.xy/p + 0.5, 0.0); -#else - return norm.xyz*0.5+0.5; -#endif + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; } void main() { vec4 diff = texture2D(diffuseMap, vary_texcoord0.xy); - if (diff.a < 0.2) + if (diff.a < minimum_alpha) { discard; } @@ -57,6 +54,6 @@ void main() frag_data[0] = vec4(diff.rgb, 0.0); frag_data[1] = vec4(0,0,0,0); vec3 nvn = normalize(vary_normal); - frag_data[2] = vec4(pack(nvn), 0.0); + frag_data[2] = vec4(encode_normal(nvn.xyz), 0.0, 0.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/blurLightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/blurLightF.glsl index 9a9b889da..3e836e794 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/blurLightF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/blurLightF.glsl @@ -64,24 +64,28 @@ vec4 getPosition(vec2 pos_screen) return pos; } -vec3 unpack(vec2 tc) +vec2 encode_normal(vec3 n) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - vec2 enc = texture2DRect(normalMap, tc).xy; - enc = enc*4.0-2.0; - float prod = dot(enc,enc); - return vec3(enc*sqrt(1.0-prod*.25),1.0-prod*.5); -#else - vec3 norm = texture2DRect(normalMap, tc).xyz; - return norm*2.0-1.0; -#endif + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; } void main() { vec2 tc = vary_fragcoord.xy; - vec3 norm = unpack(tc); // unpack norm + vec3 norm = texture2DRect(normalMap, tc).xyz; + norm = decode_normal(norm.xy); // unpack norm vec3 pos = getPosition(tc).xyz; vec4 ccol = texture2DRect(lightMap, tc).rgba; @@ -93,11 +97,12 @@ void main() vec4 col = defined_weight.xyxx * ccol; // relax tolerance according to distance to avoid speckling artifacts, as angles and distances are a lot more abrupt within a small screen area at larger distances - float pointplanedist_tolerance_pow2 = pos.z*-0.001; + float pointplanedist_tolerance_pow2 = pos.z*pos.z*0.00005; // perturb sampling origin slightly in screen-space to hide edge-ghosting artifacts where smoothing radius is quite large - vec2 tc_v = fract(0.5 * tc.xy); // we now have floor(mod(tc,2.0))*0.5 - float tc_mod = 2.0 * abs(tc_v.x - tc_v.y); // diff of x,y makes checkerboard + float tc_mod = 0.5*(tc.x + tc.y); // mod(tc.x+tc.y,2) + tc_mod -= floor(tc_mod); + tc_mod *= 2.0; tc += ( (tc_mod - 0.5) * getKern(1).z * dlt * 0.5 ); for (int i = 1; i < 4; i++) diff --git a/indra/newview/app_settings/shaders/class1/deferred/bumpF.glsl b/indra/newview/app_settings/shaders/class1/deferred/bumpF.glsl index db80a8e87..595c11fae 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/bumpF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/bumpF.glsl @@ -39,15 +39,10 @@ VARYING vec3 vary_mat2; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; -vec3 pack(vec3 norm) +vec2 encode_normal(vec3 n) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - float p = sqrt(8.0*norm.z+8.0); - return vec3(norm.xy/p + 0.5, 0.0); -#else - return norm.xyz*0.5+0.5; -#endif + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; } void main() @@ -63,5 +58,5 @@ void main() frag_data[1] = vertex_color.aaaa; // spec //frag_data[1] = vec4(vec3(vertex_color.a), vertex_color.a+(1.0-vertex_color.a)*vertex_color.a); // spec - from former class3 - maybe better, but not so well tested vec3 nvn = normalize(tnorm); - frag_data[2] = vec4(pack(nvn), 0.0); + frag_data[2] = vec4(encode_normal(nvn.xyz), vertex_color.a, 0.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/cloudsF.glsl b/indra/newview/app_settings/shaders/class1/deferred/cloudsF.glsl index 9f6b1ea1f..bb1093082 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/cloudsF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/cloudsF.glsl @@ -102,11 +102,6 @@ void main() /// Gamma correct for WL (soft clip effect). frag_data[0] = vec4(scaleSoftClip(color.rgb), alpha1); frag_data[1] = vec4(0.0,0.0,0.0,0.0); -//#define PACK_NORMALS -#ifdef PACK_NORMALS frag_data[2] = vec4(0.5,0.5,0.0,0.0); -#else - frag_data[2] = vec4(0.0,0.0,1.0,0.0); -#endif } diff --git a/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskF.glsl b/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskF.glsl index d31cbfa28..7930b5d18 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskF.glsl @@ -37,15 +37,10 @@ VARYING vec3 vary_normal; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; -vec3 pack(vec3 norm) +vec2 encode_normal(vec3 n) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - float p = sqrt(8.0*norm.z+8.0); - return vec3(norm.xy/p + 0.5, 0.0); -#else - return norm.xyz*0.5+0.5; -#endif + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; } void main() @@ -60,6 +55,6 @@ void main() frag_data[0] = vec4(col.rgb, 0.0); frag_data[1] = vec4(0,0,0,0); // spec vec3 nvn = normalize(vary_normal); - frag_data[2] = vec4(pack(nvn),0.0); + frag_data[2] = vec4(encode_normal(nvn.xyz), 0.0, 0.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskIndexedF.glsl b/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskIndexedF.glsl index c65539d43..8525e1333 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskIndexedF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskIndexedF.glsl @@ -36,15 +36,10 @@ uniform float minimum_alpha; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; -vec3 pack(vec3 norm) +vec2 encode_normal(vec3 n) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - float p = sqrt(8.0*norm.z+8.0); - return vec3(norm.xy/p + 0.5, 0.0); -#else - return norm.xyz*0.5+0.5; -#endif + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; } void main() @@ -55,9 +50,9 @@ void main() { discard; } - + frag_data[0] = vec4(col.rgb, 0.0); frag_data[1] = vec4(0,0,0,0); vec3 nvn = normalize(vary_normal); - frag_data[2] = vec4(pack(nvn), 0.0); + frag_data[2] = vec4(encode_normal(nvn.xyz), 0.0, 0.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskNoColorF.glsl b/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskNoColorF.glsl index 252975aad..37d70a241 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskNoColorF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/diffuseAlphaMaskNoColorF.glsl @@ -37,15 +37,10 @@ uniform sampler2D diffuseMap; VARYING vec3 vary_normal; VARYING vec2 vary_texcoord0; -vec3 pack(vec3 norm) +vec2 encode_normal(vec3 n) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - float p = sqrt(8.0*norm.z+8.0); - return vec3(norm.xy/p + 0.5, 0.0); -#else - return norm.xyz*0.5+0.5; -#endif + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; } void main() @@ -60,6 +55,6 @@ void main() frag_data[0] = vec4(col.rgb, 0.0); frag_data[1] = vec4(0,0,0,0); // spec vec3 nvn = normalize(vary_normal); - frag_data[2] = vec4(pack(nvn),0.0); + frag_data[2] = vec4(encode_normal(nvn.xyz), 0.0, 0.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/diffuseF.glsl b/indra/newview/app_settings/shaders/class1/deferred/diffuseF.glsl index ac33fd261..6befb1bd8 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/diffuseF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/diffuseF.glsl @@ -35,15 +35,10 @@ VARYING vec3 vary_normal; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; -vec3 pack(vec3 norm) +vec2 encode_normal(vec3 n) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - float p = sqrt(8.0*norm.z+8.0); - return vec3(norm.xy/p + 0.5, 0.0); -#else - return norm.xyz*0.5+0.5; -#endif + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; } void main() @@ -53,6 +48,6 @@ void main() frag_data[1] = vertex_color.aaaa; // spec //frag_data[1] = vec4(vec3(vertex_color.a), vertex_color.a+(1.0-vertex_color.a)*vertex_color.a); // spec - from former class3 - maybe better, but not so well tested vec3 nvn = normalize(vary_normal); - frag_data[2] = vec4(pack(nvn),0.0); + frag_data[2] = vec4(encode_normal(nvn.xyz), vertex_color.a, 0.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/diffuseIndexedF.glsl b/indra/newview/app_settings/shaders/class1/deferred/diffuseIndexedF.glsl index b97cd060d..adc361d7a 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/diffuseIndexedF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/diffuseIndexedF.glsl @@ -33,24 +33,22 @@ VARYING vec3 vary_normal; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; -vec3 pack(vec3 norm) +vec2 encode_normal(vec3 n) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - float p = sqrt(8.0*norm.z+8.0); - return vec3(norm.xy/p + 0.5, 0.0); -#else - return norm.xyz*0.5+0.5; -#endif + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; } + void main() { vec3 col = vertex_color.rgb * diffuseLookup(vary_texcoord0.xy).rgb; + + vec3 spec; + spec.rgb = vec3(vertex_color.a); frag_data[0] = vec4(col, 0.0); - frag_data[1] = vertex_color.aaaa; // spec - //frag_data[1] = vec4(vec3(vertex_color.a), vertex_color.a+(1.0-vertex_color.a)*vertex_color.a); // spec - from former class3 - maybe better, but not so well tested + frag_data[1] = vec4(spec, vertex_color.a); // spec vec3 nvn = normalize(vary_normal); - frag_data[2] = vec4(pack(nvn),0.0); + frag_data[2] = vec4(encode_normal(nvn.xyz), vertex_color.a, 0.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/emissiveF.glsl b/indra/newview/app_settings/shaders/class1/deferred/emissiveF.glsl index 6aa4d7b4e..ed02c4a48 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/emissiveF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/emissiveF.glsl @@ -42,7 +42,7 @@ void main() float shadow = 1.0; vec4 color = diffuseLookup(vary_texcoord0.xy)*vertex_color; - + color.rgb = pow(color.rgb, vec3(2.2)); color.rgb = fullbrightAtmosTransport(color.rgb); color.rgb = fullbrightScaleSoftClip(color.rgb); diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl index 764afa7a7..41cee1db0 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl @@ -31,22 +31,145 @@ out vec4 frag_color; #define frag_color gl_FragColor #endif +#if !HAS_DIFFUSE_LOOKUP +uniform sampler2D diffuseMap; +#endif + +VARYING vec3 vary_position; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; -vec3 fullbrightAtmosTransport(vec3 light); -vec3 fullbrightScaleSoftClip(vec3 light); +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lte); +#endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + +vec3 fullbrightAtmosTransportDeferred(vec3 light) +{ + return light; +} + +vec3 fullbrightScaleSoftClipDeferred(vec3 light) +{ + //soft clip effect: + return light; +} + +#ifdef HAS_ALPHA_MASK +uniform float minimum_alpha; +#endif + +#ifdef WATER_FOG +uniform vec4 waterPlane; +uniform vec4 waterFogColor; +uniform float waterFogDensity; +uniform float waterFogKS; + +vec4 applyWaterFogDeferred(vec3 pos, vec4 color) +{ + //normalize view vector + vec3 view = normalize(pos); + float es = -(dot(view, waterPlane.xyz)); + + //find intersection point with water plane and eye vector + + //get eye depth + float e0 = max(-waterPlane.w, 0.0); + + vec3 int_v = waterPlane.w > 0.0 ? view * waterPlane.w/es : vec3(0.0, 0.0, 0.0); + + //get object depth + float depth = length(pos - int_v); + + //get "thickness" of water + float l = max(depth, 0.1); + + float kd = waterFogDensity; + float ks = waterFogKS; + vec4 kc = waterFogColor; + + float F = 0.98; + + float t1 = -kd * pow(F, ks * e0); + float t2 = kd + ks * es; + float t3 = pow(F, t2*l) - 1.0; + + float L = min(t1/t2*t3, 1.0); + + float D = pow(0.98, l*kd); + + color.rgb = color.rgb * D + kc.rgb * L; + color.a = kc.a + color.a; + + return color; +} +#endif void main() { - float shadow = 1.0; +#if HAS_DIFFUSE_LOOKUP + vec4 color = diffuseLookup(vary_texcoord0.xy); +#else + vec4 color = texture2D(diffuseMap, vary_texcoord0.xy); +#endif - vec4 color = diffuseLookup(vary_texcoord0.xy)*vertex_color; + float final_alpha = color.a * vertex_color.a; + +#ifdef HAS_ALPHA_MASK + if (color.a < minimum_alpha) + { + discard; + } +#endif + + color.rgb *= vertex_color.rgb; + color.rgb = srgb_to_linear(color.rgb); + color.rgb = fullbrightAtmosTransportDeferred(color.rgb); + color.rgb = fullbrightScaleSoftClipDeferred(color.rgb); - color.rgb = fullbrightAtmosTransport(color.rgb); + color.rgb = linear_to_srgb(color.rgb); - color.rgb = fullbrightScaleSoftClip(color.rgb); +#ifdef WATER_FOG + vec3 pos = vary_position; + vec4 fogged = applyWaterFogDeferred(pos, vec4(color.rgb, final_alpha)); + color.rgb = fogged.rgb; + //color.a = fogged.a; +#else + //color.a = final_alpha; +#endif color.a = .0; frag_color = color; diff --git a/indra/newview/app_settings/shaders/class1/deferred/alphaNonIndexedF.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl similarity index 51% rename from indra/newview/app_settings/shaders/class1/deferred/alphaNonIndexedF.glsl rename to indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl index beb329018..edc6ff049 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/alphaNonIndexedF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl @@ -1,5 +1,5 @@ /** - * @file alphaF.glsl + * @file fullbrightShinyF.glsl * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code @@ -23,7 +23,7 @@ * $/LicenseInfo$ */ -#extension GL_ARB_texture_rectangle : enable + #ifdef DEFINE_GL_FRAGCOLOR out vec4 frag_color; @@ -31,59 +31,42 @@ out vec4 frag_color; #define frag_color gl_FragColor #endif -uniform sampler2DRect depthMap; +#ifndef diffuseLookup uniform sampler2D diffuseMap; +#endif - -uniform vec2 screen_res; - -vec3 atmosLighting(vec3 light); -vec3 scaleSoftClip(vec3 light); - -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; -VARYING vec3 vary_fragcoord; -VARYING vec3 vary_position; -VARYING vec3 vary_pointlight_col; -VARYING vec2 vary_texcoord0; VARYING vec4 vertex_color; +VARYING vec2 vary_texcoord0; +VARYING vec3 vary_texcoord1; -uniform mat4 inv_proj; +uniform samplerCube environmentMap; -vec4 getPosition(vec2 pos_screen) +vec3 fullbrightShinyAtmosTransport(vec3 light); +vec3 fullbrightScaleSoftClip(vec3 light); + +void main() { - float depth = texture2DRect(depthMap, pos_screen.xy).a; - vec2 sc = pos_screen.xy*2.0; - sc /= screen_res; - sc -= vec2(1.0,1.0); - vec4 ndc = vec4(sc.x, sc.y, 2.0*depth-1.0, 1.0); - vec4 pos = inv_proj * ndc; - pos /= pos.w; - pos.w = 1.0; - return pos; -} +#if HAS_DIFFUSE_LOOKUP + vec4 color = diffuseLookup(vary_texcoord0.xy); +#else + vec4 color = texture2D(diffuseMap, vary_texcoord0.xy); +#endif -void main() -{ - vec2 frag = vary_fragcoord.xy/vary_fragcoord.z*0.5+0.5; - frag *= screen_res; - vec4 pos = vec4(vary_position, 1.0); + color.rgb *= vertex_color.rgb; - vec4 diff= texture2D(diffuseMap,vary_texcoord0.xy); + vec3 envColor = textureCube(environmentMap, vary_texcoord1.xyz).rgb; + color.rgb = mix(color.rgb, envColor.rgb, vertex_color.a); - vec4 col = vec4(vary_ambient + vary_directional.rgb, vertex_color.a); - vec4 color = diff * col; + color.rgb = pow(color.rgb,vec3(2.2f,2.2f,2.2f)); - color.rgb = atmosLighting(color.rgb); + color.rgb = fullbrightShinyAtmosTransport(color.rgb); + color.rgb = fullbrightScaleSoftClip(color.rgb); - color.rgb = scaleSoftClip(color.rgb); + color.a = 0.0; - color.rgb += diff.rgb * vary_pointlight_col.rgb; + color.rgb = pow(color.rgb, vec3(1.0/2.2)); frag_color = color; - //frag_color = vec4(1,0,1,1); - //frag_color = vec4(1,0,1,1)*shadow; - } diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyV.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyV.glsl new file mode 100644 index 000000000..34bd8d445 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyV.glsl @@ -0,0 +1,67 @@ +/** + * @file fullbrightShinyV.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +uniform mat3 normal_matrix; +uniform mat4 texture_matrix0; +uniform mat4 texture_matrix1; +uniform mat4 modelview_matrix; +uniform mat4 modelview_projection_matrix; + + +void calcAtmospherics(vec3 inPositionEye); + +uniform vec4 origin; + + + +ATTRIBUTE vec3 position; +void passTextureIndex(); +ATTRIBUTE vec3 normal; +ATTRIBUTE vec4 diffuse_color; +ATTRIBUTE vec2 texcoord0; + +VARYING vec4 vertex_color; +VARYING vec2 vary_texcoord0; +VARYING vec3 vary_texcoord1; + + +void main() +{ + //transform vertex + vec4 vert = vec4(position.xyz,1.0); + passTextureIndex(); + vec4 pos = (modelview_matrix * vert); + gl_Position = modelview_projection_matrix*vec4(position.xyz, 1.0); + + vec3 norm = normalize(normal_matrix * normal); + vec3 ref = reflect(pos.xyz, -norm); + + vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy; + vary_texcoord1 = (texture_matrix1*vec4(ref,1.0)).xyz; + + calcAtmospherics(pos.xyz); + + vertex_color = diffuse_color; +} diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightV.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightV.glsl index 2e6982d10..0e623aa4f 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/fullbrightV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightV.glsl @@ -40,6 +40,9 @@ vec3 atmosAffectDirectionalLight(float lightIntensity); vec3 scaleDownLight(vec3 light); vec3 scaleUpLight(vec3 light); +#ifdef WATER_FOG +VARYING vec3 vary_position; +#endif VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; @@ -53,11 +56,15 @@ void main() passTextureIndex(); gl_Position = modelview_projection_matrix*vec4(position.xyz, 1.0); - + +#ifdef WATER_FOG + vary_position = pos.xyz; +#endif + vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy; calcAtmospherics(pos.xyz); - + vertex_color = diffuse_color; diff --git a/indra/newview/app_settings/shaders/class1/deferred/fxaaF.glsl b/indra/newview/app_settings/shaders/class1/deferred/fxaaF.glsl index 2cef8f2a5..a2b4b3b8c 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/fxaaF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/fxaaF.glsl @@ -24,6 +24,7 @@ */ #extension GL_ARB_texture_rectangle : enable +#extension GL_ARB_shader_texture_lod : enable #ifdef DEFINE_GL_FRAGCOLOR out vec4 frag_color; diff --git a/indra/newview/app_settings/shaders/class1/deferred/giF.glsl b/indra/newview/app_settings/shaders/class1/deferred/giF.glsl deleted file mode 100644 index da1b23424..000000000 --- a/indra/newview/app_settings/shaders/class1/deferred/giF.glsl +++ /dev/null @@ -1,190 +0,0 @@ -/** - * @file giF.glsl - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#extension GL_ARB_texture_rectangle : enable - -#ifdef DEFINE_GL_FRAGCOLOR -out vec4 frag_color; -#else -#define frag_color gl_FragColor -#endif - -uniform sampler2DRect depthMap; -uniform sampler2DRect normalMap; -uniform sampler2D noiseMap; - -uniform sampler2D diffuseGIMap; -uniform sampler2D normalGIMap; -uniform sampler2D depthGIMap; - -uniform sampler2D lightFunc; - -// Inputs -VARYING vec2 vary_fragcoord; - -uniform vec2 screen_res; - -uniform mat4 inv_proj; -uniform mat4 gi_mat; //gPipeline.mGIMatrix - eye space to sun space -uniform mat4 gi_mat_proj; //gPipeline.mGIMatrixProj - eye space to projected sun space -uniform mat4 gi_norm_mat; //gPipeline.mGINormalMatrix - eye space normal to sun space normal matrix -uniform mat4 gi_inv_proj; //gPipeline.mGIInvProj - projected sun space to sun space -uniform float gi_radius; -uniform float gi_intensity; -uniform int gi_samples; -uniform vec2 gi_kern[25]; -uniform vec2 gi_scale; -uniform vec3 gi_quad; -uniform vec3 gi_spec; -uniform float gi_direction_weight; -uniform float gi_light_offset; - -vec4 getPosition(vec2 pos_screen) -{ - float depth = texture2DRect(depthMap, pos_screen.xy).a; - vec2 sc = pos_screen.xy*2.0; - sc /= screen_res; - sc -= vec2(1.0,1.0); - vec4 ndc = vec4(sc.x, sc.y, 2.0*depth-1.0, 1.0); - vec4 pos = inv_proj * ndc; - pos /= pos.w; - pos.w = 1.0; - return pos; -} - -vec4 getGIPosition(vec2 gi_tc) -{ - float depth = texture2D(depthGIMap, gi_tc).a; - vec2 sc = gi_tc*2.0; - sc -= vec2(1.0, 1.0); - vec4 ndc = vec4(sc.x, sc.y, 2.0*depth-1.0, 1.0); - vec4 pos = gi_inv_proj*ndc; - pos.xyz /= pos.w; - pos.w = 1.0; - return pos; -} - -vec3 giAmbient(vec3 pos, vec3 norm) -{ - vec4 gi_c = gi_mat_proj * vec4(pos, 1.0); - gi_c.xyz /= gi_c.w; - - vec4 gi_pos = gi_mat*vec4(pos,1.0); - vec3 gi_norm = (gi_norm_mat*vec4(norm,1.0)).xyz; - gi_norm = normalize(gi_norm); - - vec2 tcx = gi_norm.xy; - vec2 tcy = gi_norm.yx; - - vec4 eye_pos = gi_mat*vec4(0,0,0,1.0); - - vec3 eye_dir = normalize(gi_pos.xyz-eye_pos.xyz/eye_pos.w); - - //vec3 eye_dir = vec3(0,0,-1); - //eye_dir = (gi_norm_mat*vec4(eye_dir, 1.0)).xyz; - //eye_dir = normalize(eye_dir); - - //float round_x = gi_scale.x; - //float round_y = gi_scale.y; - - vec3 debug = texture2D(normalGIMap, gi_c.xy).rgb*0.5+0.5; - debug.xz = vec2(0.0,0.0); - //debug = fract(debug); - - float round_x = 1.0/64.0; - float round_y = 1.0/64.0; - - //gi_c.x = floor(gi_c.x/round_x+0.5)*round_x; - //gi_c.y = floor(gi_c.y/round_y+0.5)*round_y; - - float fda = 0.0; - vec3 fdiff = vec3(0,0,0); - - vec3 rcol = vec3(0,0,0); - - float fsa = 0.0; - - for (int i = -1; i < 2; i+=2 ) - { - for (int j = -1; j < 2; j+=2) - { - vec2 tc = vec2(i, j)*0.75; - vec3 nz = texture2D(noiseMap, vary_fragcoord.xy/128.0+tc*0.5).xyz; - //tc += gi_norm.xy*nz.z; - tc += nz.xy*2.0; - tc /= gi_samples; - tc += gi_c.xy; - - vec3 lnorm = -normalize(texture2D(normalGIMap, tc.xy).xyz*2.0-1.0); - vec3 lpos = getGIPosition(tc.xy).xyz; - - vec3 at = lpos-gi_pos.xyz; - float dist = dot(at,at); - float da = clamp(1.0/(gi_spec.x*dist), 0.0, 1.0); - - if (da > 0.0) - { - //add angular attenuation - vec3 ldir = at; - float ang_atten = clamp(dot(ldir, gi_norm), 0.0, 1.0); - - float ld = -dot(ldir, lnorm); - - if (ang_atten > 0.0 && ld < 0.0) - { - vec3 diff = texture2D(diffuseGIMap, tc.xy).xyz; - da = da*ang_atten; - fda += da; - fdiff += diff*da; - } - } - } - } - - fdiff /= max(gi_spec.y*fda, gi_quad.z); - fdiff = clamp(fdiff, vec3(0), vec3(1)); - - vec3 ret = fda*fdiff; - //ret = ret*ret*gi_quad.x+ret*gi_quad.y+gi_quad.z; - - //fda *= nz.z; - - //rcol.rgb *= gi_intensity; - //return rcol.rgb+vary_AmblitColor.rgb*0.25; - //return vec4(debug, 0.0); - //return vec4(fda*fdiff, 0.0); - return clamp(ret,vec3(0.0), vec3(1.0)); - //return debug.xyz; -} - -void main() -{ - vec2 pos_screen = vary_fragcoord.xy; - vec4 pos = getPosition(pos_screen); - vec3 norm = texture2DRect(normalMap, pos_screen).xyz; - norm = vec3((norm.xy-0.5)*2.0,norm.z); // unpack norm - - frag_color.xyz = giAmbient(pos, norm); -} diff --git a/indra/newview/app_settings/shaders/class1/deferred/impostorF.glsl b/indra/newview/app_settings/shaders/class1/deferred/impostorF.glsl index bc0719cb8..f8fdde43f 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/impostorF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/impostorF.glsl @@ -38,6 +38,42 @@ uniform sampler2D specularMap; VARYING vec2 vary_texcoord0; +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + void main() { vec4 col = texture2D(diffuseMap, vary_texcoord0.xy); @@ -47,7 +83,12 @@ void main() discard; } - frag_data[0] = vec4(col.rgb, col.a * 0.005); - frag_data[1] = texture2D(specularMap, vary_texcoord0.xy); - frag_data[2] = vec4(texture2D(normalMap, vary_texcoord0.xy).xyz, 0.0); + vec4 norm = texture2D(normalMap, vary_texcoord0.xy); + vec4 spec = texture2D(specularMap, vary_texcoord0.xy); + + col.rgb = linear_to_srgb(col.rgb); + + frag_data[0] = vec4(col.rgb, 0.0); + frag_data[1] = spec; + frag_data[2] = vec4(norm.xy,0,0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/materialF.glsl b/indra/newview/app_settings/shaders/class1/deferred/materialF.glsl new file mode 100644 index 000000000..04f17e853 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/materialF.glsl @@ -0,0 +1,788 @@ +/** + * @file materialF.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#define DIFFUSE_ALPHA_MODE_IGNORE 0 +#define DIFFUSE_ALPHA_MODE_BLEND 1 +#define DIFFUSE_ALPHA_MODE_MASK 2 +#define DIFFUSE_ALPHA_MODE_EMISSIVE 3 + +uniform float emissive_brightness; +//uniform float display_gamma; + +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lte); +#endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + +#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND) + +#ifdef DEFINE_GL_FRAGCOLOR +out vec4 frag_color; +#else +#define frag_color gl_FragColor +#endif + +#if HAS_SUN_SHADOW + +uniform sampler2DShadow shadowMap0; +uniform sampler2DShadow shadowMap1; +uniform sampler2DShadow shadowMap2; +uniform sampler2DShadow shadowMap3; + +uniform mat4 shadow_matrix[6]; +uniform vec4 shadow_clip; +uniform vec2 shadow_res; +uniform float shadow_bias; + +float pcfShadow(sampler2DShadow shadowMap, vec4 stc) +{ + stc.xyz /= stc.w; + stc.z += shadow_bias; + + stc.x = floor(stc.x*shadow_res.x + fract(stc.y*shadow_res.y*12345))/shadow_res.x; // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here + + float cs = shadow2D(shadowMap, stc.xyz).x; + float shadow = cs; + + shadow += shadow2D(shadowMap, stc.xyz+vec3(2.0/shadow_res.x, 1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(1.0/shadow_res.x, -1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-1.0/shadow_res.x, 1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-2.0/shadow_res.x, -1.5/shadow_res.y, 0.0)).x; + + return shadow*0.2; +} + +#endif + +uniform samplerCube environmentMap; +uniform sampler2D lightFunc; + +// Inputs +uniform vec4 morphFactor; +uniform vec3 camPosLocal; +//uniform vec4 camPosWorld; +uniform vec4 gamma; +uniform vec4 lightnorm; +uniform vec4 sunlight_color; +uniform vec4 ambient; +uniform vec4 blue_horizon; +uniform vec4 blue_density; +uniform float haze_horizon; +uniform float haze_density; +uniform float cloud_shadow; +uniform float density_multiplier; +uniform float distance_multiplier; +uniform float max_y; +uniform vec4 glow; +uniform float scene_light_strength; +uniform mat3 env_mat; +uniform mat3 ssao_effect_mat; + +uniform vec3 sun_dir; +VARYING vec2 vary_fragcoord; + +VARYING vec3 vary_position; + +vec3 vary_PositionEye; + +vec3 vary_SunlitColor; +vec3 vary_AmblitColor; +vec3 vary_AdditiveColor; +vec3 vary_AtmosAttenuation; + +uniform mat4 inv_proj; +uniform vec2 screen_res; + +uniform vec4 light_position[8]; +uniform vec3 light_direction[8]; +uniform vec3 light_attenuation[8]; +uniform vec3 light_diffuse[8]; + +#ifdef WATER_FOG +uniform vec4 waterPlane; +uniform vec4 waterFogColor; +uniform float waterFogDensity; +uniform float waterFogKS; + +vec4 applyWaterFogDeferred(vec3 pos, vec4 color) +{ + //normalize view vector + vec3 view = normalize(pos); + float es = -(dot(view, waterPlane.xyz)); + + //find intersection point with water plane and eye vector + + //get eye depth + float e0 = max(-waterPlane.w, 0.0); + + vec3 int_v = waterPlane.w > 0.0 ? view * waterPlane.w/es : vec3(0.0, 0.0, 0.0); + + //get object depth + float depth = length(pos - int_v); + + //get "thickness" of water + float l = max(depth, 0.1); + + float kd = waterFogDensity; + float ks = waterFogKS; + vec4 kc = waterFogColor; + + float F = 0.98; + + float t1 = -kd * pow(F, ks * e0); + float t2 = kd + ks * es; + float t3 = pow(F, t2*l) - 1.0; + + float L = min(t1/t2*t3, 1.0); + + float D = pow(0.98, l*kd); + + color.rgb = color.rgb * D + kc.rgb * L; + color.a = kc.a + color.a; + + return color; +} +#endif + +vec3 calcDirectionalLight(vec3 n, vec3 l) +{ + float a = max(dot(n,l),0.0); + return vec3(a,a,a); +} + + +vec3 calcPointLightOrSpotLight(vec3 light_col, vec3 npos, vec3 diffuse, vec4 spec, vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float fa, float is_pointlight, inout float glare) +{ + //get light vector + vec3 lv = lp.xyz-v; + + //get distance + float d = length(lv); + + float da = 1.0; + + vec3 col = vec3(0,0,0); + + if (d > 0.0 && la > 0.0 && fa > 0.0) + { + //normalize light vector + lv = normalize(lv); + + //distance attenuation + float dist = d/la; + float dist_atten = clamp(1.0-(dist-1.0*(1.0-fa))/fa, 0.0, 1.0); + dist_atten *= dist_atten; + dist_atten *= 2.0; + + // spotlight coefficient. + float spot = max(dot(-ln, lv), is_pointlight); + da *= spot*spot; // GL_SPOT_EXPONENT=2 + + //angular attenuation + da *= max(dot(n, lv), 0.0); + + float lit = max(da * dist_atten, 0.0); + + col = light_col*lit*diffuse; + + if (spec.a > 0.0) + { + //vec3 ref = dot(pos+lv, norm); + vec3 h = normalize(lv+npos); + float nh = dot(n, h); + float nv = dot(n, npos); + float vh = dot(npos, h); + float sa = nh; + float fres = pow(1 - dot(h, npos), 5)*0.4+0.5; + + float gtdenom = 2 * nh; + float gt = max(0, min(gtdenom * nv / vh, gtdenom * da / vh)); + + if (nh > 0.0) + { + float scol = fres*texture2D(lightFunc, vec2(nh, spec.a)).r*gt/(nh*da); + vec3 speccol = lit*scol*light_col.rgb*spec.rgb; + col += speccol; + + float cur_glare = max(speccol.r, speccol.g); + cur_glare = max(cur_glare, speccol.b); + glare = max(glare, speccol.r); + glare += max(cur_glare, 0.0); + //col += spec.rgb; + } + } + } + + return max(col, vec3(0.0,0.0,0.0)); + +} + +vec4 getPosition_d(vec2 pos_screen, float depth) +{ + vec2 sc = pos_screen.xy*2.0; + sc /= screen_res; + sc -= vec2(1.0,1.0); + vec4 ndc = vec4(sc.x, sc.y, 2.0*depth-1.0, 1.0); + vec4 pos = inv_proj * ndc; + pos /= pos.w; + pos.w = 1.0; + return pos; +} + +#ifndef WATER_FOG +vec3 getPositionEye() +{ + return vary_PositionEye; +} +#endif + +vec3 getSunlitColor() +{ + return vary_SunlitColor; +} +vec3 getAmblitColor() +{ + return vary_AmblitColor; +} +vec3 getAdditiveColor() +{ + return vary_AdditiveColor; +} +vec3 getAtmosAttenuation() +{ + return vary_AtmosAttenuation; +} + +void setPositionEye(vec3 v) +{ + vary_PositionEye = v; +} + +void setSunlitColor(vec3 v) +{ + vary_SunlitColor = v; +} + +void setAmblitColor(vec3 v) +{ + vary_AmblitColor = v; +} + +void setAdditiveColor(vec3 v) +{ + vary_AdditiveColor = v; +} + +void setAtmosAttenuation(vec3 v) +{ + vary_AtmosAttenuation = v; +} + +void calcAtmospherics(vec3 inPositionEye, float ambFactor) { + + vec3 P = inPositionEye; + setPositionEye(P); + + vec3 tmpLightnorm = lightnorm.xyz; + + vec3 Pn = normalize(P); + float Plen = length(P); + + vec4 temp1 = vec4(0); + vec3 temp2 = vec3(0); + vec4 blue_weight; + vec4 haze_weight; + vec4 sunlight = sunlight_color; + vec4 light_atten; + + //sunlight attenuation effect (hue and brightness) due to atmosphere + //this is used later for sunlight modulation at various altitudes + light_atten = (blue_density + vec4(haze_density * 0.25)) * (density_multiplier * max_y); + //I had thought blue_density and haze_density should have equal weighting, + //but attenuation due to haze_density tends to seem too strong + + temp1 = blue_density + vec4(haze_density); + blue_weight = blue_density / temp1; + haze_weight = vec4(haze_density) / temp1; + + //(TERRAIN) compute sunlight from lightnorm only (for short rays like terrain) + temp2.y = max(0.0, tmpLightnorm.y); + temp2.y = 1. / temp2.y; + sunlight *= exp( - light_atten * temp2.y); + + // main atmospheric scattering line integral + temp2.z = Plen * density_multiplier; + + // Transparency (-> temp1) + // ATI Bugfix -- can't store temp1*temp2.z*distance_multiplier in a variable because the ati + // compiler gets confused. + temp1 = exp(-temp1 * temp2.z * distance_multiplier); + + //final atmosphere attenuation factor + setAtmosAttenuation(temp1.rgb); + + //compute haze glow + //(can use temp2.x as temp because we haven't used it yet) + temp2.x = dot(Pn, tmpLightnorm.xyz); + temp2.x = 1. - temp2.x; + //temp2.x is 0 at the sun and increases away from sun + temp2.x = max(temp2.x, .03); //was glow.y + //set a minimum "angle" (smaller glow.y allows tighter, brighter hotspot) + temp2.x *= glow.x; + //higher glow.x gives dimmer glow (because next step is 1 / "angle") + temp2.x = pow(temp2.x, glow.z); + //glow.z should be negative, so we're doing a sort of (1 / "angle") function + + //add "minimum anti-solar illumination" + temp2.x += .25; + + //increase ambient when there are more clouds + vec4 tmpAmbient = ambient + (vec4(1.) - ambient) * cloud_shadow * 0.5; + + /* decrease value and saturation (that in HSV, not HSL) for occluded areas + * // for HSV color/geometry used here, see http://gimp-savvy.com/BOOK/index.html?node52.html + * // The following line of code performs the equivalent of: + * float ambAlpha = tmpAmbient.a; + * float ambValue = dot(vec3(tmpAmbient), vec3(0.577)); // projection onto <1/rt(3), 1/rt(3), 1/rt(3)>, the neutral white-black axis + * vec3 ambHueSat = vec3(tmpAmbient) - vec3(ambValue); + * tmpAmbient = vec4(RenderSSAOEffect.valueFactor * vec3(ambValue) + RenderSSAOEffect.saturationFactor *(1.0 - ambFactor) * ambHueSat, ambAlpha); + */ + tmpAmbient = vec4(mix(ssao_effect_mat * tmpAmbient.rgb, tmpAmbient.rgb, ambFactor), tmpAmbient.a); + + //haze color + setAdditiveColor( + vec3(blue_horizon * blue_weight * (sunlight*(1.-cloud_shadow) + tmpAmbient) + + (haze_horizon * haze_weight) * (sunlight*(1.-cloud_shadow) * temp2.x + + tmpAmbient))); + + //brightness of surface both sunlight and ambient + setSunlitColor(vec3(sunlight * .5)); + setAmblitColor(vec3(tmpAmbient * .25)); + setAdditiveColor(getAdditiveColor() * vec3(1.0 - temp1)); +} + +vec3 atmosLighting(vec3 light) +{ + light *= getAtmosAttenuation().r; + light += getAdditiveColor(); + return (2.0 * light); +} + +vec3 atmosTransport(vec3 light) { + light *= getAtmosAttenuation().r; + light += getAdditiveColor() * 2.0; + return light; +} +vec3 atmosGetDiffuseSunlightColor() +{ + return getSunlitColor(); +} + +vec3 scaleDownLight(vec3 light) +{ + return (light / vec3(scene_light_strength, scene_light_strength, scene_light_strength)); +} + +vec3 scaleUpLight(vec3 light) +{ + return (light * vec3(scene_light_strength, scene_light_strength, scene_light_strength)); +} + +vec3 atmosAmbient(vec3 light) +{ + return getAmblitColor() + (light * vec3(0.5f, 0.5f, 0.5f)); +} + +vec3 atmosAffectDirectionalLight(float lightIntensity) +{ + return getSunlitColor() * vec3(lightIntensity, lightIntensity, lightIntensity); +} + +vec3 scaleSoftClip(vec3 light) +{ + //soft clip effect: + vec3 zeroes = vec3(0.0f, 0.0f, 0.0f); + vec3 ones = vec3(1.0f, 1.0f, 1.0f); + + light = ones - clamp(light, zeroes, ones); + light = ones - pow(light, gamma.xxx); + + return light; +} + +vec3 fullbrightAtmosTransport(vec3 light) { + float brightness = dot(light.rgb, vec3(0.33333)); + + return mix(atmosTransport(light.rgb), light.rgb + getAdditiveColor().rgb, brightness * brightness); +} + +vec3 fullbrightScaleSoftClip(vec3 light) +{ + //soft clip effect: + return light; +} + +#else +#ifdef DEFINE_GL_FRAGCOLOR +out vec4 frag_data[3]; +#else +#define frag_data gl_FragData +#endif +#endif + +uniform sampler2D diffuseMap; + +#if HAS_NORMAL_MAP +uniform sampler2D bumpMap; +#endif + +#if HAS_SPECULAR_MAP +uniform sampler2D specularMap; + +VARYING vec2 vary_texcoord2; +#endif + +uniform float env_intensity; +uniform vec4 specular_color; // specular color RGB and specular exponent (glossiness) in alpha + +#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_MASK) +uniform float minimum_alpha; +#endif + +#if HAS_NORMAL_MAP +VARYING vec3 vary_mat0; +VARYING vec3 vary_mat1; +VARYING vec3 vary_mat2; +VARYING vec2 vary_texcoord1; +#else +VARYING vec3 vary_normal; +#endif + +VARYING vec4 vertex_color; +VARYING vec2 vary_texcoord0; + +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + +void main() +{ + vec4 diffcol = texture2D(diffuseMap, vary_texcoord0.xy); + diffcol.rgb *= vertex_color.rgb; + +#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_MASK) + if (diffcol.a < minimum_alpha) + { + discard; + } +#endif + +#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND) + vec3 gamma_diff = diffcol.rgb; + diffcol.rgb = srgb_to_linear(diffcol.rgb); +#endif + +#if HAS_SPECULAR_MAP + vec4 spec = texture2D(specularMap, vary_texcoord2.xy); + spec.rgb *= specular_color.rgb; +#else + vec4 spec = vec4(specular_color.rgb, 1.0); +#endif + +#if HAS_NORMAL_MAP + vec4 norm = texture2D(bumpMap, vary_texcoord1.xy); + + norm.xyz = norm.xyz * 2 - 1; + + vec3 tnorm = vec3(dot(norm.xyz,vary_mat0), + dot(norm.xyz,vary_mat1), + dot(norm.xyz,vary_mat2)); +#else + vec4 norm = vec4(0,0,0,1.0); + vec3 tnorm = vary_normal; +#endif + + norm.xyz = tnorm; + norm.xyz = normalize(norm.xyz); + + vec2 abnormal = encode_normal(norm.xyz); + norm.xyz = decode_normal(abnormal.xy); + + vec4 final_color = diffcol; + +#if (DIFFUSE_ALPHA_MODE != DIFFUSE_ALPHA_MODE_EMISSIVE) + final_color.a = emissive_brightness; +#else + final_color.a = max(final_color.a, emissive_brightness); +#endif + + vec4 final_specular = spec; +#if HAS_SPECULAR_MAP + vec4 final_normal = vec4(encode_normal(normalize(tnorm)), env_intensity * spec.a, 0.0); + final_specular.a = specular_color.a * norm.a; +#else + vec4 final_normal = vec4(encode_normal(normalize(tnorm)), env_intensity, 0.0); + final_specular.a = specular_color.a; +#endif + + +#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND) + //forward rendering, output just lit RGBA + vec3 pos = vary_position; + +#if HAS_SUN_SHADOW + float shadow = 0.0; + + vec4 spos = vec4(pos,1.0); + + if (spos.z > -shadow_clip.w) + { + vec4 lpos; + + vec4 near_split = shadow_clip*-0.75; + vec4 far_split = shadow_clip*-1.25; + vec4 transition_domain = near_split-far_split; + float weight = 0.0; + + if (spos.z < near_split.z) + { + lpos = shadow_matrix[3]*spos; + + float w = 1.0; + w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; + shadow += pcfShadow(shadowMap3, lpos)*w; + weight += w; + shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0); + } + + if (spos.z < near_split.y && spos.z > far_split.z) + { + lpos = shadow_matrix[2]*spos; + + float w = 1.0; + w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; + w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; + shadow += pcfShadow(shadowMap2, lpos)*w; + weight += w; + } + + if (spos.z < near_split.x && spos.z > far_split.y) + { + lpos = shadow_matrix[1]*spos; + + float w = 1.0; + w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; + w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; + shadow += pcfShadow(shadowMap1, lpos)*w; + weight += w; + } + + if (spos.z > far_split.x) + { + lpos = shadow_matrix[0]*spos; + + float w = 1.0; + w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; + + shadow += pcfShadow(shadowMap0, lpos)*w; + weight += w; + } + + + shadow /= weight; + } + else + { + shadow = 1.0; + } +#else + float shadow = 1.0; +#endif + + spec = final_specular; + vec4 diffuse = final_color; + float envIntensity = final_normal.z; + + vec3 col = vec3(0.0f,0.0f,0.0f); + + float bloom = 0.0; + calcAtmospherics(pos.xyz, 1.0); + + vec3 refnormpersp = normalize(reflect(pos.xyz, norm.xyz)); + + float da =dot(norm.xyz, sun_dir.xyz); + + float final_da = da; + final_da = min(final_da, shadow); + //final_da = max(final_da, diffuse.a); + final_da = max(final_da, 0.0f); + final_da = min(final_da, 1.0f); + final_da = pow(final_da, 1.0/1.3); + + col.rgb = atmosAmbient(col); + + float ambient = min(abs(da), 1.0); + ambient *= 0.5; + ambient *= ambient; + ambient = (1.0-ambient); + + col.rgb *= ambient; + + col.rgb = col.rgb + atmosAffectDirectionalLight(final_da); + + col.rgb *= gamma_diff.rgb; + + + float glare = 0.0; + + if (spec.a > 0.0) // specular reflection + { + // the old infinite-sky shiny reflection + // + + float sa = dot(refnormpersp, sun_dir.xyz); + vec3 dumbshiny = vary_SunlitColor*shadow*(texture2D(lightFunc, vec2(sa, spec.a)).r); + + // add the two types of shiny together + vec3 spec_contrib = dumbshiny * spec.rgb; + bloom = dot(spec_contrib, spec_contrib) / 6; + + glare = max(spec_contrib.r, spec_contrib.g); + glare = max(glare, spec_contrib.b); + + col += spec_contrib; + } + + + col = mix(col.rgb, diffcol.rgb, diffuse.a); + + if (envIntensity > 0.0) + { + //add environmentmap + vec3 env_vec = env_mat * refnormpersp; + + vec3 refcol = textureCube(environmentMap, env_vec).rgb; + + col = mix(col.rgb, refcol, + envIntensity); + + float cur_glare = max(refcol.r, refcol.g); + cur_glare = max(cur_glare, refcol.b); + cur_glare *= envIntensity*4.0; + glare += cur_glare; + } + + //col = mix(atmosLighting(col), fullbrightAtmosTransport(col), diffuse.a); + //col = mix(scaleSoftClip(col), fullbrightScaleSoftClip(col), diffuse.a); + + col = atmosLighting(col); + col = scaleSoftClip(col); + + //convert to linear space before adding local lights + col = srgb_to_linear(col); + + vec3 npos = normalize(-pos.xyz); + + vec3 light = vec3(0,0,0); + + #define LIGHT_LOOP(i) light.rgb += calcPointLightOrSpotLight(light_diffuse[i].rgb, npos, diffuse.rgb, final_specular, pos.xyz, norm.xyz, light_position[i], light_direction[i].xyz, light_attenuation[i].x, light_attenuation[i].y, light_attenuation[i].z, glare); + + LIGHT_LOOP(1) + LIGHT_LOOP(2) + LIGHT_LOOP(3) + LIGHT_LOOP(4) + LIGHT_LOOP(5) + LIGHT_LOOP(6) + LIGHT_LOOP(7) + + col.rgb += light.rgb; + + glare = min(glare, 1.0); + float al = max(diffcol.a,glare)*vertex_color.a; + + //convert to gamma space for display on screen + col.rgb = linear_to_srgb(col.rgb); + +#ifdef WATER_FOG + vec4 temp = applyWaterFogDeferred(pos, vec4(col.rgb, al)); + col.rgb = temp.rgb; + al = temp.a; +#endif + + frag_color.rgb = col.rgb; + frag_color.a = al; + +#else + frag_data[0] = final_color; + frag_data[1] = final_specular; // XYZ = Specular color. W = Specular exponent. + frag_data[2] = final_normal; // XY = Normal. Z = Env. intensity. +#endif +} + diff --git a/indra/newview/app_settings/shaders/class1/deferred/materialV.glsl b/indra/newview/app_settings/shaders/class1/deferred/materialV.glsl new file mode 100644 index 000000000..393d1e69d --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/materialV.glsl @@ -0,0 +1,145 @@ +/** + * @file bumpV.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#define DIFFUSE_ALPHA_MODE_IGNORE 0 +#define DIFFUSE_ALPHA_MODE_BLEND 1 +#define DIFFUSE_ALPHA_MODE_MASK 2 +#define DIFFUSE_ALPHA_MODE_EMISSIVE 3 + +#if HAS_SKIN +uniform mat4 modelview_matrix; +uniform mat4 projection_matrix; +mat4 getObjectSkinnedTransform(); +#else +uniform mat3 normal_matrix; +uniform mat4 modelview_projection_matrix; +#endif + +#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND) + +#if !HAS_SKIN +uniform mat4 modelview_matrix; +#endif + +VARYING vec3 vary_position; + +#endif + +uniform mat4 texture_matrix0; + +ATTRIBUTE vec3 position; +ATTRIBUTE vec4 diffuse_color; +ATTRIBUTE vec3 normal; +ATTRIBUTE vec2 texcoord0; + + +#if HAS_NORMAL_MAP +ATTRIBUTE vec4 tangent; +ATTRIBUTE vec2 texcoord1; + +VARYING vec3 vary_mat0; +VARYING vec3 vary_mat1; +VARYING vec3 vary_mat2; + +VARYING vec2 vary_texcoord1; +#else +VARYING vec3 vary_normal; +#endif + +#if HAS_SPECULAR_MAP +ATTRIBUTE vec2 texcoord2; +VARYING vec2 vary_texcoord2; +#endif + +VARYING vec4 vertex_color; +VARYING vec2 vary_texcoord0; + +void main() +{ +#if HAS_SKIN + mat4 mat = getObjectSkinnedTransform(); + + mat = modelview_matrix * mat; + + vec3 pos = (mat*vec4(position.xyz,1.0)).xyz; + +#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND) + vary_position = pos; +#endif + + gl_Position = projection_matrix*vec4(pos,1.0); + +#else + //transform vertex + gl_Position = modelview_projection_matrix * vec4(position.xyz, 1.0); + +#endif + + vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy; + +#if HAS_NORMAL_MAP + vary_texcoord1 = (texture_matrix0 * vec4(texcoord1,0,1)).xy; +#endif + +#if HAS_SPECULAR_MAP + vary_texcoord2 = (texture_matrix0 * vec4(texcoord2,0,1)).xy; +#endif + +#if HAS_SKIN + vec3 n = normalize((mat*vec4(normal.xyz+position.xyz,1.0)).xyz-pos.xyz); +#if HAS_NORMAL_MAP + vec3 t = normalize((mat*vec4(tangent.xyz+position.xyz,1.0)).xyz-pos.xyz); + vec3 b = cross(n, t)*tangent.w; + + vary_mat0 = vec3(t.x, b.x, n.x); + vary_mat1 = vec3(t.y, b.y, n.y); + vary_mat2 = vec3(t.z, b.z, n.z); +#else //HAS_NORMAL_MAP +vary_normal = n; +#endif //HAS_NORMAL_MAP +#else //HAS_SKIN + vec3 n = normalize(normal_matrix * normal); +#if HAS_NORMAL_MAP + vec3 t = normalize(normal_matrix * tangent.xyz); + vec3 b = cross(n,t)*tangent.w; + //vec3 t = cross(b,n) * binormal.w; + + vary_mat0 = vec3(t.x, b.x, n.x); + vary_mat1 = vec3(t.y, b.y, n.y); + vary_mat2 = vec3(t.z, b.z, n.z); +#else //HAS_NORMAL_MAP + vary_normal = n; +#endif //HAS_NORMAL_MAP +#endif //HAS_SKIN + + vertex_color = diffuse_color; + +#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND) +#if !HAS_SKIN + vary_position = (modelview_matrix*vec4(position.xyz, 1.0)).xyz; +#endif +#endif +} + diff --git a/indra/newview/app_settings/shaders/class1/deferred/multiPointLightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/multiPointLightF.glsl index 8a39a875c..236567219 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/multiPointLightF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/multiPointLightF.glsl @@ -45,9 +45,8 @@ uniform float sun_wash; uniform int light_count; -#define MAX_LIGHT_COUNT 16 -uniform vec4 light[MAX_LIGHT_COUNT]; -uniform vec4 light_col[MAX_LIGHT_COUNT]; +uniform vec4 light[LIGHT_COUNT]; +uniform vec4 light_col[LIGHT_COUNT]; VARYING vec4 vary_fragcoord; uniform vec2 screen_res; @@ -56,6 +55,23 @@ uniform float far_z; uniform mat4 inv_proj; +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + vec4 getPosition(vec2 pos_screen) { float depth = texture2DRect(depthMap, pos_screen.xy).r; @@ -69,20 +85,6 @@ vec4 getPosition(vec2 pos_screen) return pos; } -vec3 unpack(vec2 tc) -{ -//#define PACK_NORMALS -#ifdef PACK_NORMALS - vec2 enc = texture2DRect(normalMap, tc).xy; - enc = enc*4.0-2.0; - float prod = dot(enc,enc); - return vec3(enc*sqrt(1.0-prod*.25),1.0-prod*.5); -#else - vec3 norm = texture2DRect(normalMap, tc).xyz; - return norm*2.0-1.0; -#endif -} - void main() { vec2 frag = (vary_fragcoord.xy*0.5+0.5)*screen_res; @@ -92,70 +94,71 @@ void main() discard; } - vec3 norm = unpack(frag.xy); // unpack norm - //norm = normalize(norm); // may be superfluous + vec3 norm = texture2DRect(normalMap, frag.xy).xyz; + norm = decode_normal(norm.xy); // unpack norm + norm = normalize(norm); vec4 spec = texture2DRect(specularRect, frag.xy); vec3 diff = texture2DRect(diffuseRect, frag.xy).rgb; + float noise = texture2D(noiseMap, frag.xy/128.0).b; vec3 out_col = vec3(0,0,0); vec3 npos = normalize(-pos); // As of OSX 10.6.7 ATI Apple's crash when using a variable size loop - for (int i = 0; i < MAX_LIGHT_COUNT; ++i) + for (int i = 0; i < LIGHT_COUNT; ++i) { - bool light_contrib = (i < light_count); - vec3 lv = light[i].xyz-pos; - float dist2 = dot(lv,lv); - dist2 /= light[i].w; - if (dist2 > 1.0) + float dist = length(lv); + dist /= light[i].w; + if (dist <= 1.0) { - light_contrib = false; - } - float da = dot(norm, lv); - if (da < 0.0) - { - light_contrib = false; - } - - if (light_contrib) + if (da > 0.0) { lv = normalize(lv); da = dot(norm, lv); - + float fa = light_col[i].a+1.0; - float dist_atten = clamp(1.0-(dist2-1.0*(1.0-fa))/fa, 0.0, 1.0); + float dist_atten = clamp(1.0-(dist-1.0*(1.0-fa))/fa, 0.0, 1.0); + dist_atten *= dist_atten; + dist_atten *= 2.0; + dist_atten *= noise; float lit = da * dist_atten; - + vec3 col = light_col[i].rgb*lit*diff; + //vec3 col = vec3(dist2, light_col[i].a, lit); if (spec.a > 0.0) { + lit = min(da*6.0, 1.0) * dist_atten; //vec3 ref = dot(pos+lv, norm); - - float sa = dot(normalize(lv+npos),norm); - - if (sa > 0.0) + vec3 h = normalize(lv+npos); + float nh = dot(norm, h); + float nv = dot(norm, npos); + float vh = dot(npos, h); + float sa = nh; + float fres = pow(1 - dot(h, npos), 5)*0.4+0.5; + + float gtdenom = 2 * nh; + float gt = max(0, min(gtdenom * nv / vh, gtdenom * da / vh)); + + if (nh > 0.0) { - sa = texture2D(lightFunc, vec2(sa, spec.a)).r * min(dist_atten*4.0, 1.0); - sa *= noise; - col += da*sa*light_col[i].rgb*spec.rgb; + float scol = fres*texture2D(lightFunc, vec2(nh, spec.a)).r*gt/(nh*da); + col += lit*scol*light_col[i].rgb*spec.rgb; + //col += spec.rgb; } } out_col += col; } } - - if (dot(out_col, out_col) <= 0.0) - { - discard; } + frag_color.rgb = out_col; frag_color.a = 0.0; } diff --git a/indra/newview/app_settings/shaders/class1/deferred/multiSpotLightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/multiSpotLightF.glsl index a82ba899a..0e6ab80d4 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/multiSpotLightF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/multiSpotLightF.glsl @@ -32,6 +32,7 @@ out vec4 frag_color; //class 1 -- no shadows #extension GL_ARB_texture_rectangle : enable +#extension GL_ARB_shader_texture_lod : enable uniform sampler2DRect diffuseRect; uniform sampler2DRect specularRect; @@ -40,6 +41,7 @@ uniform sampler2DRect normalMap; uniform samplerCube environmentMap; uniform sampler2D noiseMap; uniform sampler2D projectionMap; +uniform sampler2D lightFunc; uniform mat4 proj_mat; //screen space to light space uniform float proj_near; //near clip for projection @@ -66,9 +68,64 @@ uniform vec2 screen_res; uniform mat4 inv_proj; +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lte); +#endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + + vec4 texture2DLodSpecular(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); + ret.rgb = srgb_to_linear(ret.rgb); vec2 dist = tc-vec2(0.5); @@ -84,6 +141,7 @@ vec4 texture2DLodSpecular(sampler2D projectionMap, vec2 tc, float lod) vec4 texture2DLodDiffuse(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); + ret.rgb = srgb_to_linear(ret.rgb); vec2 dist = vec2(0.5) - abs(tc-vec2(0.5)); @@ -101,6 +159,7 @@ vec4 texture2DLodDiffuse(sampler2D projectionMap, vec2 tc, float lod) vec4 texture2DLodAmbient(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); + ret.rgb = srgb_to_linear(ret.rgb); vec2 dist = tc-vec2(0.5); @@ -125,20 +184,6 @@ vec4 getPosition(vec2 pos_screen) return pos; } -vec3 unpack(vec2 tc) -{ -//#define PACK_NORMALS -#ifdef PACK_NORMALS - vec2 enc = texture2DRect(normalMap, tc).xy; - enc = enc*4.0-2.0; - float prod = dot(enc,enc); - return vec3(enc*sqrt(1.0-prod*.25),1.0-prod*.5); -#else - vec3 norm = texture2DRect(normalMap, tc).xyz; - return norm*2.0-1.0; -#endif -} - void main() { vec4 frag = vary_fragcoord; @@ -148,16 +193,19 @@ void main() vec3 pos = getPosition(frag.xy).xyz; vec3 lv = center.xyz-pos.xyz; - float dist2 = dot(lv,lv); - dist2 /= size; - if (dist2 > 1.0) + float dist = length(lv); + dist /= size; + if (dist > 1.0) { discard; } - vec3 norm = unpack(frag.xy); // unpack norm + vec3 norm = texture2DRect(normalMap, frag.xy).xyz; + float envIntensity = norm.z; + + norm = decode_normal(norm.xy); - //norm = normalize(norm); // may be superfluous + norm = normalize(norm); float l_dist = -dot(lv, proj_n); vec4 proj_tc = (proj_mat * vec4(pos.xyz, 1.0)); @@ -169,7 +217,10 @@ void main() proj_tc.xyz /= proj_tc.w; float fa = falloff+1.0; - float dist_atten = min(1.0-(dist2-1.0*(1.0-fa))/fa, 1.0); + float dist_atten = min(1.0-(dist-1.0*(1.0-fa))/fa, 1.0); + dist_atten *= dist_atten; + dist_atten *= 2.0; + if (dist_atten <= 0.0) { discard; @@ -182,7 +233,8 @@ void main() vec3 col = vec3(0,0,0); vec3 diff_tex = texture2DRect(diffuseRect, frag.xy).rgb; - + vec3 dlit = vec3(0, 0, 0); + float noise = texture2D(noiseMap, frag.xy/128.0).b; if (proj_tc.z > 0.0 && proj_tc.x < 1.0 && @@ -200,14 +252,13 @@ void main() vec4 plcol = texture2DLodDiffuse(projectionMap, proj_tc.xy, lod); - vec3 lcol = color.rgb * plcol.rgb * plcol.a; + dlit = color.rgb * plcol.rgb * plcol.a; lit = da * dist_atten * noise; - col = lcol*lit*diff_tex; + col = dlit*lit*diff_tex; amb_da += (da*0.5)*proj_ambiance; } - //float diff = clamp((proj_range-proj_focus)/proj_range, 0.0, 1.0); vec4 amb_plcol = texture2DLodAmbient(projectionMap, proj_tc.xy, proj_lod); @@ -216,13 +267,38 @@ void main() amb_da *= dist_atten * noise; amb_da = min(amb_da, 1.0-lit); - - col += amb_da*color.rgb*diff_tex.rgb*amb_plcol.rgb*amb_plcol.a; + col += amb_da*color.rgb*diff_tex*amb_plcol.rgb*amb_plcol.a; } vec4 spec = texture2DRect(specularRect, frag.xy); + if (spec.a > 0.0) + { + dlit *= min(da*6.0, 1.0) * dist_atten; + + vec3 npos = -normalize(pos); + + //vec3 ref = dot(pos+lv, norm); + vec3 h = normalize(lv+npos); + float nh = dot(norm, h); + float nv = dot(norm, npos); + float vh = dot(npos, h); + float sa = nh; + float fres = pow(1 - dot(h, npos), 5)*0.4+0.5; + + float gtdenom = 2 * nh; + float gt = max(0, min(gtdenom * nv / vh, gtdenom * da / vh)); + + if (nh > 0.0) + { + float scol = fres*texture2D(lightFunc, vec2(nh, spec.a)).r*gt/(nh*da); + col += dlit*scol*spec.rgb; + //col += spec.rgb; + } + } + + if (envIntensity > 0.0) { vec3 ref = reflect(normalize(pos), norm); @@ -235,12 +311,10 @@ void main() vec3 pfinal = pos + ref * dot(pdelta, proj_n)/ds; vec4 stc = (proj_mat * vec4(pfinal.xyz, 1.0)); - + stc /= stc.w; if (stc.z > 0.0) { - stc.xy /= stc.w; - - float fatten = clamp(spec.a*spec.a+spec.a*0.5, 0.25, 1.0); + float fatten = clamp(envIntensity*envIntensity+envIntensity*0.25, 0.25, 1.0); stc.xy = (stc.xy - vec2(0.5)) * fatten + vec2(0.5); @@ -249,8 +323,7 @@ void main() stc.x > 0.0 && stc.y > 0.0) { - vec4 scol = texture2DLodSpecular(projectionMap, stc.xy, proj_lod-spec.a*proj_lod); - col += dist_atten*scol.rgb*color.rgb*scol.a*spec.rgb; + col += color.rgb*texture2DLodSpecular(projectionMap, stc.xy, proj_lod).rgb*spec.rgb; } } } diff --git a/indra/newview/app_settings/shaders/class1/deferred/pointLightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/pointLightF.glsl index 107fd04f5..106d48bd7 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/pointLightF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/pointLightF.glsl @@ -54,6 +54,23 @@ uniform vec2 screen_res; uniform mat4 inv_proj; uniform vec4 viewport; +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + vec4 getPosition(vec2 pos_screen) { float depth = texture2DRect(depthMap, pos_screen.xy).r; @@ -67,20 +84,6 @@ vec4 getPosition(vec2 pos_screen) return pos; } -vec3 unpack(vec2 tc) -{ -//#define PACK_NORMALS -#ifdef PACK_NORMALS - vec2 enc = texture2DRect(normalMap, tc).xy; - enc = enc*4.0-2.0; - float prod = dot(enc,enc); - return vec3(enc*sqrt(1.0-prod*.25),1.0-prod*.5); -#else - vec3 norm = texture2DRect(normalMap, tc).xyz; - return norm*2.0-1.0; -#endif -} - void main() { vec4 frag = vary_fragcoord; @@ -90,21 +93,22 @@ void main() vec3 pos = getPosition(frag.xy).xyz; vec3 lv = trans_center.xyz-pos; - float dist2 = dot(lv,lv); - dist2 /= size; - if (dist2 > 1.0) + float dist = length(lv); + dist /= size; + if (dist > 1.0) { discard; } - vec3 norm = unpack(frag.xy); // unpack norm + vec3 norm = texture2DRect(normalMap, frag.xy).xyz; + norm = decode_normal(norm.xy); // unpack norm float da = dot(norm, lv); if (da < 0.0) { discard; } - //norm = normalize(norm); // may be superfluous + norm = normalize(norm); lv = normalize(lv); da = dot(norm, lv); @@ -112,20 +116,33 @@ void main() vec3 col = texture2DRect(diffuseRect, frag.xy).rgb; float fa = falloff+1.0; - float dist_atten = clamp(1.0-(dist2-1.0*(1.0-fa))/fa, 0.0, 1.0); - float lit = da * dist_atten * noise; + float dist_atten = clamp(1.0-(dist-1.0*(1.0-fa))/fa, 0.0, 1.0); + dist_atten *= dist_atten; + dist_atten *= 2.0; + float lit = da * dist_atten * noise; + col = color.rgb*lit*col; vec4 spec = texture2DRect(specularRect, frag.xy); if (spec.a > 0.0) { - float sa = dot(normalize(lv-normalize(pos)),norm); - if (sa > 0.0) + lit = min(da*6.0, 1.0) * dist_atten; + + vec3 npos = -normalize(pos); + vec3 h = normalize(lv+npos); + float nh = dot(norm, h); + float nv = dot(norm, npos); + float vh = dot(npos, h); + float sa = nh; + float fres = pow(1 - dot(h, npos), 5) * 0.4+0.5; + float gtdenom = 2 * nh; + float gt = max(0,(min(gtdenom * nv / vh, gtdenom * da / vh))); + + if (nh > 0.0) { - sa = texture2D(lightFunc, vec2(sa, spec.a)).r * min(dist_atten*4.0, 1.0); - sa *= noise; - col += da*sa*color.rgb*spec.rgb; + float scol = fres*texture2D(lightFunc, vec2(nh, spec.a)).r*gt/(nh*da); + col += lit*scol*color.rgb*spec.rgb; } } diff --git a/indra/newview/app_settings/shaders/class1/deferred/pointLightV.glsl b/indra/newview/app_settings/shaders/class1/deferred/pointLightV.glsl index 949142123..a5625fbc1 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/pointLightV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/pointLightV.glsl @@ -37,7 +37,7 @@ VARYING vec3 trans_center; void main() { //transform vertex - vec3 p = position*sqrt(size)+center; + vec3 p = position*size+center; vec4 pos = modelview_projection_matrix * vec4(p.xyz, 1.0); vary_fragcoord = pos; trans_center = (modelview_matrix*vec4(center.xyz, 1.0)).xyz; diff --git a/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl b/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl new file mode 100644 index 000000000..126f17fab --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl @@ -0,0 +1,67 @@ +/** + * @file postDeferredGammaCorrect.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#extension GL_ARB_texture_rectangle : enable + +#ifdef DEFINE_GL_FRAGCOLOR +out vec4 frag_color; +#else +#define frag_color gl_FragColor +#endif + +uniform sampler2DRect diffuseRect; + +uniform vec2 screen_res; +VARYING vec2 vary_fragcoord; + +//uniform float display_gamma; + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + + +void main() +{ + vec4 diff = texture2DRect(diffuseRect, vary_fragcoord); + diff.rgb = linear_to_srgb(diff.rgb); + frag_color = diff; +} + diff --git a/indra/newview/app_settings/shaders/class1/deferred/skyF.glsl b/indra/newview/app_settings/shaders/class1/deferred/skyF.glsl index 4eefdd8b6..3e861a043 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/skyF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/skyF.glsl @@ -61,11 +61,7 @@ void main() /// Gamma correct for WL (soft clip effect). frag_data[0] = vec4(scaleSoftClip(color.rgb), 1.0); frag_data[1] = vec4(vary_HazeColor.a,0.0,0.0,0.0); -//#define PACK_NORMALS -#ifdef PACK_NORMALS - frag_data[2] = vec4(0.5,0.5,0.0,0.0); -#else - frag_data[2] = vec4(0.0,0.0,1.0,0.0); -#endif + frag_data[2] = vec4(0.5,0.5,0.0,1.0); + } diff --git a/indra/newview/app_settings/shaders/class1/deferred/softenLightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/softenLightF.glsl index 302f31d28..edab4d4e8 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/softenLightF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/softenLightF.glsl @@ -24,6 +24,7 @@ */ #extension GL_ARB_texture_rectangle : enable +#extension GL_ARB_shader_texture_lod : enable #ifdef DEFINE_GL_FRAGCOLOR out vec4 frag_color; @@ -60,6 +61,7 @@ uniform float density_multiplier; uniform float distance_multiplier; uniform float max_y; uniform vec4 glow; +uniform float global_gamma; uniform float scene_light_strength; uniform mat3 env_mat; uniform float ssao_effect; @@ -77,6 +79,54 @@ vec3 vary_AtmosAttenuation; uniform mat4 inv_proj; uniform vec2 screen_res; +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lte); +#endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + vec4 getPosition_d(vec2 pos_screen, float depth) { vec2 sc = pos_screen.xy*2.0; @@ -142,6 +192,52 @@ void setAtmosAttenuation(vec3 v) vary_AtmosAttenuation = v; } +#ifdef WATER_FOG +uniform vec4 waterPlane; +uniform vec4 waterFogColor; +uniform float waterFogDensity; +uniform float waterFogKS; + +vec4 applyWaterFogDeferred(vec3 pos, vec4 color) +{ + //normalize view vector + vec3 view = normalize(pos); + float es = -(dot(view, waterPlane.xyz)); + + //find intersection point with water plane and eye vector + + //get eye depth + float e0 = max(-waterPlane.w, 0.0); + + vec3 int_v = waterPlane.w > 0.0 ? view * waterPlane.w/es : vec3(0.0, 0.0, 0.0); + + //get object depth + float depth = length(pos - int_v); + + //get "thickness" of water + float l = max(depth, 0.1); + + float kd = waterFogDensity; + float ks = waterFogKS; + vec4 kc = waterFogColor; + + float F = 0.98; + + float t1 = -kd * pow(F, ks * e0); + float t2 = kd + ks * es; + float t3 = pow(F, t2*l) - 1.0; + + float L = min(t1/t2*t3, 1.0); + + float D = pow(0.98, l*kd); + + color.rgb = color.rgb * D + kc.rgb * L; + color.a = kc.a + color.a; + + return color; +} +#endif + void calcAtmospherics(vec3 inPositionEye, float ambFactor) { vec3 P = inPositionEye; @@ -230,6 +326,15 @@ vec3 atmosTransport(vec3 light) { light += getAdditiveColor() * 2.0; return light; } + +vec3 fullbrightAtmosTransport(vec3 light) { + float brightness = dot(light.rgb, vec3(0.33333)); + + return mix(atmosTransport(light.rgb), light.rgb + getAdditiveColor().rgb, brightness * brightness); +} + + + vec3 atmosGetDiffuseSunlightColor() { return getSunlitColor(); @@ -264,18 +369,10 @@ vec3 scaleSoftClip(vec3 light) return light; } -vec3 unpack(vec2 tc) +vec3 fullbrightScaleSoftClip(vec3 light) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - vec2 enc = texture2DRect(normalMap, tc).xy; - enc = enc*4.0-2.0; - float prod = dot(enc,enc); - return vec3(enc*sqrt(1.0-prod*.25),1.0-prod*.5); -#else - vec3 norm = texture2DRect(normalMap, tc).xyz; - return norm*2.0-1.0; -#endif + //soft clip effect: + return light; } float luminance(vec3 color) @@ -290,57 +387,95 @@ void main() vec2 tc = vary_fragcoord.xy; float depth = texture2DRect(depthMap, tc.xy).r; vec3 pos = getPosition_d(tc, depth).xyz; - vec3 norm = unpack(tc); // unpack norm + vec4 norm = texture2DRect(normalMap, tc); + float envIntensity = norm.z; + norm.xyz = decode_normal(norm.xy); // unpack norm - float da = max(dot(norm.xyz, sun_dir.xyz), 0.0); - + float da = dot(norm.xyz, sun_dir.xyz); + + float final_da = max(0.0,da); + final_da = min(final_da, 1.0f); + final_da = pow(final_da, 1.0/1.3); + vec4 diffuse = texture2DRect(diffuseRect, tc); + + //convert to gamma space + diffuse.rgb = linear_to_srgb(diffuse.rgb); + vec4 spec = texture2DRect(specularRect, vary_fragcoord.xy); - vec3 col; float bloom = 0.0; - if (diffuse.a < 0.9) { + bloom = spec.r*norm.w; + if (norm.w < 0.5) + { calcAtmospherics(pos.xyz, 1.0); col = atmosAmbient(vec3(0)); - col += atmosAffectDirectionalLight(max(min(da, 1.0), diffuse.a)); + float ambient = min(abs(dot(norm.xyz, sun_dir.xyz)), 1.0); + ambient *= 0.5; + ambient *= ambient; + ambient = (1.0-ambient); + + col.rgb *= ambient; + + col += atmosAffectDirectionalLight(final_da); col *= diffuse.rgb; + vec3 refnormpersp = normalize(reflect(pos.xyz, norm.xyz)); + if (spec.a > 0.0) // specular reflection { // the old infinite-sky shiny reflection // - vec3 refnormpersp = normalize(reflect(pos.xyz, norm.xyz)); + float sa = dot(refnormpersp, sun_dir.xyz); vec3 dumbshiny = vary_SunlitColor*(texture2D(lightFunc, vec2(sa, spec.a)).r); // add the two types of shiny together vec3 spec_contrib = dumbshiny * spec.rgb; - bloom = dot(spec_contrib, spec_contrib) / 4; + bloom = dot(spec_contrib, spec_contrib) / 6; col += spec_contrib; - - //add environmentmap - vec3 env_vec = env_mat * refnormpersp; - vec3 env = textureCube(environmentMap, env_vec).rgb; - bloom = (luminance(env) - .45)*.25; - col = mix(col.rgb, env, - max(spec.a-diffuse.a*2.0, 0.0)); } - - col = atmosLighting(col); - col = scaleSoftClip(col); + + + col = mix(col.rgb, diffuse.rgb, diffuse.a); + + if (envIntensity > 0.0) + { //add environmentmap + vec3 env_vec = env_mat * refnormpersp; + vec3 refcol = textureCube(environmentMap, env_vec).rgb; + bloom = (luminance(refcol) - .45)*.25; + col = mix(col.rgb, refcol, + envIntensity); + } + + //if (norm.w < 0.5) + { + col = mix(atmosLighting(col), fullbrightAtmosTransport(col), diffuse.a); + col = mix(scaleSoftClip(col), fullbrightScaleSoftClip(col), diffuse.a); + //bloom += (luminance(col))*.075; //This looks nice, but requires a larger glow rendertarget. + } - col = mix(col, diffuse.rgb, diffuse.a); - } - else - { - bloom = spec.r; - col = diffuse.rgb; + #ifdef WATER_FOG + vec4 fogged = applyWaterFogDeferred(pos,vec4(col, bloom)); + col = fogged.rgb; + bloom = fogged.a; + #endif + } + else + { + col = diffuse.rgb; + } + + col = srgb_to_linear(col); + + //col = vec3(1,0,1); + //col.g = envIntensity; } - frag_color.rgb = col; + frag_color.rgb = col.rgb; frag_color.a = bloom; } diff --git a/indra/newview/app_settings/shaders/class1/deferred/spotLightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/spotLightF.glsl index ae64d8937..8d8a6c9dd 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/spotLightF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/spotLightF.glsl @@ -23,7 +23,9 @@ * $/LicenseInfo$ */ - +#extension GL_ARB_texture_rectangle : enable +#extension GL_ARB_shader_texture_lod : enable + #ifdef DEFINE_GL_FRAGCOLOR out vec4 frag_color; #else @@ -32,8 +34,6 @@ out vec4 frag_color; //class 1 -- no shadows -#extension GL_ARB_texture_rectangle : enable - uniform sampler2DRect diffuseRect; uniform sampler2DRect specularRect; uniform sampler2DRect depthMap; @@ -41,6 +41,7 @@ uniform sampler2DRect normalMap; uniform samplerCube environmentMap; uniform sampler2D noiseMap; uniform sampler2D projectionMap; +uniform sampler2D lightFunc; uniform mat4 proj_mat; //screen space to light space uniform float proj_near; //near clip for projection @@ -57,20 +58,79 @@ uniform float far_clip; uniform vec3 proj_origin; //origin of projection to be used for angular attenuation uniform float sun_wash; +uniform float size; uniform vec3 color; uniform float falloff; -uniform float size; -VARYING vec4 vary_fragcoord; VARYING vec3 trans_center; - +VARYING vec4 vary_fragcoord; uniform vec2 screen_res; uniform mat4 inv_proj; +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lte); +#endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + +vec4 correctWithGamma(vec4 col) +{ + return vec4(srgb_to_linear(col.rgb), col.a); +} + vec4 texture2DLodSpecular(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); + ret = correctWithGamma(ret); vec2 dist = tc-vec2(0.5); @@ -86,6 +146,7 @@ vec4 texture2DLodSpecular(sampler2D projectionMap, vec2 tc, float lod) vec4 texture2DLodDiffuse(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); + ret = correctWithGamma(ret); vec2 dist = vec2(0.5) - abs(tc-vec2(0.5)); @@ -103,6 +164,7 @@ vec4 texture2DLodDiffuse(sampler2D projectionMap, vec2 tc, float lod) vec4 texture2DLodAmbient(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); + ret = correctWithGamma(ret); vec2 dist = tc-vec2(0.5); @@ -127,20 +189,6 @@ vec4 getPosition(vec2 pos_screen) return pos; } -vec3 unpack(vec2 tc) -{ -//#define PACK_NORMALS -#ifdef PACK_NORMALS - vec2 enc = texture2DRect(normalMap, tc).xy; - enc = enc*4.0-2.0; - float prod = dot(enc,enc); - return vec3(enc*sqrt(1.0-prod*.25),1.0-prod*.5); -#else - vec3 norm = texture2DRect(normalMap, tc).xyz; - return norm*2.0-1.0; -#endif -} - void main() { vec4 frag = vary_fragcoord; @@ -150,16 +198,19 @@ void main() vec3 pos = getPosition(frag.xy).xyz; vec3 lv = trans_center.xyz-pos.xyz; - float dist2 = dot(lv,lv); - dist2 /= size; - if (dist2 > 1.0) + float dist = length(lv); + dist /= size; + if (dist > 1.0) { discard; } - - vec3 norm = unpack(frag.xy); // unpack norm - //norm = normalize(norm); // may be superfluous + + vec3 norm = texture2DRect(normalMap, frag.xy).xyz; + float envIntensity = norm.z; + norm = decode_normal(norm.xy); + + norm = normalize(norm); float l_dist = -dot(lv, proj_n); vec4 proj_tc = (proj_mat * vec4(pos.xyz, 1.0)); @@ -171,7 +222,10 @@ void main() proj_tc.xyz /= proj_tc.w; float fa = falloff+1.0; - float dist_atten = min(1.0-(dist2-1.0*(1.0-fa))/fa, 1.0); + float dist_atten = min(1.0-(dist-1.0*(1.0-fa))/fa, 1.0); + dist_atten *= dist_atten; + dist_atten *= 2.0; + if (dist_atten <= 0.0) { discard; @@ -185,31 +239,35 @@ void main() vec3 diff_tex = texture2DRect(diffuseRect, frag.xy).rgb; + vec4 spec = texture2DRect(specularRect, frag.xy); + + + float noise = texture2D(noiseMap, frag.xy/128.0).b; + vec3 dlit = vec3(0, 0, 0); + if (proj_tc.z > 0.0 && proj_tc.x < 1.0 && proj_tc.y < 1.0 && proj_tc.x > 0.0 && proj_tc.y > 0.0) { - float lit = 0.0; float amb_da = proj_ambiance; + float lit = 0.0; if (da > 0.0) { + lit = da * dist_atten * noise; + float diff = clamp((l_dist-proj_focus)/proj_range, 0.0, 1.0); float lod = diff * proj_lod; vec4 plcol = texture2DLodDiffuse(projectionMap, proj_tc.xy, lod); - - vec3 lcol = color.rgb * plcol.rgb * plcol.a; + dlit = color.rgb * plcol.rgb * plcol.a; - lit = da * dist_atten * noise; - - col = lcol*lit*diff_tex; - amb_da += (da*0.5)*proj_ambiance; + col = dlit*lit*diff_tex; + //amb_da += (da*0.5)*(1.0-shadow)*proj_ambiance; } - //float diff = clamp((proj_range-proj_focus)/proj_range, 0.0, 1.0); vec4 amb_plcol = texture2DLodAmbient(projectionMap, proj_tc.xy, proj_lod); @@ -218,13 +276,37 @@ void main() amb_da *= dist_atten * noise; amb_da = min(amb_da, 1.0-lit); - - col += amb_da*color.rgb*diff_tex.rgb*amb_plcol.rgb*amb_plcol.a; + col += amb_da*color.rgb*diff_tex.rgb*amb_plcol.rgb*amb_plcol.a*diff_tex.rgb; } - - vec4 spec = texture2DRect(specularRect, frag.xy); + if (spec.a > 0.0) + { + dlit *= min(da*6.0, 1.0) * dist_atten; + vec3 npos = -normalize(pos); + + //vec3 ref = dot(pos+lv, norm); + vec3 h = normalize(lv+npos); + float nh = dot(norm, h); + float nv = dot(norm, npos); + float vh = dot(npos, h); + float sa = nh; + float fres = pow(1 - dot(h, npos), 5)*0.4+0.5; + + float gtdenom = 2 * nh; + float gt = max(0, min(gtdenom * nv / vh, gtdenom * da / vh)); + + if (nh > 0.0) + { + + float scol = fres*texture2D(lightFunc, vec2(nh, spec.a)).r*gt/(nh*da); + col += dlit*scol*spec.rgb; + //col += spec.rgb; + } + } + + + if (envIntensity > 0.0) { vec3 ref = reflect(normalize(pos), norm); @@ -242,8 +324,9 @@ void main() { stc.xy /= stc.w; - float fatten = clamp(spec.a*spec.a+spec.a*0.5, 0.25, 1.0); + float fatten = clamp(envIntensity*envIntensity+envIntensity*0.5, 0.25, 1.0); + //stc.xy = (stc.xy - vec2(0.5)) * fatten + vec2(0.5); stc.xy = (stc.xy - vec2(0.5)) * fatten + vec2(0.5); if (stc.x < 1.0 && @@ -251,8 +334,7 @@ void main() stc.x > 0.0 && stc.y > 0.0) { - vec4 scol = texture2DLodSpecular(projectionMap, stc.xy, proj_lod-spec.a*proj_lod); - col += dist_atten*scol.rgb*color.rgb*scol.a*spec.rgb; + col += color.rgb*texture2DLodSpecular(projectionMap, stc.xy, proj_lod-envIntensity*proj_lod).rgb*spec.rgb; } } } diff --git a/indra/newview/app_settings/shaders/class1/deferred/srgb.glsl b/indra/newview/app_settings/shaders/class1/deferred/srgb.glsl new file mode 100644 index 000000000..587f3d5a9 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/srgb.glsl @@ -0,0 +1,46 @@ +/** + * @file srgb.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + return mix(high_range, low_range, lte); + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + + bvec3 lt = lessThan(cl,vec3(0.0031308)); + return mix(high_range, low_range, lt); + +} + diff --git a/indra/newview/app_settings/shaders/class1/deferred/srgb_mac.glsl b/indra/newview/app_settings/shaders/class1/deferred/srgb_mac.glsl new file mode 100644 index 000000000..6cc1e6e79 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/srgb_mac.glsl @@ -0,0 +1,54 @@ +/** + * @file srgb.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + + bvec3 lt = lessThan(cl,vec3(0.0031308)); + + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +} + diff --git a/indra/newview/app_settings/shaders/class1/deferred/starsF.glsl b/indra/newview/app_settings/shaders/class1/deferred/starsF.glsl index 2e4b416c4..99e371cf8 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/starsF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/starsF.glsl @@ -42,10 +42,5 @@ void main() frag_data[0] = vec4(col.rgb,col.a*custom_alpha); frag_data[1] = vec4(0,0,0,0); -//#define PACK_NORMALS -#ifdef PACK_NORMALS - frag_data[2] = vec4(0.5,0.5,0.0,0.0); -#else - frag_data[2] = vec4(0.0,0.0,1.0,0.0); -#endif + frag_data[2] = vec4(0.5,0.5,0.0,1.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/sunLightSSAOF.glsl b/indra/newview/app_settings/shaders/class1/deferred/sunLightSSAOF.glsl index 24a827e91..730f01117 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/sunLightSSAOF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/sunLightSSAOF.glsl @@ -49,6 +49,23 @@ VARYING vec2 vary_fragcoord; uniform mat4 inv_proj; uniform vec2 screen_res; +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + vec4 getPosition(vec2 pos_screen) { float depth = texture2DRect(depthMap, pos_screen.xy).r; @@ -115,19 +132,6 @@ float calcAmbientOcclusion(vec4 pos, vec3 norm) return (rtn * rtn); } -vec3 unpack(vec2 tc) -{ -//#define PACK_NORMALS -#ifdef PACK_NORMALS - vec2 enc = texture2DRect(normalMap, tc).xy; - enc = enc*4.0-2.0; - float prod = dot(enc,enc); - return vec3(enc*sqrt(1.0-prod*.25),1.0-prod*.5); -#else - vec3 norm = texture2DRect(normalMap, tc).xyz; - return norm*2.0-1.0; -#endif -} void main() { vec2 pos_screen = vary_fragcoord.xy; @@ -136,7 +140,8 @@ void main() vec4 pos = getPosition(pos_screen); - vec3 norm = unpack(pos_screen); // unpack norm + vec3 norm = texture2DRect(normalMap, pos_screen).xyz; + norm = decode_normal(norm.xy); frag_color[0] = 1.0; frag_color[1] = calcAmbientOcclusion(pos, norm); diff --git a/indra/newview/app_settings/shaders/class1/deferred/terrainF.glsl b/indra/newview/app_settings/shaders/class1/deferred/terrainF.glsl index 3e4c5fc9c..52a429465 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/terrainF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/terrainF.glsl @@ -39,15 +39,10 @@ VARYING vec3 vary_normal; VARYING vec4 vary_texcoord0; VARYING vec4 vary_texcoord1; -vec3 pack(vec3 norm) +vec2 encode_normal(vec3 n) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - float p = sqrt(8.0*norm.z+8.0); - return vec3(norm.xy/p + 0.5, 0.0); -#else - return norm.xyz*0.5+0.5; -#endif + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; } void main() @@ -67,6 +62,6 @@ void main() frag_data[0] = vec4(outColor.rgb, 0.0); frag_data[1] = vec4(0,0,0,0); vec3 nvn = normalize(vary_normal); - frag_data[2] = vec4(pack(nvn),0.0); + frag_data[2] = vec4(encode_normal(nvn.xyz), 0.0, 0.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/treeF.glsl b/indra/newview/app_settings/shaders/class1/deferred/treeF.glsl index 6cd309fdb..808750496 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/treeF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/treeF.glsl @@ -37,15 +37,10 @@ VARYING vec2 vary_texcoord0; uniform float minimum_alpha; -vec3 pack(vec3 norm) +vec2 encode_normal(vec3 n) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - float p = sqrt(8.0*norm.z+8.0); - return vec3(norm.xy/p + 0.5, 0.0); -#else - return norm.xyz*0.5+0.5; -#endif + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; } void main() @@ -59,5 +54,5 @@ void main() frag_data[0] = vec4(vertex_color.rgb*col.rgb, 0.0); frag_data[1] = vec4(0,0,0,0); vec3 nvn = normalize(vary_normal); - frag_data[2] = vec4(pack(nvn),0.0); + frag_data[2] = vec4(encode_normal(nvn.xyz), 0.0, 0.0); } diff --git a/indra/newview/app_settings/shaders/class1/deferred/underWaterF.glsl b/indra/newview/app_settings/shaders/class1/deferred/underWaterF.glsl new file mode 100644 index 000000000..78f841c73 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/underWaterF.glsl @@ -0,0 +1,157 @@ +/** + * @file underWaterF.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifdef DEFINE_GL_FRAGCOLOR +out vec4 frag_data[3]; +#else +#define frag_data gl_FragData +#endif + +uniform sampler2D diffuseMap; +uniform sampler2D bumpMap; +uniform sampler2D screenTex; +uniform sampler2D refTex; +uniform sampler2D screenDepth; + +uniform vec4 fogCol; +uniform vec3 lightDir; +uniform vec3 specular; +uniform float lightExp; +uniform vec2 fbScale; +uniform float refScale; +uniform float znear; +uniform float zfar; +uniform float kd; +uniform vec4 waterPlane; +uniform vec3 eyeVec; +uniform vec4 waterFogColor; +uniform float waterFogDensity; +uniform float waterFogKS; +uniform vec2 screenRes; + +//bigWave is (refCoord.w, view.w); +VARYING vec4 refCoord; +VARYING vec4 littleWave; +VARYING vec4 view; + +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lte); +#endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec4 applyWaterFog(vec4 color, vec3 viewVec) +{ + //normalize view vector + vec3 view = normalize(viewVec); + float es = -view.z; + + //find intersection point with water plane and eye vector + + //get eye depth + float e0 = max(-waterPlane.w, 0.0); + + //get object depth + float depth = length(viewVec); + + //get "thickness" of water + float l = max(depth, 0.1); + + float kd = waterFogDensity; + float ks = waterFogKS; + vec4 kc = waterFogColor; + + float F = 0.98; + + float t1 = -kd * pow(F, ks * e0); + float t2 = kd + ks * es; + float t3 = pow(F, t2*l) - 1.0; + + float L = min(t1/t2*t3, 1.0); + + float D = pow(0.98, l*kd); + //return vec4(1.0, 0.0, 1.0, 1.0); + return color * D + kc * L; + //depth /= 10.0; + //return vec4(depth,depth,depth,0.0); +} + +void main() +{ + vec4 color; + + //get detail normals + vec3 wave1 = texture2D(bumpMap, vec2(refCoord.w, view.w)).xyz*2.0-1.0; + vec3 wave2 = texture2D(bumpMap, littleWave.xy).xyz*2.0-1.0; + vec3 wave3 = texture2D(bumpMap, littleWave.zw).xyz*2.0-1.0; + vec3 wavef = normalize(wave1+wave2+wave3); + + //figure out distortion vector (ripply) + vec2 distort = (refCoord.xy/refCoord.z) * 0.5 + 0.5; + distort = distort+wavef.xy*refScale; + + vec4 fb = texture2D(screenTex, distort); + + frag_data[0] = vec4(linear_to_srgb(fb.rgb), 1.0); // diffuse + frag_data[1] = vec4(0.5,0.5,0.5, 0.95); // speccolor*spec, spec + frag_data[2] = vec4(encode_normal(wavef), 0.0, 0.0); // normalxyz, displace +} diff --git a/indra/newview/app_settings/shaders/class1/deferred/waterF.glsl b/indra/newview/app_settings/shaders/class1/deferred/waterF.glsl index b27822814..4b758772a 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/waterF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/waterF.glsl @@ -67,15 +67,47 @@ VARYING vec4 littleWave; VARYING vec4 view; VARYING vec4 vary_position; -vec3 pack(vec3 norm) +vec3 srgb_to_linear(vec3 cs) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - float p = sqrt(8.0*norm.z+8.0); - return vec3(norm.xy/p + 0.5, 0.0); + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; #else - return norm.xyz*0.5+0.5; + return mix(high_range, low_range, lte); #endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; } void main() @@ -126,7 +158,7 @@ void main() refcol *= df1 * 0.333; vec3 wavef = (wave1 + wave2 * 0.4 + wave3 * 0.6) * 0.5; - //wavef.z *= max(-viewVec.z, 0.1); + wavef.z *= max(-viewVec.z, 0.1); wavef = normalize(wavef); float df2 = dot(viewVec, wavef) * fresnelScale+fresnelOffset; @@ -136,13 +168,14 @@ void main() vec2 refvec4 = distort+refdistort4/dmod; float dweight = min(dist2*blurMultiplier, 1.0); vec4 baseCol = texture2D(refTex, refvec4); + refcol = mix(baseCol*df2, refcol, dweight); //get specular component - //float spec = clamp(dot(lightDir, (reflect(viewVec,wavef))),0.0,1.0); + float spec = clamp(dot(lightDir, (reflect(viewVec,wavef))),0.0,1.0); //harden specular - //spec = pow(spec, 128.0); + spec = pow(spec, 128.0); //figure out distortion vector (ripply) vec2 distort2 = distort+wavef.xy*refScale/max(dmod*df1, 1.0); @@ -153,25 +186,17 @@ void main() // Note we actually want to use just df1, but multiplying by 0.999999 gets around an nvidia compiler bug color.rgb = mix(fb.rgb, refcol.rgb, df1 * 0.99999); - float shadow = 1.0; vec4 pos = vary_position; - //vec3 nz = texture2D(noiseMap, gl_FragCoord.xy/128.0).xyz; - vec4 spos = pos; - - //spec *= shadow; - //color.rgb += spec * specular; + color.rgb += spec * specular; color.rgb = atmosTransport(color.rgb); color.rgb = scaleSoftClip(color.rgb); + color.a = spec * sunAngle2; - //color.a = spec * sunAngle2; - - //wavef.z *= 0.1f; - //wavef = normalize(wavef); - vec3 screenspacewavef = (norm_mat*vec4(wavef, 1.0)).xyz; + vec3 screenspacewavef = normalize((norm_mat*vec4(wavef, 1.0)).xyz); - frag_data[0] = vec4(color.rgb, 0.5); // diffuse - frag_data[1] = vec4(0.5,0.5,0.5, 0.95); // speccolor*spec, spec - frag_data[2] = vec4(pack(screenspacewavef), 0.5); + frag_data[0] = vec4(color.rgb, color.a); // diffuse + frag_data[1] = vec4(0); // speccolor, spec + frag_data[2] = vec4(encode_normal(screenspacewavef.xyz*0.5+0.5), 0.05, 0);// normalxy, 0, 0 } diff --git a/indra/newview/app_settings/shaders/class1/deferred/waterV.glsl b/indra/newview/app_settings/shaders/class1/deferred/waterV.glsl index ece34dcc4..9734acf00 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/waterV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/waterV.glsl @@ -85,7 +85,7 @@ void main() pos.w = 1.0; pos = modelview_matrix*pos; - calcAtmospherics(view.xyz); + calcAtmospherics(pos.xyz); //pass wave parameters to pixel shader vec2 bigWave = (v.xy) * vec2(0.04,0.04) + d1 * time * 0.055; diff --git a/indra/newview/app_settings/shaders/class1/environment/waterV.glsl b/indra/newview/app_settings/shaders/class1/environment/waterV.glsl index f77edde70..df65cb953 100644 --- a/indra/newview/app_settings/shaders/class1/environment/waterV.glsl +++ b/indra/newview/app_settings/shaders/class1/environment/waterV.glsl @@ -48,40 +48,40 @@ float wave(vec2 v, float t, float f, vec2 d, float s) void main() { //transform vertex + vec4 pos = vec4(position.xyz, 1.0); mat4 modelViewProj = modelview_projection_matrix; vec4 oPosition; //get view vector vec3 oEyeVec; - oEyeVec.xyz = position.xyz-eyeVec; + oEyeVec.xyz = pos.xyz-eyeVec; float d = length(oEyeVec.xy); float ld = min(d, 2560.0); - vec3 lpos = position; - lpos.xy = eyeVec.xy + oEyeVec.xy/d*ld; + pos.xy = eyeVec.xy + oEyeVec.xy/d*ld; view.xyz = oEyeVec; d = clamp(ld/1536.0-0.5, 0.0, 1.0); d *= d; - oPosition = vec4(lpos, 1.0); + oPosition = vec4(position, 1.0); oPosition.z = mix(oPosition.z, max(eyeVec.z*0.75, 0.0), d); oPosition = modelViewProj * oPosition; + refCoord.xyz = oPosition.xyz + vec3(0,0,0.2); //get wave position parameter (create sweeping horizontal waves) - vec3 v = lpos; + vec3 v = pos.xyz; v.x += (cos(v.x*0.08/*+time*0.01*/)+sin(v.y*0.02))*6.0; //push position for further horizon effect. - vec4 pos; pos.xyz = oEyeVec.xyz*(waterHeight/oEyeVec.z); pos.w = 1.0; pos = modelview_matrix*pos; - calcAtmospherics(view.xyz); + calcAtmospherics(pos.xyz); //pass wave parameters to pixel shader diff --git a/indra/newview/app_settings/shaders/class1/interface/downsampleDepthF.glsl b/indra/newview/app_settings/shaders/class1/interface/downsampleDepthF.glsl index 6523a06d2..f8efd7cb4 100644 --- a/indra/newview/app_settings/shaders/class1/interface/downsampleDepthF.glsl +++ b/indra/newview/app_settings/shaders/class1/interface/downsampleDepthF.glsl @@ -31,8 +31,6 @@ out vec4 frag_color; uniform sampler2D depthMap; -uniform float delta; - VARYING vec2 tc0; VARYING vec2 tc1; VARYING vec2 tc2; diff --git a/indra/newview/app_settings/shaders/class1/interface/downsampleDepthRectF.glsl b/indra/newview/app_settings/shaders/class1/interface/downsampleDepthRectF.glsl index 0e5dc0818..942c5888e 100644 --- a/indra/newview/app_settings/shaders/class1/interface/downsampleDepthRectF.glsl +++ b/indra/newview/app_settings/shaders/class1/interface/downsampleDepthRectF.glsl @@ -33,8 +33,6 @@ out vec4 frag_color; uniform sampler2DRect depthMap; -uniform float delta; - VARYING vec2 tc0; VARYING vec2 tc1; VARYING vec2 tc2; diff --git a/indra/newview/app_settings/shaders/class1/lighting/lightAlphaMaskNonIndexedF.glsl b/indra/newview/app_settings/shaders/class1/lighting/lightAlphaMaskNonIndexedF.glsl index 4070d41f4..b9ddbc8e1 100644 --- a/indra/newview/app_settings/shaders/class1/lighting/lightAlphaMaskNonIndexedF.glsl +++ b/indra/newview/app_settings/shaders/class1/lighting/lightAlphaMaskNonIndexedF.glsl @@ -41,13 +41,15 @@ VARYING vec2 vary_texcoord0; void default_lighting() { - vec4 color = texture2D(diffuseMap,vary_texcoord0.xy) * vertex_color; + vec4 color = texture2D(diffuseMap,vary_texcoord0.xy); if (color.a < minimum_alpha) { discard; } + color.rgb *= vertex_color.rgb; + color.rgb = atmosLighting(color.rgb); color.rgb = scaleSoftClip(color.rgb); diff --git a/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightAlphaMaskF.glsl b/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightAlphaMaskF.glsl index 6c34643aa..5740987ab 100644 --- a/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightAlphaMaskF.glsl +++ b/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightAlphaMaskF.glsl @@ -30,6 +30,7 @@ out vec4 frag_color; #endif uniform float minimum_alpha; +uniform float texture_gamma; vec3 fullbrightAtmosTransport(vec3 light); vec3 fullbrightScaleSoftClip(vec3 light); @@ -39,13 +40,16 @@ VARYING vec2 vary_texcoord0; void fullbright_lighting() { - vec4 color = diffuseLookup(vary_texcoord0.xy) * vertex_color; + vec4 color = diffuseLookup(vary_texcoord0.xy); if (color.a < minimum_alpha) { discard; } + color.rgb *= vertex_color.rgb; + + color.rgb = pow(color.rgb, vec3(texture_gamma)); color.rgb = fullbrightAtmosTransport(color.rgb); color.rgb = fullbrightScaleSoftClip(color.rgb); diff --git a/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightF.glsl b/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightF.glsl index 2ff7f795b..c8771a3f1 100644 --- a/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightF.glsl +++ b/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightF.glsl @@ -32,6 +32,8 @@ out vec4 frag_color; VARYING vec4 vertex_color; VARYING vec2 vary_texcoord0; +uniform float texture_gamma; + vec3 fullbrightAtmosTransport(vec3 light); vec3 fullbrightScaleSoftClip(vec3 light); @@ -39,10 +41,14 @@ void fullbright_lighting() { vec4 color = diffuseLookup(vary_texcoord0.xy) * vertex_color; + color.rgb = pow(color.rgb, vec3(texture_gamma)); + color.rgb = fullbrightAtmosTransport(color.rgb); color.rgb = fullbrightScaleSoftClip(color.rgb); + color.rgb = pow(color.rgb, vec3(1.0/texture_gamma)); + frag_color = color; } diff --git a/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightNonIndexedAlphaMaskF.glsl b/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightNonIndexedAlphaMaskF.glsl index f4477bd29..f72f20b03 100644 --- a/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightNonIndexedAlphaMaskF.glsl +++ b/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightNonIndexedAlphaMaskF.glsl @@ -30,6 +30,7 @@ out vec4 frag_color; #endif uniform float minimum_alpha; +uniform float texture_gamma; vec3 fullbrightAtmosTransport(vec3 light); vec3 fullbrightScaleSoftClip(vec3 light); @@ -41,17 +42,22 @@ VARYING vec2 vary_texcoord0; void fullbright_lighting() { - vec4 color = texture2D(diffuseMap,vary_texcoord0.xy) * vertex_color; + vec4 color = texture2D(diffuseMap,vary_texcoord0.xy); if (color.a < minimum_alpha) { discard; } + + color.rgb *= vertex_color.rgb; + color.rgb = pow(color.rgb, vec3(texture_gamma)); color.rgb = fullbrightAtmosTransport(color.rgb); color.rgb = fullbrightScaleSoftClip(color.rgb); + color.rgb = pow(color.rgb, vec3(1.0/texture_gamma)); + frag_color = color; } diff --git a/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightWaterAlphaMaskF.glsl b/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightWaterAlphaMaskF.glsl index 99a6fe85f..9c82056fd 100644 --- a/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightWaterAlphaMaskF.glsl +++ b/indra/newview/app_settings/shaders/class1/lighting/lightFullbrightWaterAlphaMaskF.glsl @@ -31,7 +31,7 @@ out vec4 frag_color; uniform float minimum_alpha; -vec4 diffuseLookup(vec2 texcoord); +/* vec4 diffuseLookup(vec2 texcoord); */ vec3 fullbrightAtmosTransport(vec3 light); vec4 applyWaterFog(vec4 color); diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl deleted file mode 100644 index 8db4cb58c..000000000 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl +++ /dev/null @@ -1,168 +0,0 @@ -/** - * @file alphaF.glsl - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#extension GL_ARB_texture_rectangle : enable - -#ifdef DEFINE_GL_FRAGCOLOR -out vec4 frag_color; -#else -#define frag_color gl_FragColor -#endif - -VARYING vec4 vertex_color; -VARYING vec2 vary_texcoord0; - -uniform sampler2DRectShadow shadowMap0; -uniform sampler2DRectShadow shadowMap1; -uniform sampler2DRectShadow shadowMap2; -uniform sampler2DRectShadow shadowMap3; -uniform sampler2DRect depthMap; - -uniform mat4 shadow_matrix[6]; -uniform vec4 shadow_clip; -uniform vec2 screen_res; -uniform vec2 shadow_res; - -vec3 atmosLighting(vec3 light); -vec3 scaleSoftClip(vec3 light); - -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; -VARYING vec3 vary_fragcoord; -VARYING vec3 vary_position; -VARYING vec3 vary_pointlight_col; - -uniform float shadow_bias; - -uniform mat4 inv_proj; - -float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) -{ - stc.xyz /= stc.w; - stc.z += shadow_bias; - - stc.x = floor(stc.x + fract(stc.y*12345)); // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here - - float cs = shadow2DRect(shadowMap, stc.xyz).x; - float shadow = cs; - - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x; - - return shadow*0.2; -} - - -void main() -{ - vec2 frag = vary_fragcoord.xy/vary_fragcoord.z*0.5+0.5; - frag *= screen_res; - - float shadow = 0.0; - vec4 pos = vec4(vary_position, 1.0); - - vec4 spos = pos; - - if (spos.z > -shadow_clip.w) - { - vec4 lpos; - - vec4 near_split = shadow_clip*-0.75; - vec4 far_split = shadow_clip*-1.25; - vec4 transition_domain = near_split-far_split; - float weight = 0.0; - - if (spos.z < near_split.z) - { - lpos = shadow_matrix[3]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap3, lpos)*w; - weight += w; - shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0); - } - - if (spos.z < near_split.y && spos.z > far_split.z) - { - lpos = shadow_matrix[2]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; - w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap2, lpos)*w; - weight += w; - } - - if (spos.z < near_split.x && spos.z > far_split.y) - { - lpos = shadow_matrix[1]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; - w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; - shadow += pcfShadow(shadowMap1, lpos)*w; - weight += w; - } - - if (spos.z > far_split.x) - { - lpos = shadow_matrix[0]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; - - shadow += pcfShadow(shadowMap0, lpos)*w; - weight += w; - } - - - shadow /= weight; - } - else - { - shadow = 1.0; - } - - vec4 diff = diffuseLookup(vary_texcoord0.xy); - - vec4 col = vec4(vary_ambient + vary_directional.rgb*shadow, vertex_color.a); - vec4 color = diff * col; - - color.rgb = atmosLighting(color.rgb); - - color.rgb = scaleSoftClip(color.rgb); - - color.rgb += diff.rgb * vary_pointlight_col.rgb; - - frag_color = color; -} - diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl deleted file mode 100644 index 33958a501..000000000 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedF.glsl +++ /dev/null @@ -1,182 +0,0 @@ -/** - * @file alphaF.glsl - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#extension GL_ARB_texture_rectangle : enable - -#ifdef DEFINE_GL_FRAGCOLOR -out vec4 frag_color; -#else -#define frag_color gl_FragColor -#endif - -uniform sampler2DRectShadow shadowMap0; -uniform sampler2DRectShadow shadowMap1; -uniform sampler2DRectShadow shadowMap2; -uniform sampler2DRectShadow shadowMap3; -uniform sampler2DRect depthMap; -uniform sampler2D diffuseMap; - -uniform mat4 shadow_matrix[6]; -uniform vec4 shadow_clip; -uniform vec2 screen_res; -uniform vec2 shadow_res; - -vec3 atmosLighting(vec3 light); -vec3 scaleSoftClip(vec3 light); - -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; -VARYING vec3 vary_fragcoord; -VARYING vec3 vary_position; -VARYING vec3 vary_pointlight_col; -VARYING vec2 vary_texcoord0; -VARYING vec4 vertex_color; - -uniform float shadow_bias; - -uniform mat4 inv_proj; - -vec4 getPosition(vec2 pos_screen) -{ - float depth = texture2DRect(depthMap, pos_screen.xy).a; - vec2 sc = pos_screen.xy*2.0; - sc /= screen_res; - sc -= vec2(1.0,1.0); - vec4 ndc = vec4(sc.x, sc.y, 2.0*depth-1.0, 1.0); - vec4 pos = inv_proj * ndc; - pos.xyz /= pos.w; - pos.w = 1.0; - return pos; -} - -float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) -{ - stc.xyz /= stc.w; - stc.z += shadow_bias; - - stc.x = floor(stc.x + fract(stc.y*12345)); // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here - - float cs = shadow2DRect(shadowMap, stc.xyz).x; - float shadow = cs; - - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x; - - return shadow*0.2; -} - - -void main() -{ - vec2 frag = vary_fragcoord.xy/vary_fragcoord.z*0.5+0.5; - frag *= screen_res; - - float shadow = 0.0; - vec4 pos = vec4(vary_position, 1.0); - - vec4 spos = pos; - - if (spos.z > -shadow_clip.w) - { - vec4 lpos; - - vec4 near_split = shadow_clip*-0.75; - vec4 far_split = shadow_clip*-1.25; - vec4 transition_domain = near_split-far_split; - float weight = 0.0; - - if (spos.z < near_split.z) - { - lpos = shadow_matrix[3]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap3, lpos)*w; - weight += w; - shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0); - } - - if (spos.z < near_split.y && spos.z > far_split.z) - { - lpos = shadow_matrix[2]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; - w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap2, lpos)*w; - weight += w; - } - - if (spos.z < near_split.x && spos.z > far_split.y) - { - lpos = shadow_matrix[1]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; - w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; - shadow += pcfShadow(shadowMap1, lpos)*w; - weight += w; - } - - if (spos.z > far_split.x) - { - lpos = shadow_matrix[0]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; - - shadow += pcfShadow(shadowMap0, lpos)*w; - weight += w; - } - - - shadow /= weight; - - } - else - { - shadow = 1.0; - } - - vec4 diff = texture2D(diffuseMap,vary_texcoord0.xy); - - vec4 col = vec4(vary_ambient + vary_directional.rgb*shadow, vertex_color.a); - vec4 color = diff * col; - - color.rgb = atmosLighting(color.rgb); - - color.rgb = scaleSoftClip(color.rgb); - - color.rgb += diff.rgb * vary_pointlight_col.rgb; - - frag_color = color; -} - diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl deleted file mode 100644 index 2093fc37d..000000000 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaNonIndexedNoColorF.glsl +++ /dev/null @@ -1,187 +0,0 @@ -/** - * @file alphaNonIndexedNoColorF.glsl - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2005, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#extension GL_ARB_texture_rectangle : enable - -#ifdef DEFINE_GL_FRAGCOLOR -out vec4 frag_color; -#else -#define frag_color gl_FragColor -#endif - -uniform float minimum_alpha; - -uniform sampler2DRectShadow shadowMap0; -uniform sampler2DRectShadow shadowMap1; -uniform sampler2DRectShadow shadowMap2; -uniform sampler2DRectShadow shadowMap3; -uniform sampler2DRect depthMap; -uniform sampler2D diffuseMap; - -uniform mat4 shadow_matrix[6]; -uniform vec4 shadow_clip; -uniform vec2 screen_res; -uniform vec2 shadow_res; - -vec3 atmosLighting(vec3 light); -vec3 scaleSoftClip(vec3 light); - -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; -VARYING vec3 vary_fragcoord; -VARYING vec3 vary_position; -VARYING vec3 vary_pointlight_col; -VARYING vec2 vary_texcoord0; - -uniform float shadow_bias; - -uniform mat4 inv_proj; - -vec4 getPosition(vec2 pos_screen) -{ - float depth = texture2DRect(depthMap, pos_screen.xy).a; - vec2 sc = pos_screen.xy*2.0; - sc /= screen_res; - sc -= vec2(1.0,1.0); - vec4 ndc = vec4(sc.x, sc.y, 2.0*depth-1.0, 1.0); - vec4 pos = inv_proj * ndc; - pos.xyz /= pos.w; - pos.w = 1.0; - return pos; -} - -float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc) -{ - stc.xyz /= stc.w; - stc.z += shadow_bias; - - stc.x = floor(stc.x + fract(stc.y*12345)); // add some chaotic jitter to X sample pos according to Y to disguise the snapping going on here - - float cs = shadow2DRect(shadowMap, stc.xyz).x; - float shadow = cs; - - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x; - - return shadow*0.2; -} - - -void main() -{ - vec2 frag = vary_fragcoord.xy/vary_fragcoord.z*0.5+0.5; - frag *= screen_res; - - float shadow = 0.0; - vec4 pos = vec4(vary_position, 1.0); - - vec4 diff = texture2D(diffuseMap,vary_texcoord0.xy); - - if (diff.a < minimum_alpha) - { - discard; - } - - vec4 spos = pos; - - if (spos.z > -shadow_clip.w) - { - vec4 lpos; - - vec4 near_split = shadow_clip*-0.75; - vec4 far_split = shadow_clip*-1.25; - vec4 transition_domain = near_split-far_split; - float weight = 0.0; - - if (spos.z < near_split.z) - { - lpos = shadow_matrix[3]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap3, lpos)*w; - weight += w; - shadow += max((pos.z+shadow_clip.z)/(shadow_clip.z-shadow_clip.w)*2.0-1.0, 0.0); - } - - if (spos.z < near_split.y && spos.z > far_split.z) - { - lpos = shadow_matrix[2]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; - w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; - shadow += pcfShadow(shadowMap2, lpos)*w; - weight += w; - } - - if (spos.z < near_split.x && spos.z > far_split.y) - { - lpos = shadow_matrix[1]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; - w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; - shadow += pcfShadow(shadowMap1, lpos)*w; - weight += w; - } - - if (spos.z > far_split.x) - { - lpos = shadow_matrix[0]*spos; - lpos.xy *= shadow_res; - - float w = 1.0; - w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; - - shadow += pcfShadow(shadowMap0, lpos)*w; - weight += w; - } - - - shadow /= weight; - } - else - { - shadow = 1.0; - } - - vec4 col = vec4(vary_ambient + vary_directional.rgb*shadow, 1.0); - vec4 color = diff * col; - - color.rgb = atmosLighting(color.rgb); - - color.rgb = scaleSoftClip(color.rgb); - - color.rgb += diff.rgb * vary_pointlight_col.rgb; - - frag_color = color; -} - diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaSkinnedV.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaSkinnedV.glsl deleted file mode 100644 index 9629cfe82..000000000 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaSkinnedV.glsl +++ /dev/null @@ -1,155 +0,0 @@ -/** - * @file alphaSkinnedV.glsl - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -uniform mat4 projection_matrix; -uniform mat4 texture_matrix0; -uniform mat4 modelview_matrix; -uniform mat4 modelview_projection_matrix; - -ATTRIBUTE vec3 position; -ATTRIBUTE vec3 normal; -ATTRIBUTE vec4 diffuse_color; -ATTRIBUTE vec2 texcoord0; - -vec4 calcLighting(vec3 pos, vec3 norm, vec4 color, vec4 baseCol); -void calcAtmospherics(vec3 inPositionEye); - -float calcDirectionalLight(vec3 n, vec3 l); -mat4 getObjectSkinnedTransform(); -vec3 atmosAmbient(vec3 light); -vec3 atmosAffectDirectionalLight(float lightIntensity); -vec3 scaleDownLight(vec3 light); -vec3 scaleUpLight(vec3 light); - -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; -VARYING vec3 vary_fragcoord; -VARYING vec3 vary_position; -VARYING vec3 vary_pointlight_col; - -VARYING vec4 vertex_color; -VARYING vec2 vary_texcoord0; - - -uniform float near_clip; -uniform float shadow_offset; -uniform float shadow_bias; - -uniform vec4 light_position[8]; -uniform vec3 light_direction[8]; -uniform vec3 light_attenuation[8]; -uniform vec3 light_diffuse[8]; - -float calcDirectionalLight(vec3 n, vec3 l) -{ - float a = max(dot(n,l),0.0); - return a; -} - -float calcPointLightOrSpotLight(vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float fa, float is_pointlight) -{ -//get light vector - vec3 lv = lp.xyz-v; - - //get distance - float d = dot(lv,lv); - - float da = 0.0; - - if (d > 0.0 && la > 0.0 && fa > 0.0) - { - //normalize light vector - lv = normalize(lv); - - //distance attenuation - float dist2 = d/la; - da = clamp(1.0-(dist2-1.0*(1.0-fa))/fa, 0.0, 1.0); - - // spotlight coefficient. - float spot = max(dot(-ln, lv), is_pointlight); - da *= spot*spot; // GL_SPOT_EXPONENT=2 - - //angular attenuation - da *= max(dot(n, lv), 0.0); - } - - return da; -} - -void main() -{ - vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy; - - mat4 mat = getObjectSkinnedTransform(); - - mat = modelview_matrix * mat; - - vec3 pos = (mat*vec4(position, 1.0)).xyz; - - gl_Position = projection_matrix * vec4(pos, 1.0); - - vec4 n = vec4(position, 1.0); - n.xyz += normal.xyz; - n.xyz = (mat*n).xyz; - n.xyz = normalize(n.xyz-pos.xyz); - - vec3 norm = n.xyz; - - float dp_directional_light = max(0.0, dot(norm, light_position[0].xyz)); - vary_position = pos.xyz + light_position[0].xyz * (1.0-dp_directional_light)*shadow_offset; - - calcAtmospherics(pos.xyz); - - //vec4 color = calcLighting(pos.xyz, norm, diffuse_color, vec4(0.)); - vec4 col = vec4(0.0, 0.0, 0.0, diffuse_color.a); - - // Collect normal lights - col.rgb += light_diffuse[2].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[2], light_direction[2], light_attenuation[2].x, light_attenuation[2].y, light_attenuation[2].z); - col.rgb += light_diffuse[3].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[3], light_direction[3], light_attenuation[3].x, light_attenuation[3].y, light_attenuation[3].z); - col.rgb += light_diffuse[4].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[4], light_direction[4], light_attenuation[4].x, light_attenuation[4].y, light_attenuation[4].z); - col.rgb += light_diffuse[5].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[5], light_direction[5], light_attenuation[5].x, light_attenuation[5].y, light_attenuation[5].z); - col.rgb += light_diffuse[6].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[6], light_direction[6], light_attenuation[6].x, light_attenuation[6].y, light_attenuation[6].z); - col.rgb += light_diffuse[7].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[7], light_direction[7], light_attenuation[7].x, light_attenuation[7].y, light_attenuation[7].z); - - vary_pointlight_col = col.rgb*diffuse_color.rgb; - - col.rgb = vec3(0,0,0); - - // Add windlight lights - col.rgb = atmosAmbient(vec3(0.)); - - vary_ambient = col.rgb*diffuse_color.rgb; - vary_directional.rgb = diffuse_color.rgb*atmosAffectDirectionalLight(max(calcDirectionalLight(norm, light_position[0].xyz), (1.0-diffuse_color.a)*(1.0-diffuse_color.a))); - - col.rgb = min(col.rgb*diffuse_color.rgb, 1.0); - - vertex_color = col; - - - - pos.xyz = (modelview_projection_matrix * vec4(position.xyz, 1.0)).xyz; - vary_fragcoord.xyz = pos.xyz + vec3(0,0,near_clip); - -} - diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaV.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaV.glsl deleted file mode 100644 index 1586aab0f..000000000 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaV.glsl +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @file alphaV.glsl - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -uniform mat3 normal_matrix; -uniform mat4 texture_matrix0; -uniform mat4 modelview_matrix; -uniform mat4 modelview_projection_matrix; - -ATTRIBUTE vec3 position; -void passTextureIndex(); -ATTRIBUTE vec3 normal; -ATTRIBUTE vec4 diffuse_color; -ATTRIBUTE vec2 texcoord0; - -vec4 calcLighting(vec3 pos, vec3 norm, vec4 color, vec4 baseCol); -void calcAtmospherics(vec3 inPositionEye); - -float calcDirectionalLight(vec3 n, vec3 l); - -vec3 atmosAmbient(vec3 light); -vec3 atmosAffectDirectionalLight(float lightIntensity); -vec3 scaleDownLight(vec3 light); -vec3 scaleUpLight(vec3 light); - -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; -VARYING vec3 vary_fragcoord; -VARYING vec3 vary_position; -VARYING vec3 vary_pointlight_col; - -VARYING vec4 vertex_color; -VARYING vec2 vary_texcoord0; - - -uniform float near_clip; -uniform float shadow_offset; -uniform float shadow_bias; - -uniform vec4 light_position[8]; -uniform vec3 light_direction[8]; -uniform vec3 light_attenuation[8]; -uniform vec3 light_diffuse[8]; - -float calcDirectionalLight(vec3 n, vec3 l) -{ - float a = max(dot(n,l),0.0); - return a; -} - -float calcPointLightOrSpotLight(vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float fa, float is_pointlight) -{ - //get light vector - vec3 lv = lp.xyz-v; - - //get distance - float d = dot(lv,lv); - - float da = 0.0; - - if (d > 0.0 && la > 0.0 && fa > 0.0) - { - //normalize light vector - lv = normalize(lv); - - //distance attenuation - float dist2 = d/la; - da = clamp(1.0-(dist2-1.0*(1.0-fa))/fa, 0.0, 1.0); - - // spotlight coefficient. - float spot = max(dot(-ln, lv), is_pointlight); - da *= spot*spot; // GL_SPOT_EXPONENT=2 - - //angular attenuation - da *= max(dot(n, lv), 0.0); - } - - return da; -} - -void main() -{ - //transform vertex - vec4 vert = vec4(position.xyz, 1.0); - passTextureIndex(); - vec4 pos = (modelview_matrix * vert); - gl_Position = modelview_projection_matrix*vec4(position.xyz, 1.0); - - vary_texcoord0 = (texture_matrix0 * vec4(texcoord0,0,1)).xy; - - vec3 norm = normalize(normal_matrix * normal); - - float dp_directional_light = max(0.0, dot(norm, light_position[0].xyz)); - vary_position = pos.xyz + light_position[0].xyz * (1.0-dp_directional_light)*shadow_offset; - - calcAtmospherics(pos.xyz); - - //vec4 color = calcLighting(pos.xyz, norm, diffuse_color, vec4(0.)); - vec4 col = vec4(0.0, 0.0, 0.0, diffuse_color.a); - - - // Collect normal lights - col.rgb += light_diffuse[2].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[2], light_direction[2], light_attenuation[2].x, light_attenuation[2].y, light_attenuation[2].z); - col.rgb += light_diffuse[3].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[3], light_direction[3], light_attenuation[3].x, light_attenuation[3].y, light_attenuation[3].z); - col.rgb += light_diffuse[4].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[4], light_direction[4], light_attenuation[4].x, light_attenuation[4].y, light_attenuation[4].z); - col.rgb += light_diffuse[5].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[5], light_direction[5], light_attenuation[5].x, light_attenuation[5].y, light_attenuation[5].z); - col.rgb += light_diffuse[6].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[6], light_direction[6], light_attenuation[6].x, light_attenuation[6].y, light_attenuation[6].z); - col.rgb += light_diffuse[7].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[7], light_direction[7], light_attenuation[7].x, light_attenuation[7].y, light_attenuation[7].z); - - vary_pointlight_col = col.rgb*diffuse_color.rgb; - - col.rgb = vec3(0,0,0); - - // Add windlight lights - col.rgb = atmosAmbient(vec3(0.)); - - vary_ambient = col.rgb*diffuse_color.rgb; - vary_directional.rgb = diffuse_color.rgb*atmosAffectDirectionalLight(max(calcDirectionalLight(norm, light_position[0].xyz), (1.0-diffuse_color.a)*(1.0-diffuse_color.a))); - - col.rgb = col.rgb*diffuse_color.rgb; - - vertex_color = col; - - - - pos = modelview_projection_matrix * vert; - vary_fragcoord.xyz = pos.xyz + vec3(0,0,near_clip); - -} diff --git a/indra/newview/app_settings/shaders/class2/deferred/avatarAlphaV.glsl b/indra/newview/app_settings/shaders/class2/deferred/avatarAlphaV.glsl deleted file mode 100644 index 44aaa98b9..000000000 --- a/indra/newview/app_settings/shaders/class2/deferred/avatarAlphaV.glsl +++ /dev/null @@ -1,153 +0,0 @@ -/** - * @file avatarAlphaV.glsl - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2007, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -uniform mat4 projection_matrix; - -ATTRIBUTE vec3 position; -ATTRIBUTE vec3 normal; -ATTRIBUTE vec2 texcoord0; - -vec4 calcLighting(vec3 pos, vec3 norm, vec4 color, vec4 baseCol); -mat4 getSkinnedTransform(); -void calcAtmospherics(vec3 inPositionEye); - -float calcDirectionalLight(vec3 n, vec3 l); -float calcPointLightOrSpotLight(vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float is_pointlight); - -vec3 atmosAmbient(vec3 light); -vec3 atmosAffectDirectionalLight(float lightIntensity); -vec3 scaleDownLight(vec3 light); -vec3 scaleUpLight(vec3 light); - -VARYING vec3 vary_position; -VARYING vec3 vary_ambient; -VARYING vec3 vary_directional; -VARYING vec3 vary_fragcoord; -VARYING vec3 vary_pointlight_col; -VARYING vec4 vertex_color; -VARYING vec2 vary_texcoord0; - -uniform vec4 color; - -uniform float near_clip; -uniform float shadow_offset; -uniform float shadow_bias; - -uniform vec4 light_position[8]; -uniform vec3 light_direction[8]; -uniform vec3 light_attenuation[8]; -uniform vec3 light_diffuse[8]; - -float calcDirectionalLight(vec3 n, vec3 l) -{ - float a = max(dot(n,l),0.0); - return a; -} - -float calcPointLightOrSpotLight(vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float fa, float is_pointlight) -{ - //get light vector - vec3 lv = lp.xyz-v; - - //get distance - float d = dot(lv,lv); - - float da = 0.0; - - if (d > 0.0 && la > 0.0 && fa > 0.0) - { - //normalize light vector - lv = normalize(lv); - - //distance attenuation - float dist2 = d/la; - da = clamp(1.0-(dist2-1.0*(1.0-fa))/fa, 0.0, 1.0); - - // spotlight coefficient. - float spot = max(dot(-ln, lv), is_pointlight); - da *= spot*spot; // GL_SPOT_EXPONENT=2 - - //angular attenuation - da *= max(dot(n, lv), 0.0); - } - - return da; -} - -void main() -{ - vary_texcoord0 = texcoord0; - - vec4 pos; - vec3 norm; - - mat4 trans = getSkinnedTransform(); - vec4 pos_in = vec4(position.xyz, 1.0); - pos.x = dot(trans[0], pos_in); - pos.y = dot(trans[1], pos_in); - pos.z = dot(trans[2], pos_in); - pos.w = 1.0; - - norm.x = dot(trans[0].xyz, normal); - norm.y = dot(trans[1].xyz, normal); - norm.z = dot(trans[2].xyz, normal); - norm = normalize(norm); - - gl_Position = projection_matrix * pos; - - float dp_directional_light = max(0.0, dot(norm, light_position[0].xyz)); - vary_position = pos.xyz + light_position[0].xyz * (1.0-dp_directional_light)*shadow_offset; - - calcAtmospherics(pos.xyz); - - vec4 col = vec4(0.0, 0.0, 0.0, 1.0); - - // Collect normal lights - col.rgb += light_diffuse[2].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[2], light_direction[2], light_attenuation[2].x, light_attenuation[2].y, light_attenuation[2].z); - col.rgb += light_diffuse[3].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[3], light_direction[3], light_attenuation[3].x, light_attenuation[3].y, light_attenuation[3].z); - col.rgb += light_diffuse[4].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[4], light_direction[4], light_attenuation[4].x, light_attenuation[4].y, light_attenuation[4].z); - col.rgb += light_diffuse[5].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[5], light_direction[5], light_attenuation[5].x, light_attenuation[5].y, light_attenuation[5].z); - col.rgb += light_diffuse[6].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[6], light_direction[6], light_attenuation[6].x, light_attenuation[6].y, light_attenuation[6].z); - col.rgb += light_diffuse[7].rgb*calcPointLightOrSpotLight(pos.xyz, norm, light_position[7], light_direction[7], light_attenuation[7].x, light_attenuation[7].y, light_attenuation[7].z); - - vary_pointlight_col = col.rgb*color.rgb; - - col.rgb = vec3(0,0,0); - - // Add windlight lights - col.rgb = atmosAmbient(vec3(0.)); - - vary_ambient = col.rgb*color.rgb; - vary_directional = atmosAffectDirectionalLight(max(calcDirectionalLight(norm, light_position[0].xyz), 0.0)); - - col.rgb = col.rgb*color.rgb; - - vertex_color = col; - - - vary_fragcoord.xyz = pos.xyz + vec3(0,0,near_clip); -} - - diff --git a/indra/newview/app_settings/shaders/class2/deferred/multiSpotLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/multiSpotLightF.glsl index 7f6a31ef7..ac5b7d676 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/multiSpotLightF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/multiSpotLightF.glsl @@ -24,6 +24,7 @@ */ #extension GL_ARB_texture_rectangle : enable +#extension GL_ARB_shader_texture_lod : enable #ifdef DEFINE_GL_FRAGCOLOR out vec4 frag_color; @@ -39,6 +40,7 @@ uniform samplerCube environmentMap; uniform sampler2DRect lightMap; uniform sampler2D noiseMap; uniform sampler2D projectionMap; +uniform sampler2D lightFunc; uniform mat4 proj_mat; //screen space to light space uniform float proj_near; //near clip for projection @@ -67,10 +69,70 @@ uniform vec2 screen_res; uniform mat4 inv_proj; +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lte); +#endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + +vec4 correctWithGamma(vec4 col) +{ + return vec4(srgb_to_linear(col.rgb), col.a); +} + vec4 texture2DLodSpecular(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); - + ret = correctWithGamma(ret); + vec2 dist = tc-vec2(0.5); float det = max(1.0-lod/(proj_lod*0.5), 0.0); @@ -85,7 +147,8 @@ vec4 texture2DLodSpecular(sampler2D projectionMap, vec2 tc, float lod) vec4 texture2DLodDiffuse(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); - + ret = correctWithGamma(ret); + vec2 dist = vec2(0.5) - abs(tc-vec2(0.5)); float det = min(lod/(proj_lod*0.5), 1.0); @@ -102,6 +165,7 @@ vec4 texture2DLodDiffuse(sampler2D projectionMap, vec2 tc, float lod) vec4 texture2DLodAmbient(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); + ret = correctWithGamma(ret); vec2 dist = tc-vec2(0.5); @@ -126,20 +190,6 @@ vec4 getPosition(vec2 pos_screen) return pos; } -vec3 unpack(vec2 tc) -{ -//#define PACK_NORMALS -#ifdef PACK_NORMALS - vec2 enc = texture2DRect(normalMap, tc).xy; - enc = enc*4.0-2.0; - float prod = dot(enc,enc); - return vec3(enc*sqrt(1.0-prod*.25),1.0-prod*.5); -#else - vec3 norm = texture2DRect(normalMap, tc).xyz; - return norm*2.0-1.0; -#endif -} - void main() { vec4 frag = vary_fragcoord; @@ -149,9 +199,9 @@ void main() vec3 pos = getPosition(frag.xy).xyz; vec3 lv = center.xyz-pos.xyz; - float dist2 = dot(lv,lv); - dist2 /= size; - if (dist2 > 1.0) + float dist = length(lv); + dist /= size; + if (dist > 1.0) { discard; } @@ -167,9 +217,13 @@ void main() shadow = min(sh[proj_shadow_idx]+shadow_fade, 1.0); } - vec3 norm = unpack(frag.xy); // unpack norm + vec3 norm = texture2DRect(normalMap, frag.xy).xyz; - //norm = normalize(norm); // may be superfluous + float envIntensity = norm.z; + + norm = decode_normal(norm.xy); + + norm = normalize(norm); float l_dist = -dot(lv, proj_n); vec4 proj_tc = (proj_mat * vec4(pos.xyz, 1.0)); @@ -181,7 +235,9 @@ void main() proj_tc.xyz /= proj_tc.w; float fa = falloff+1.0; - float dist_atten = min(1.0-(dist2-1.0*(1.0-fa))/fa, 1.0); + float dist_atten = min(1.0-(dist-1.0*(1.0-fa))/fa, 1.0); + dist_atten *= dist_atten; + dist_atten *= 2.0; if (dist_atten <= 0.0) { discard; @@ -190,11 +246,15 @@ void main() lv = proj_origin-pos.xyz; lv = normalize(lv); float da = dot(norm, lv); - + vec3 col = vec3(0,0,0); vec3 diff_tex = texture2DRect(diffuseRect, frag.xy).rgb; - + + vec4 spec = texture2DRect(specularRect, frag.xy); + + vec3 dlit = vec3(0, 0, 0); + float noise = texture2D(noiseMap, frag.xy/128.0).b; if (proj_tc.z > 0.0 && proj_tc.x < 1.0 && @@ -202,21 +262,21 @@ void main() proj_tc.x > 0.0 && proj_tc.y > 0.0) { - float lit = 0.0; float amb_da = proj_ambiance; - + float lit = 0.0; + if (da > 0.0) { + lit = da * dist_atten * noise; + float diff = clamp((l_dist-proj_focus)/proj_range, 0.0, 1.0); float lod = diff * proj_lod; vec4 plcol = texture2DLodDiffuse(projectionMap, proj_tc.xy, lod); - vec3 lcol = color.rgb * plcol.rgb * plcol.a; + dlit = color.rgb * plcol.rgb * plcol.a; - lit = da * dist_atten * noise; - - col = lcol*lit*diff_tex*shadow; + col = dlit*lit*diff_tex*shadow; amb_da += (da*0.5)*(1.0-shadow)*proj_ambiance; } @@ -232,9 +292,36 @@ void main() col += amb_da*color.rgb*diff_tex.rgb*amb_plcol.rgb*amb_plcol.a; } - - vec4 spec = texture2DRect(specularRect, frag.xy); + if (spec.a > 0.0) + { + vec3 npos = -normalize(pos); + dlit *= min(da*6.0, 1.0) * dist_atten; + + //vec3 ref = dot(pos+lv, norm); + vec3 h = normalize(lv+npos); + float nh = dot(norm, h); + float nv = dot(norm, npos); + float vh = dot(npos, h); + float sa = nh; + float fres = pow(1 - dot(h, npos), 5)*0.4+0.5; + + float gtdenom = 2 * nh; + float gt = max(0, min(gtdenom * nv / vh, gtdenom * da / vh)); + + if (nh > 0.0) + { + float scol = fres*texture2D(lightFunc, vec2(nh, spec.a)).r*gt/(nh*da); + col += dlit*scol*spec.rgb*shadow; + //col += spec.rgb; + } + } + + + + + + if (envIntensity > 0.0) { vec3 ref = reflect(normalize(pos), norm); @@ -247,13 +334,12 @@ void main() vec3 pfinal = pos + ref * dot(pdelta, proj_n)/ds; vec4 stc = (proj_mat * vec4(pfinal.xyz, 1.0)); + stc /= stc.w; if (stc.z > 0.0) { - stc.xy /= stc.w; + float fatten = clamp(envIntensity*envIntensity+envIntensity*0.25, 0.25, 1.0); - float fatten = clamp(spec.a*spec.a+spec.a*0.5, 0.25, 1.0); - stc.xy = (stc.xy - vec2(0.5)) * fatten + vec2(0.5); if (stc.x < 1.0 && @@ -261,13 +347,16 @@ void main() stc.x > 0.0 && stc.y > 0.0) { - vec4 scol = texture2DLodSpecular(projectionMap, stc.xy, proj_lod-spec.a*proj_lod); - col += dist_atten*scol.rgb*color.rgb*scol.a*spec.rgb*shadow; + col += color.rgb*texture2DLodSpecular(projectionMap, stc.xy, proj_lod).rgb*shadow*spec.rgb; } } } } + + //not sure why, but this line prevents MATBUG-194 + col = max(col, vec3(0.0)); + frag_color.rgb = col; frag_color.a = 0.0; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl index d490b5d46..64ac5ebb2 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl @@ -38,7 +38,6 @@ uniform sampler2DRect lightMap; uniform sampler2DRect depthMap; uniform samplerCube environmentMap; uniform sampler2D lightFunc; -uniform vec3 gi_quad; uniform float blur_size; uniform float blur_fidelity; @@ -60,16 +59,13 @@ uniform float density_multiplier; uniform float distance_multiplier; uniform float max_y; uniform vec4 glow; +uniform float global_gamma; uniform float scene_light_strength; uniform mat3 env_mat; uniform vec4 shadow_clip; -uniform float ssao_effect; - -uniform mat4 inv_proj; -uniform vec2 screen_res; +uniform mat3 ssao_effect_mat; uniform vec3 sun_dir; - VARYING vec2 vary_fragcoord; vec3 vary_PositionEye; @@ -79,11 +75,61 @@ vec3 vary_AmblitColor; vec3 vary_AdditiveColor; vec3 vary_AtmosAttenuation; -float luminance(vec3 color) +uniform mat4 inv_proj; +uniform vec2 screen_res; + +vec3 srgb_to_linear(vec3 cs) { - /// CALCULATING LUMINANCE (Using NTSC lum weights) - /// http://en.wikipedia.org/wiki/Luma_%28video%29 - return dot(color, vec3(0.299, 0.587, 0.114)); + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lte); +#endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; } vec4 getPosition_d(vec2 pos_screen, float depth) @@ -125,7 +171,6 @@ vec3 getAtmosAttenuation() return vary_AtmosAttenuation; } - void setPositionEye(vec3 v) { vary_PositionEye = v; @@ -212,21 +257,78 @@ void calcAtmospherics(vec3 inPositionEye, float ambFactor) { //increase ambient when there are more clouds vec4 tmpAmbient = ambient + (vec4(1.) - ambient) * cloud_shadow * 0.5; + /* decrease value and saturation (that in HSV, not HSL) for occluded areas + * // for HSV color/geometry used here, see http://gimp-savvy.com/BOOK/index.html?node52.html + * // The following line of code performs the equivalent of: + * float ambAlpha = tmpAmbient.a; + * float ambValue = dot(vec3(tmpAmbient), vec3(0.577)); // projection onto <1/rt(3), 1/rt(3), 1/rt(3)>, the neutral white-black axis + * vec3 ambHueSat = vec3(tmpAmbient) - vec3(ambValue); + * tmpAmbient = vec4(RenderSSAOEffect.valueFactor * vec3(ambValue) + RenderSSAOEffect.saturationFactor *(1.0 - ambFactor) * ambHueSat, ambAlpha); + */ + tmpAmbient = vec4(mix(ssao_effect_mat * tmpAmbient.rgb, tmpAmbient.rgb, ambFactor), tmpAmbient.a); + //haze color setAdditiveColor( vec3(blue_horizon * blue_weight * (sunlight*(1.-cloud_shadow) + tmpAmbient) + (haze_horizon * haze_weight) * (sunlight*(1.-cloud_shadow) * temp2.x + tmpAmbient))); - // decrease ambient value for occluded areas - tmpAmbient *= mix(ssao_effect, 1.0, ambFactor); - //brightness of surface both sunlight and ambient + /*setSunlitColor(pow(vec3(sunlight * .5), vec3(global_gamma)) * global_gamma); + setAmblitColor(pow(vec3(tmpAmbient * .25), vec3(global_gamma)) * global_gamma); + setAdditiveColor(pow(getAdditiveColor() * vec3(1.0 - temp1), vec3(global_gamma)) * global_gamma);*/ + setSunlitColor(vec3(sunlight * .5)); setAmblitColor(vec3(tmpAmbient * .25)); setAdditiveColor(getAdditiveColor() * vec3(1.0 - temp1)); } +#ifdef WATER_FOG +uniform vec4 waterPlane; +uniform vec4 waterFogColor; +uniform float waterFogDensity; +uniform float waterFogKS; + +vec4 applyWaterFogDeferred(vec3 pos, vec4 color) +{ + //normalize view vector + vec3 view = normalize(pos); + float es = -(dot(view, waterPlane.xyz)); + + //find intersection point with water plane and eye vector + + //get eye depth + float e0 = max(-waterPlane.w, 0.0); + + vec3 int_v = waterPlane.w > 0.0 ? view * waterPlane.w/es : vec3(0.0, 0.0, 0.0); + + //get object depth + float depth = length(pos - int_v); + + //get "thickness" of water + float l = max(depth, 0.1); + + float kd = waterFogDensity; + float ks = waterFogKS; + vec4 kc = waterFogColor; + + float F = 0.98; + + float t1 = -kd * pow(F, ks * e0); + float t2 = kd + ks * es; + float t3 = pow(F, t2*l) - 1.0; + + float L = min(t1/t2*t3, 1.0); + + float D = pow(0.98, l*kd); + + color.rgb = color.rgb * D + kc.rgb * L; + color.a = kc.a + color.a; + + return color; +} +#endif + vec3 atmosLighting(vec3 light) { light *= getAtmosAttenuation().r; @@ -239,6 +341,15 @@ vec3 atmosTransport(vec3 light) { light += getAdditiveColor() * 2.0; return light; } + +vec3 fullbrightAtmosTransport(vec3 light) { + float brightness = dot(light.rgb, vec3(0.33333)); + + return mix(atmosTransport(light.rgb), light.rgb + getAdditiveColor().rgb, brightness * brightness); +} + + + vec3 atmosGetDiffuseSunlightColor() { return getSunlitColor(); @@ -273,18 +384,18 @@ vec3 scaleSoftClip(vec3 light) return light; } -vec3 unpack(vec2 tc) + +vec3 fullbrightScaleSoftClip(vec3 light) { -//#define PACK_NORMALS -#ifdef PACK_NORMALS - vec2 enc = texture2DRect(normalMap, tc).xy; - enc = enc*4.0-2.0; - float prod = dot(enc,enc); - return vec3(enc*sqrt(1.0-prod*.25),1.0-prod*.5); -#else - vec3 norm = texture2DRect(normalMap, tc).xyz; - return norm*2.0-1.0; -#endif + //soft clip effect: + return light; +} + +float luminance(vec3 color) +{ + /// CALCULATING LUMINANCE (Using NTSC lum weights) + /// http://en.wikipedia.org/wiki/Luma_%28video%29 + return dot(color, vec3(0.299, 0.587, 0.114)); } void main() @@ -292,61 +403,107 @@ void main() vec2 tc = vary_fragcoord.xy; float depth = texture2DRect(depthMap, tc.xy).r; vec3 pos = getPosition_d(tc, depth).xyz; - vec3 norm = unpack(tc); // unpack norm + vec4 norm = texture2DRect(normalMap, tc); + float envIntensity = norm.z; + norm.xyz = decode_normal(norm.xy); // unpack norm float da = max(dot(norm.xyz, sun_dir.xyz), 0.0); - - vec4 diffuse = texture2DRect(diffuseRect, tc); - vec4 spec = texture2DRect(specularRect, vary_fragcoord.xy); + float light_gamma = 1.0/1.3; + da = pow(da, light_gamma); + + + vec4 diffuse = texture2DRect(diffuseRect, tc); + + //convert to gamma space + diffuse.rgb = linear_to_srgb(diffuse.rgb); + vec3 col; float bloom = 0.0; - - if (diffuse.a < 0.9) { + vec4 spec = texture2DRect(specularRect, vary_fragcoord.xy); + bloom = spec.r*norm.w; + + if (norm.w < 0.5) + { vec2 scol_ambocc = texture2DRect(lightMap, vary_fragcoord.xy).rg; + scol_ambocc = pow(scol_ambocc, vec2(light_gamma)); + float scol = max(scol_ambocc.r, diffuse.a); + + + float ambocc = scol_ambocc.g; calcAtmospherics(pos.xyz, ambocc); col = atmosAmbient(vec3(0)); - col += atmosAffectDirectionalLight(max(min(da, scol), diffuse.a)); + float ambient = min(abs(dot(norm.xyz, sun_dir.xyz)), 1.0); + ambient *= 0.5; + ambient *= ambient; + ambient = (1.0-ambient); + + col.rgb *= ambient; + + col += atmosAffectDirectionalLight(max(min(da, scol), 0.0)); col *= diffuse.rgb; + vec3 refnormpersp = normalize(reflect(pos.xyz, norm.xyz)); + if (spec.a > 0.0) // specular reflection { // the old infinite-sky shiny reflection // - vec3 refnormpersp = normalize(reflect(pos.xyz, norm.xyz)); + float sa = dot(refnormpersp, sun_dir.xyz); - vec3 dumbshiny = vary_SunlitColor*scol_ambocc.r*(6 * texture2D(lightFunc, vec2(sa, spec.a)).r); - + vec3 dumbshiny = vary_SunlitColor*scol_ambocc.r*(texture2D(lightFunc, vec2(sa, spec.a)).r); + // add the two types of shiny together vec3 spec_contrib = dumbshiny * spec.rgb; - bloom = dot(spec_contrib, spec_contrib) / 4; + bloom = dot(spec_contrib, spec_contrib) / 6; col += spec_contrib; - - //add environmentmap - vec3 env_vec = env_mat * refnormpersp; - vec3 env = textureCube(environmentMap, env_vec).rgb; - bloom = (luminance(env) - .45)*.25; - col = mix(col.rgb, env, - max(spec.a-diffuse.a*2.0, 0.0)); - } - col = atmosLighting(col); - col = scaleSoftClip(col); - - col = mix(col, diffuse.rgb, diffuse.a); - } - else - { - bloom = spec.r; - col = diffuse.rgb; - } + } + + col = mix(col, diffuse.rgb, diffuse.a); + + if (envIntensity > 0.0) + { //add environmentmap + vec3 env_vec = env_mat * refnormpersp; + + vec3 refcol = textureCube(environmentMap, env_vec).rgb*scol_ambocc.r; + bloom = (luminance(refcol) - .45)*.25; + col = mix(col.rgb, refcol, + envIntensity); + + } + + //if (norm.w < 0.5) + { + col = mix(atmosLighting(col), fullbrightAtmosTransport(col), diffuse.a); + col = mix(scaleSoftClip(col), fullbrightScaleSoftClip(col), diffuse.a); + //bloom += (luminance(col))*.075; //This looks nice, but requires a larger glow rendertarget. + } + + #ifdef WATER_FOG + vec4 fogged = applyWaterFogDeferred(pos,vec4(col, bloom)); + col = fogged.rgb; + bloom = fogged.a; + #endif + } + else + { + col = diffuse.rgb; + } + + col = srgb_to_linear(col); + + //col = vec3(1,0,1); + //col.g = envIntensity; + } + frag_color.rgb = col; frag_color.a = bloom; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/spotLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/spotLightF.glsl index 3a556e913..7689b72d2 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/spotLightF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/spotLightF.glsl @@ -24,6 +24,7 @@ */ #extension GL_ARB_texture_rectangle : enable +#extension GL_ARB_shader_texture_lod : enable #ifdef DEFINE_GL_FRAGCOLOR out vec4 frag_color; @@ -39,6 +40,7 @@ uniform samplerCube environmentMap; uniform sampler2DRect lightMap; uniform sampler2D noiseMap; uniform sampler2D projectionMap; +uniform sampler2D lightFunc; uniform mat4 proj_mat; //screen space to light space uniform float proj_near; //near clip for projection @@ -67,9 +69,69 @@ uniform vec2 screen_res; uniform mat4 inv_proj; +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + +vec3 srgb_to_linear(vec3 cs) +{ + vec3 low_range = cs / vec3(12.92); + vec3 high_range = pow((cs+vec3(0.055))/vec3(1.055), vec3(2.4)); + bvec3 lte = lessThanEqual(cs,vec3(0.04045)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lte.r ? low_range.r : high_range.r; + result.g = lte.g ? low_range.g : high_range.g; + result.b = lte.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lte); +#endif + +} + +vec3 linear_to_srgb(vec3 cl) +{ + cl = clamp(cl, vec3(0), vec3(1)); + vec3 low_range = cl * 12.92; + vec3 high_range = 1.055 * pow(cl, vec3(0.41666)) - 0.055; + bvec3 lt = lessThan(cl,vec3(0.0031308)); + +#ifdef OLD_SELECT + vec3 result; + result.r = lt.r ? low_range.r : high_range.r; + result.g = lt.g ? low_range.g : high_range.g; + result.b = lt.b ? low_range.b : high_range.b; + return result; +#else + return mix(high_range, low_range, lt); +#endif + +} + +vec4 correctWithGamma(vec4 col) +{ + return vec4(srgb_to_linear(col.rgb), col.a); +} + vec4 texture2DLodSpecular(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); + ret = correctWithGamma(ret); vec2 dist = tc-vec2(0.5); @@ -85,6 +147,7 @@ vec4 texture2DLodSpecular(sampler2D projectionMap, vec2 tc, float lod) vec4 texture2DLodDiffuse(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); + ret = correctWithGamma(ret); vec2 dist = vec2(0.5) - abs(tc-vec2(0.5)); @@ -102,6 +165,7 @@ vec4 texture2DLodDiffuse(sampler2D projectionMap, vec2 tc, float lod) vec4 texture2DLodAmbient(sampler2D projectionMap, vec2 tc, float lod) { vec4 ret = texture2DLod(projectionMap, tc, lod); + ret = correctWithGamma(ret); vec2 dist = tc-vec2(0.5); @@ -126,20 +190,6 @@ vec4 getPosition(vec2 pos_screen) return pos; } -vec3 unpack(vec2 tc) -{ -//#define PACK_NORMALS -#ifdef PACK_NORMALS - vec2 enc = texture2DRect(normalMap, tc).xy; - enc = enc*4.0-2.0; - float prod = dot(enc,enc); - return vec3(enc*sqrt(1.0-prod*.25),1.0-prod*.5); -#else - vec3 norm = texture2DRect(normalMap, tc).xyz; - return norm*2.0-1.0; -#endif -} - void main() { vec4 frag = vary_fragcoord; @@ -149,9 +199,9 @@ void main() vec3 pos = getPosition(frag.xy).xyz; vec3 lv = trans_center.xyz-pos.xyz; - float dist2 = dot(lv,lv); - dist2 /= size; - if (dist2 > 1.0) + float dist = length(lv); + dist /= size; + if (dist > 1.0) { discard; } @@ -167,9 +217,11 @@ void main() shadow = min(sh[proj_shadow_idx]+shadow_fade, 1.0); } - vec3 norm = unpack(frag.xy); // unpack norm + vec3 norm = texture2DRect(normalMap, frag.xy).xyz; + float envIntensity = norm.z; + norm = decode_normal(norm.xy); - //norm = normalize(norm); // may be superfluous + norm = normalize(norm); float l_dist = -dot(lv, proj_n); vec4 proj_tc = (proj_mat * vec4(pos.xyz, 1.0)); @@ -181,7 +233,10 @@ void main() proj_tc.xyz /= proj_tc.w; float fa = falloff+1.0; - float dist_atten = min(1.0-(dist2-1.0*(1.0-fa))/fa, 1.0); + float dist_atten = min(1.0-(dist-1.0*(1.0-fa))/fa, 1.0); + dist_atten *= dist_atten; + dist_atten *= 2.0; + if (dist_atten <= 0.0) { discard; @@ -195,6 +250,10 @@ void main() vec3 diff_tex = texture2DRect(diffuseRect, frag.xy).rgb; + vec4 spec = texture2DRect(specularRect, frag.xy); + + vec3 dlit = vec3(0, 0, 0); + float noise = texture2D(noiseMap, frag.xy/128.0).b; if (proj_tc.z > 0.0 && proj_tc.x < 1.0 && @@ -202,21 +261,21 @@ void main() proj_tc.x > 0.0 && proj_tc.y > 0.0) { - float lit = 0.0; float amb_da = proj_ambiance; + float lit = 0.0; if (da > 0.0) { + lit = da * dist_atten * noise; + float diff = clamp((l_dist-proj_focus)/proj_range, 0.0, 1.0); float lod = diff * proj_lod; vec4 plcol = texture2DLodDiffuse(projectionMap, proj_tc.xy, lod); - vec3 lcol = color.rgb * plcol.rgb * plcol.a; + dlit = color.rgb * plcol.rgb * plcol.a; - lit = da * dist_atten * noise; - - col = lcol*lit*diff_tex*shadow; + col = dlit*lit*diff_tex*shadow; amb_da += (da*0.5)*(1.0-shadow)*proj_ambiance; } @@ -232,9 +291,36 @@ void main() col += amb_da*color.rgb*diff_tex.rgb*amb_plcol.rgb*amb_plcol.a; } - - vec4 spec = texture2DRect(specularRect, frag.xy); + if (spec.a > 0.0) + { + dlit *= min(da*6.0, 1.0) * dist_atten; + vec3 npos = -normalize(pos); + + //vec3 ref = dot(pos+lv, norm); + vec3 h = normalize(lv+npos); + float nh = dot(norm, h); + float nv = dot(norm, npos); + float vh = dot(npos, h); + float sa = nh; + float fres = pow(1 - dot(h, npos), 5)*0.4+0.5; + + float gtdenom = 2 * nh; + float gt = max(0, min(gtdenom * nv / vh, gtdenom * da / vh)); + + if (nh > 0.0) + { + float scol = fres*texture2D(lightFunc, vec2(nh, spec.a)).r*gt/(nh*da); + col += dlit*scol*spec.rgb*shadow; + //col += spec.rgb; + } + } + + + + + + if (envIntensity > 0.0) { vec3 ref = reflect(normalize(pos), norm); @@ -252,8 +338,9 @@ void main() { stc.xy /= stc.w; - float fatten = clamp(spec.a*spec.a+spec.a*0.5, 0.25, 1.0); + float fatten = clamp(envIntensity*envIntensity+envIntensity*0.5, 0.25, 1.0); + //stc.xy = (stc.xy - vec2(0.5)) * fatten + vec2(0.5); stc.xy = (stc.xy - vec2(0.5)) * fatten + vec2(0.5); if (stc.x < 1.0 && @@ -261,13 +348,15 @@ void main() stc.x > 0.0 && stc.y > 0.0) { - vec4 scol = texture2DLodSpecular(projectionMap, stc.xy, proj_lod-spec.a*proj_lod); - col += dist_atten*scol.rgb*color.rgb*scol.a*spec.rgb*shadow; + col += color.rgb*texture2DLodSpecular(projectionMap, stc.xy, proj_lod-envIntensity*proj_lod).rgb*shadow*spec.rgb; } } } } + //not sure why, but this line prevents MATBUG-194 + col = max(col, vec3(0.0)); + frag_color.rgb = col; frag_color.a = 0.0; } diff --git a/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl index f8f57e5e7..18f601f1b 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/sunLightF.glsl @@ -35,10 +35,10 @@ out vec4 frag_color; uniform sampler2DRect depthMap; uniform sampler2DRect normalMap; -uniform sampler2DRectShadow shadowMap0; -uniform sampler2DRectShadow shadowMap1; -uniform sampler2DRectShadow shadowMap2; -uniform sampler2DRectShadow shadowMap3; +uniform sampler2DShadow shadowMap0; +uniform sampler2DShadow shadowMap1; +uniform sampler2DShadow shadowMap2; +uniform sampler2DShadow shadowMap3; uniform sampler2DShadow shadowMap4; uniform sampler2DShadow shadowMap5; @@ -55,16 +55,33 @@ VARYING vec2 vary_fragcoord; uniform mat4 inv_proj; uniform vec2 screen_res; -uniform vec2 shadow_res; uniform vec2 proj_shadow_res; uniform vec3 sun_dir; +uniform vec2 shadow_res; uniform float shadow_bias; uniform float shadow_offset; uniform float spot_shadow_bias; uniform float spot_shadow_offset; +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + vec4 getPosition(vec2 pos_screen) { float depth = texture2DRect(depthMap, pos_screen.xy).r; @@ -78,30 +95,31 @@ vec4 getPosition(vec2 pos_screen) return pos; } -float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) +float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) { stc.xyz /= stc.w; - stc.z += shadow_bias*scl; + stc.z += shadow_bias; - stc.x = floor(stc.x + fract(pos_screen.y*0.666666666)); // add some jitter to X sample pos according to Y to disguise the snapping going on here + stc.x = floor(stc.x*shadow_res.x + fract(pos_screen.y*0.666666666))/shadow_res.x; // add some jitter to X sample pos according to Y to disguise the snapping going on here + float cs = shadow2D(shadowMap, stc.xyz).x; - float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, 1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, -1.5, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(2.0/shadow_res.x, 1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(1.0/shadow_res.x, -1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-2.0/shadow_res.x, 1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-1.0/shadow_res.x, -1.5/shadow_res.y, 0.0)).x; + - return shadow*0.2; + return shadow*0.2; } -float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) +float pcfSpotShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) { stc.xyz /= stc.w; stc.z += spot_shadow_bias*scl; stc.x = floor(proj_shadow_res.x * stc.x + fract(pos_screen.y*0.666666666)) / proj_shadow_res.x; // snap - + float cs = shadow2D(shadowMap, stc.xyz).x; float shadow = cs; @@ -113,23 +131,7 @@ float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) shadow += shadow2D(shadowMap, stc.xyz+vec3(-off.x, off.y, 0.0)).x; shadow += shadow2D(shadowMap, stc.xyz+vec3(-off.x*2.0, -off.y, 0.0)).x; - return shadow*0.2; -} - -vec4 unpack(vec2 tc) -{ - vec4 norm = texture2DRect(normalMap, tc).xyzw; -//#define PACK_NORMALS -#ifdef PACK_NORMALS - norm.xy = (norm.xy*4.0)-2.0; - float prod = dot(norm.xy,norm.xy); - norm.xy *= sqrt(1.0-prod*.25); - norm.z = 1.0-prod*.5; -#else - norm.xyz = norm.xyz*2.0-1.0; -#endif - norm.w *= norm.z; - return norm; + return shadow*0.2; } void main() @@ -140,10 +142,9 @@ void main() vec4 pos = getPosition(pos_screen); - vec4 nmap4 = unpack(pos_screen); // unpack norm - float displace = nmap4.w; - vec3 norm = nmap4.xyz; - + vec3 norm = texture2DRect(normalMap, pos_screen).xyz; + norm = decode_normal(norm.xy); // unpack norm + /*if (pos.z == 0.0) // do nothing for sky *FIX: REMOVE THIS IF/WHEN THE POSITION MAP IS BEING USED AS A STENCIL { frag_color = vec4(0.0); // doesn't matter @@ -153,7 +154,7 @@ void main() float shadow = 0.0; float dp_directional_light = max(0.0, dot(norm, sun_dir.xyz)); - vec3 shadow_pos = pos.xyz + displace*norm; + vec3 shadow_pos = pos.xyz; vec3 offset = sun_dir.xyz * (1.0-dp_directional_light); vec4 spos = vec4(shadow_pos+offset*shadow_offset, 1.0); @@ -177,8 +178,7 @@ void main() if (spos.z < near_split.z) { lpos = shadow_matrix[3]*spos; - lpos.xy *= shadow_res; - + float w = 1.0; w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; shadow += pcfShadow(shadowMap3, lpos, 0.25, pos_screen)*w; @@ -189,8 +189,7 @@ void main() if (spos.z < near_split.y && spos.z > far_split.z) { lpos = shadow_matrix[2]*spos; - lpos.xy *= shadow_res; - + float w = 1.0; w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; @@ -201,7 +200,6 @@ void main() if (spos.z < near_split.x && spos.z > far_split.y) { lpos = shadow_matrix[1]*spos; - lpos.xy *= shadow_res; float w = 1.0; w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; @@ -213,7 +211,6 @@ void main() if (spos.z > far_split.x) { lpos = shadow_matrix[0]*spos; - lpos.xy *= shadow_res; float w = 1.0; w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; @@ -252,11 +249,11 @@ void main() //spotlight shadow 1 vec4 lpos = shadow_matrix[4]*spos; - frag_color[2] = pcfShadow(shadowMap4, lpos, 0.8, pos_screen); + frag_color[2] = pcfSpotShadow(shadowMap4, lpos, 0.8, pos_screen); //spotlight shadow 2 lpos = shadow_matrix[5]*spos; - frag_color[3] = pcfShadow(shadowMap5, lpos, 0.8, pos_screen); + frag_color[3] = pcfSpotShadow(shadowMap5, lpos, 0.8, pos_screen); //frag_color.rgb = pos.xyz; //frag_color.b = shadow; diff --git a/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl b/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl index ddcb024a9..c6d13db8e 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/sunLightSSAOF.glsl @@ -34,10 +34,10 @@ out vec4 frag_color; uniform sampler2DRect depthMap; uniform sampler2DRect normalMap; -uniform sampler2DRectShadow shadowMap0; -uniform sampler2DRectShadow shadowMap1; -uniform sampler2DRectShadow shadowMap2; -uniform sampler2DRectShadow shadowMap3; +uniform sampler2DShadow shadowMap0; +uniform sampler2DShadow shadowMap1; +uniform sampler2DShadow shadowMap2; +uniform sampler2DShadow shadowMap3; uniform sampler2DShadow shadowMap4; uniform sampler2DShadow shadowMap5; uniform sampler2D noiseMap; @@ -55,16 +55,34 @@ VARYING vec2 vary_fragcoord; uniform mat4 inv_proj; uniform vec2 screen_res; -uniform vec2 shadow_res; uniform vec2 proj_shadow_res; uniform vec3 sun_dir; +uniform vec2 shadow_res; + uniform float shadow_bias; uniform float shadow_offset; uniform float spot_shadow_bias; uniform float spot_shadow_offset; +vec2 encode_normal(vec3 n) +{ + float f = sqrt(8 * n.z + 8); + return n.xy / f + 0.5; +} + +vec3 decode_normal (vec2 enc) +{ + vec2 fenc = enc*4-2; + float f = dot(fenc,fenc); + float g = sqrt(1-f/4); + vec3 n; + n.xy = fenc*g; + n.z = 1-f/2; + return n; +} + vec4 getPosition(vec2 pos_screen) { float depth = texture2DRect(depthMap, pos_screen.xy).r; @@ -133,25 +151,25 @@ float calcAmbientOcclusion(vec4 pos, vec3 norm) return (rtn * rtn); } -float pcfShadow(sampler2DRectShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) +float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) { stc.xyz /= stc.w; - stc.z += shadow_bias*scl; + stc.z += shadow_bias; - stc.x = floor(stc.x + fract(pos_screen.y*0.666666666)); + stc.x = floor(stc.x*shadow_res.x + fract(pos_screen.y*0.666666666))/shadow_res.x; + float cs = shadow2D(shadowMap, stc.xyz).x; - float cs = shadow2DRect(shadowMap, stc.xyz).x; float shadow = cs; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(2.0, 1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(1.0, -1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-1.0, 1.5, 0.0)).x; - shadow += shadow2DRect(shadowMap, stc.xyz+vec3(-2.0, -1.5, 0.0)).x; - + shadow += shadow2D(shadowMap, stc.xyz+vec3(2.0/shadow_res.x, 1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(1.0/shadow_res.x, -1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-1.0/shadow_res.x, 1.5/shadow_res.y, 0.0)).x; + shadow += shadow2D(shadowMap, stc.xyz+vec3(-2.0/shadow_res.x, -1.5/shadow_res.y, 0.0)).x; + return shadow*0.2; } -float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) +float pcfSpotShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) { stc.xyz /= stc.w; stc.z += spot_shadow_bias*scl; @@ -171,22 +189,6 @@ float pcfShadow(sampler2DShadow shadowMap, vec4 stc, float scl, vec2 pos_screen) return shadow*0.2; } -vec4 unpack(vec2 tc) -{ - vec4 norm = texture2DRect(normalMap, tc).xyzw; -//#define PACK_NORMALS -#ifdef PACK_NORMALS - norm.xy = (norm.xy*4.0)-2.0; - float prod = dot(norm.xy,norm.xy); - norm.xy *= sqrt(1.0-prod*.25); - norm.z = 1.0-prod*.5; -#else - norm.xyz = norm.xyz*2.0-1.0; -#endif - norm.w *= norm.z; - return norm; -} - void main() { vec2 pos_screen = vary_fragcoord.xy; @@ -195,10 +197,9 @@ void main() vec4 pos = getPosition(pos_screen); - vec4 nmap4 = unpack(pos_screen); // unpack norm - float displace = nmap4.w; - vec3 norm = nmap4.xyz; - + vec3 norm = texture2DRect(normalMap, pos_screen).xyz; + norm = decode_normal(norm.xy); // unpack norm + /*if (pos.z == 0.0) // do nothing for sky *FIX: REMOVE THIS IF/WHEN THE POSITION MAP IS BEING USED AS A STENCIL { frag_color = vec4(0.0); // doesn't matter @@ -207,8 +208,8 @@ void main() float shadow = 0.0; float dp_directional_light = max(0.0, dot(norm, sun_dir.xyz)); - - vec3 shadow_pos = pos.xyz + displace*norm; + + vec3 shadow_pos = pos.xyz; vec3 offset = sun_dir.xyz * (1.0-dp_directional_light); vec4 spos = vec4(shadow_pos+offset*shadow_offset, 1.0); @@ -232,8 +233,7 @@ void main() if (spos.z < near_split.z) { lpos = shadow_matrix[3]*spos; - lpos.xy *= shadow_res; - + float w = 1.0; w -= max(spos.z-far_split.z, 0.0)/transition_domain.z; shadow += pcfShadow(shadowMap3, lpos, 0.25, pos_screen)*w; @@ -244,8 +244,7 @@ void main() if (spos.z < near_split.y && spos.z > far_split.z) { lpos = shadow_matrix[2]*spos; - lpos.xy *= shadow_res; - + float w = 1.0; w -= max(spos.z-far_split.y, 0.0)/transition_domain.y; w -= max(near_split.z-spos.z, 0.0)/transition_domain.z; @@ -256,8 +255,7 @@ void main() if (spos.z < near_split.x && spos.z > far_split.y) { lpos = shadow_matrix[1]*spos; - lpos.xy *= shadow_res; - + float w = 1.0; w -= max(spos.z-far_split.x, 0.0)/transition_domain.x; w -= max(near_split.y-spos.z, 0.0)/transition_domain.y; @@ -268,8 +266,7 @@ void main() if (spos.z > far_split.x) { lpos = shadow_matrix[0]*spos; - lpos.xy *= shadow_res; - + float w = 1.0; w -= max(near_split.x-spos.z, 0.0)/transition_domain.x; @@ -307,11 +304,11 @@ void main() //spotlight shadow 1 vec4 lpos = shadow_matrix[4]*spos; - frag_color[2] = pcfShadow(shadowMap4, lpos, 0.8, pos_screen); + frag_color[2] = pcfSpotShadow(shadowMap4, lpos, 0.8, pos_screen); //spotlight shadow 2 lpos = shadow_matrix[5]*spos; - frag_color[3] = pcfShadow(shadowMap5, lpos, 0.8, pos_screen); + frag_color[3] = pcfSpotShadow(shadowMap5, lpos, 0.8, pos_screen); //frag_color.rgb = pos.xyz; //frag_color.b = shadow; diff --git a/indra/newview/lldrawable.cpp b/indra/newview/lldrawable.cpp index 8d0f3e971..78170275b 100644 --- a/indra/newview/lldrawable.cpp +++ b/indra/newview/lldrawable.cpp @@ -304,6 +304,49 @@ LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep) } +LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp) +{ + LLFace *face; + face = new LLFace(this, mVObjp); + + face->setTEOffset(mFaces.size()); + face->setTexture(texturep); + face->setNormalMap(normalp); + face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep)); + + mFaces.push_back(face); + + if (isState(UNLIT)) + { + face->setState(LLFace::FULLBRIGHT); + } + + return face; + +} + +LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp, LLViewerTexture *specularp) +{ + LLFace *face; + face = new LLFace(this, mVObjp); + + face->setTEOffset(mFaces.size()); + face->setTexture(texturep); + face->setNormalMap(normalp); + face->setSpecularMap(specularp); + face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep)); + + mFaces.push_back(face); + + if (isState(UNLIT)) + { + face->setState(LLFace::FULLBRIGHT); + } + + return face; + +} + void LLDrawable::setNumFaces(const S32 newFaces, LLFacePool *poolp, LLViewerTexture *texturep) { if (newFaces == (S32)mFaces.size()) @@ -565,10 +608,10 @@ F32 LLDrawable::updateXform(BOOL undamped) mVObjp->dirtySpatialGroup(); } } - else if (!isRoot() && ( - dist_vec_squared(old_pos, target_pos) > 0.f - || old_rot != target_rot )) - { //fix for BUG-860, MAINT-2275, MAINT-1742, MAINT-2247 + else if (!isRoot() && + ((dist_vec_squared(old_pos, target_pos) > 0.f) + || (1.f - dot(old_rot, target_rot)) > 0.f)) + { //fix for BUG-840, MAINT-2275, MAINT-1742, MAINT-2247 gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION, TRUE); } else if (!getVOVolume() && !isAvatar()) diff --git a/indra/newview/lldrawable.h b/indra/newview/lldrawable.h index ba4665a99..421d6b23b 100644 --- a/indra/newview/lldrawable.h +++ b/indra/newview/lldrawable.h @@ -151,6 +151,8 @@ public: //void removeFace(const S32 i); // SJB: Avoid using this, it's slow LLFace* addFace(LLFacePool *poolp, LLViewerTexture *texturep); LLFace* addFace(const LLTextureEntry *te, LLViewerTexture *texturep); + LLFace* addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp); + LLFace* addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp, LLViewerTexture *specularp); void deleteFaces(S32 offset, S32 count); void setNumFaces(const S32 numFaces, LLFacePool *poolp, LLViewerTexture *texturep); void setNumFacesFast(const S32 numFaces, LLFacePool *poolp, LLViewerTexture *texturep); diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index 909cec540..a2aa5abbf 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -35,6 +35,7 @@ #include "lldrawpoolalpha.h" #include "lldrawpoolavatar.h" #include "lldrawpoolbump.h" +#include "lldrawpoolmaterials.h" #include "lldrawpoolground.h" #include "lldrawpoolsimple.h" #include "lldrawpoolsky.h" @@ -47,6 +48,7 @@ #include "llspatialpartition.h" #include "llviewercamera.h" #include "lldrawpoolwlsky.h" +#include "llglslshader.h" S32 LLDrawPool::sNumDrawPools = 0; @@ -64,6 +66,12 @@ LLDrawPool *LLDrawPool::createPool(const U32 type, LLViewerTexture *tex0) case POOL_GRASS: poolp = new LLDrawPoolGrass(); break; + case POOL_ALPHA_MASK: + poolp = new LLDrawPoolAlphaMask(); + break; + case POOL_FULLBRIGHT_ALPHA_MASK: + poolp = new LLDrawPoolFullbrightAlphaMask(); + break; case POOL_FULLBRIGHT: poolp = new LLDrawPoolFullbright(); break; @@ -98,6 +106,9 @@ LLDrawPool *LLDrawPool::createPool(const U32 type, LLViewerTexture *tex0) case POOL_BUMP: poolp = new LLDrawPoolBump(); break; + case POOL_MATERIALS: + poolp = new LLDrawPoolMaterials(); + break; case POOL_WL_SKY: poolp = new LLDrawPoolWLSky(); break; @@ -408,6 +419,27 @@ void LLRenderPass::pushBatches(U32 type, U32 mask, BOOL texture, BOOL batch_text } } +void LLRenderPass::pushMaskBatches(U32 type, U32 mask, BOOL texture, BOOL batch_textures) +{ + for (LLCullResult::drawinfo_iterator i = gPipeline.beginRenderMap(type); i != gPipeline.endRenderMap(type); ++i) + { + LLDrawInfo* pparams = *i; + if (pparams) + { + if (LLGLSLShader::sCurBoundShaderPtr) + { + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); + } + else + { + gGL.setAlphaRejectSettings(LLRender::CF_GREATER, pparams->mAlphaMaskCutoff); + } + + pushBatch(*pparams, mask, texture, batch_textures); + } + } +} + void LLRenderPass::applyModelMatrix(LLDrawInfo& params) { if (params.mModelMatrix != gGLLastMatrix) diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h index 384e39837..fa8fc3675 100644 --- a/indra/newview/lldrawpool.h +++ b/indra/newview/lldrawpool.h @@ -50,8 +50,11 @@ public: POOL_GROUND, POOL_FULLBRIGHT, POOL_BUMP, + POOL_MATERIALS, POOL_TERRAIN, POOL_TREE, // Singu Note: Before sky for zcull. + POOL_ALPHA_MASK, + POOL_FULLBRIGHT_ALPHA_MASK, POOL_SKY, POOL_WL_SKY, POOL_GRASS, @@ -129,6 +132,22 @@ public: PASS_SHINY, PASS_BUMP, PASS_POST_BUMP, + PASS_MATERIAL, + PASS_MATERIAL_ALPHA, + PASS_MATERIAL_ALPHA_MASK, + PASS_MATERIAL_ALPHA_EMISSIVE, + PASS_SPECMAP, + PASS_SPECMAP_BLEND, + PASS_SPECMAP_MASK, + PASS_SPECMAP_EMISSIVE, + PASS_NORMMAP, + PASS_NORMMAP_BLEND, + PASS_NORMMAP_MASK, + PASS_NORMMAP_EMISSIVE, + PASS_NORMSPEC, + PASS_NORMSPEC_BLEND, + PASS_NORMSPEC_MASK, + PASS_NORMSPEC_EMISSIVE, PASS_GLOW, PASS_ALPHA, PASS_ALPHA_MASK, @@ -147,6 +166,7 @@ public: static void applyModelMatrix(LLDrawInfo& params); virtual void pushBatches(U32 type, U32 mask, BOOL texture = TRUE, BOOL batch_textures = FALSE); + virtual void pushMaskBatches(U32 type, U32 mask, BOOL texture = TRUE, BOOL batch_textures = FALSE); virtual void pushBatch(LLDrawInfo& params, U32 mask, BOOL texture, BOOL batch_textures = FALSE); virtual void renderGroup(LLSpatialGroup* group, U32 type, U32 mask, BOOL texture = TRUE); virtual void renderGroups(U32 type, U32 mask, BOOL texture = TRUE); diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp index 5fdedc68e..bab4f75ca 100644 --- a/indra/newview/lldrawpoolalpha.cpp +++ b/indra/newview/lldrawpoolalpha.cpp @@ -77,33 +77,6 @@ void LLDrawPoolAlpha::prerender() mVertexShaderLevel = LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); } -S32 LLDrawPoolAlpha::getNumDeferredPasses() -{ - return 1; -} - -void LLDrawPoolAlpha::beginDeferredPass(S32 pass) -{ - -} - -void LLDrawPoolAlpha::endDeferredPass(S32 pass) -{ - -} - -void LLDrawPoolAlpha::renderDeferred(S32 pass) -{ - LLFastTimer t(FTM_RENDER_GRASS); - gDeferredDiffuseAlphaMaskProgram.bind(); - gDeferredDiffuseAlphaMaskProgram.setMinimumAlpha(0.33f); - - //render alpha masked objects - LLRenderPass::pushBatches(LLRenderPass::PASS_ALPHA_MASK, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE); - gDeferredDiffuseAlphaMaskProgram.unbind(); -} - - S32 LLDrawPoolAlpha::getNumPostDeferredPasses() { static const LLCachedControl render_depth_of_field("RenderDepthOfField"); @@ -127,25 +100,63 @@ void LLDrawPoolAlpha::beginPostDeferredPass(S32 pass) if (pass == 0) { + if (LLPipeline::sImpostorRender) + { + simple_shader = &gDeferredAlphaImpostorProgram; + fullbright_shader = &gDeferredFullbrightProgram; + } + else if (LLPipeline::sUnderWaterRender) + { + simple_shader = &gDeferredAlphaWaterProgram; + fullbright_shader = &gDeferredFullbrightWaterProgram; + } + else + { simple_shader = &gDeferredAlphaProgram; - fullbright_shader = &gObjectFullbrightAlphaMaskProgram; + fullbright_shader = &gDeferredFullbrightProgram; + } + + //F32 gamma = gSavedSettings.getF32("RenderDeferredDisplayGamma"); + + fullbright_shader->bind(); + fullbright_shader->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); + //fullbright_shader->uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f/2.2f)); + fullbright_shader->unbind(); //prime simple shader (loads shadow relevant uniforms) gPipeline.bindDeferredShader(*simple_shader); + + //simple_shader->uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f/2.2f)); } - else + else if (!LLPipeline::sImpostorRender) { //update depth buffer sampler gPipeline.mScreen.flush(); gPipeline.mDeferredDepth.copyContents(gPipeline.mDeferredScreen, 0, 0, gPipeline.mDeferredScreen.getWidth(), gPipeline.mDeferredScreen.getHeight(), 0, 0, gPipeline.mDeferredDepth.getWidth(), gPipeline.mDeferredDepth.getHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); gPipeline.mDeferredDepth.bindTarget(); - simple_shader = NULL; - fullbright_shader = NULL; + simple_shader = fullbright_shader = &gObjectFullbrightAlphaMaskProgram; gObjectFullbrightAlphaMaskProgram.bind(); gObjectFullbrightAlphaMaskProgram.setMinimumAlpha(0.33f); } + + if (LLPipeline::sRenderDeferred) + { + emissive_shader = &gDeferredEmissiveProgram; + } + else + { + if (LLPipeline::sUnderWaterRender) + { + emissive_shader = &gObjectEmissiveWaterProgram; + } + else + { + emissive_shader = &gObjectEmissiveProgram; + } + } + deferred_render = TRUE; if (mVertexShaderLevel > 0) { @@ -157,8 +168,7 @@ void LLDrawPoolAlpha::beginPostDeferredPass(S32 pass) void LLDrawPoolAlpha::endPostDeferredPass(S32 pass) { - - if (pass == 1) + if (pass == 1 && !LLPipeline::sImpostorRender) { gPipeline.mDeferredDepth.flush(); gPipeline.mScreen.bindTarget(); @@ -178,16 +188,22 @@ void LLDrawPoolAlpha::beginRenderPass(S32 pass) { LLFastTimer t(FTM_RENDER_ALPHA); - if (LLPipeline::sUnderWaterRender) + if (LLPipeline::sImpostorRender) { - simple_shader = &gObjectSimpleWaterAlphaMaskProgram; - fullbright_shader = &gObjectFullbrightWaterAlphaMaskProgram; + simple_shader = &gObjectSimpleImpostorProgram; + fullbright_shader = &gObjectFullbrightProgram; + emissive_shader = &gObjectEmissiveProgram; + } + else if (LLPipeline::sUnderWaterRender) + { + simple_shader = &gObjectSimpleWaterProgram; + fullbright_shader = &gObjectFullbrightWaterProgram; emissive_shader = &gObjectEmissiveWaterProgram; } else { - simple_shader = &gObjectSimpleAlphaMaskProgram; - fullbright_shader = &gObjectFullbrightAlphaMaskProgram; + simple_shader = &gObjectSimpleProgram; + fullbright_shader = &gObjectFullbrightProgram; emissive_shader = &gObjectEmissiveProgram; } @@ -205,7 +221,7 @@ void LLDrawPoolAlpha::endRenderPass( S32 pass ) LLFastTimer t(FTM_RENDER_ALPHA); LLRenderPass::endRenderPass(pass); - if(mVertexShaderLevel > 0) + if(gPipeline.canUseWindLightShaders()) { LLGLSLShader::bindNoShader(); } @@ -225,45 +241,14 @@ void LLDrawPoolAlpha::render(S32 pass) { gGL.setColorMask(true, true); } + + bool write_depth = LLDrawPoolWater::sSkipScreenCopy + || (deferred_render && pass == 1) + // we want depth written so that rendered alpha will + // contribute to the alpha mask used for impostors + || LLPipeline::sImpostorRenderAlphaDepthPass; - if (LLPipeline::sAutoMaskAlphaNonDeferred) - { - mColorSFactor = LLRender::BF_ONE; // } - mColorDFactor = LLRender::BF_ZERO; // } these are like disabling blend on the color channels, but we're still blending on the alpha channel so that we can suppress glow - mAlphaSFactor = LLRender::BF_ZERO; - mAlphaDFactor = LLRender::BF_ZERO; // block (zero-out) glow where the alpha test succeeds - gGL.blendFunc(mColorSFactor, mColorDFactor, mAlphaSFactor, mAlphaDFactor); - - if (mVertexShaderLevel > 0) - { - if (!LLPipeline::sRenderDeferred || !deferred_render) - { - simple_shader->bind(); - simple_shader->setMinimumAlpha(0.33f); - - pushBatches(LLRenderPass::PASS_ALPHA_MASK, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE); - } - if (fullbright_shader) - { - fullbright_shader->bind(); - fullbright_shader->setMinimumAlpha(0.33f); - } - pushBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE); - //LLGLSLShader::bindNoShader(); - } - else - { - gGL.setAlphaRejectSettings(LLRender::CF_GREATER, 0.33f); //OK - gPipeline.enableLightsFullbright(LLColor4(1,1,1,1)); - pushBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, getVertexDataMask()); - gPipeline.enableLightsDynamic(); - pushBatches(LLRenderPass::PASS_ALPHA_MASK, getVertexDataMask()); - gGL.setAlphaRejectSettings(LLRender::CF_DEFAULT); //OK - } - } - - LLGLDepthTest depth(GL_TRUE, (LLDrawPoolWater::sSkipScreenCopy || - (deferred_render && pass == 1)) ? GL_TRUE : GL_FALSE); + LLGLDepthTest depth(GL_TRUE, write_depth ? GL_TRUE : GL_FALSE); if (deferred_render && pass == 1) { @@ -309,11 +294,11 @@ void LLDrawPoolAlpha::render(S32 pass) if (mVertexShaderLevel > 0) { - renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX); + renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, pass); } else { - renderAlpha(getVertexDataMask()); + renderAlpha(getVertexDataMask(), pass); } gGL.setColorMask(true, false); @@ -335,7 +320,7 @@ void LLDrawPoolAlpha::render(S32 pass) gPipeline.enableLightsFullbright(LLColor4(1,1,1,1)); } - gGL.diffuseColor4f(0.9,0,0,0.4); + gGL.diffuseColor4f(1,0,0,1); LLViewerFetchedTexture::sSmokeImagep->addTextureStats(1024.f*1024.f); gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sSmokeImagep, TRUE) ; @@ -386,7 +371,7 @@ void LLDrawPoolAlpha::renderAlphaHighlight(U32 mask) } } -void LLDrawPoolAlpha::renderAlpha(U32 mask) +void LLDrawPoolAlpha::renderAlpha(U32 mask, S32 pass) { BOOL initialized_lighting = FALSE; BOOL light_enabled = TRUE; @@ -402,13 +387,17 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask) if (group->mSpatialPartition->mRenderByGroup && !group->isDead()) { - bool draw_glow_for_this_partition = mVertexShaderLevel > 0 && // no shaders = no glow. - // All particle systems seem to come off the wire with texture entries which claim that they glow. This is probably a bug in the data. Suppress. - group->mSpatialPartition->mPartitionType != LLViewerRegion::PARTITION_PARTICLE && -#if ENABLE_CLASSIC_CLOUDS - group->mSpatialPartition->mPartitionType != LLViewerRegion::PARTITION_CLOUD && -#endif - group->mSpatialPartition->mPartitionType != LLViewerRegion::PARTITION_HUD_PARTICLE; + bool is_particle_or_hud_particle = group->mSpatialPartition->mPartitionType == LLViewerRegion::PARTITION_PARTICLE + || group->mSpatialPartition->mPartitionType == LLViewerRegion::PARTITION_CLOUD + || group->mSpatialPartition->mPartitionType == LLViewerRegion::PARTITION_HUD_PARTICLE; + + bool draw_glow_for_this_partition = mVertexShaderLevel > 0; // no shaders = no glow. + + static LLFastTimer::DeclareTimer FTM_RENDER_ALPHA_GROUP_LOOP("Alpha Group"); + LLFastTimer t(FTM_RENDER_ALPHA_GROUP_LOOP); + + bool disable_cull = is_particle_or_hud_particle; + LLGLDisable cull(disable_cull ? GL_CULL_FACE : 0); LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[LLRenderPass::PASS_ALPHA]; @@ -422,9 +411,32 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask) continue; } - LLRenderPass::applyModelMatrix(params); + // Fix for bug - NORSPEC-271 + // If the face is more than 90% transparent, then don't update the Depth buffer for Dof + // We don't want the nearly invisible objects to cause of DoF effects + if(pass == 1 && !LLPipeline::sImpostorRender) + { + LLFace* face = params.mFace; + if(face) + { + const LLTextureEntry* tep = face->getTextureEntry(); + if(tep) + { + if(tep->getColor().mV[3] < 0.1f) + continue; + } + } + } + LLRenderPass::applyModelMatrix(params); + LLMaterial* mat = NULL; + + if (deferred_render) + { + mat = params.mMaterial; + } + if (params.mFullbright) { // Turn off lighting if it hasn't already been so. @@ -455,13 +467,37 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask) gPipeline.enableLightsDynamic(); } light_enabled = TRUE; + } + + if (deferred_render && mat) + { + U32 mask = params.mShaderMask; + + llassert(mask < LLMaterial::SHADER_COUNT); + target_shader = &(gDeferredMaterialProgram[mask]); + + if (LLPipeline::sUnderWaterRender) + { + target_shader = &(gDeferredMaterialWaterProgram[mask]); } - // If we need shaders, and we're not ALREADY using the proper shader, then bind it - // (this way we won't rebind shaders unnecessarily). - if(use_shaders && (current_shader != target_shader)) + if (current_shader != target_shader) + { + gPipeline.bindDeferredShader(*target_shader); + } + } + else if (!params.mFullbright) { - llassert(target_shader != NULL); + target_shader = simple_shader; + } + else + { + target_shader = fullbright_shader; + } + + if(use_shaders && (current_shader != target_shader)) + {// If we need shaders, and we're not ALREADY using the proper shader, then bind it + // (this way we won't rebind shaders unnecessarily). current_shader = target_shader; current_shader->bind(); } @@ -470,6 +506,38 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask) LLGLSLShader::bindNoShader(); current_shader = NULL; } + + if (use_shaders && mat) + { + // We have a material. Supply the appropriate data here. + if (LLPipeline::sRenderDeferred) + { + current_shader->uniform4f(LLShaderMgr::SPECULAR_COLOR, params.mSpecColor.mV[0], params.mSpecColor.mV[1], params.mSpecColor.mV[2], params.mSpecColor.mV[3]); + current_shader->uniform1f(LLShaderMgr::ENVIRONMENT_INTENSITY, params.mEnvIntensity); + current_shader->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, params.mFullbright ? 1.f : 0.f); + + if (params.mNormalMap) + { + params.mNormalMap->addTextureStats(params.mVSize); + current_shader->bindTexture(LLShaderMgr::BUMP_MAP, params.mNormalMap); + } + + if (params.mSpecularMap) + { + params.mSpecularMap->addTextureStats(params.mVSize); + current_shader->bindTexture(LLShaderMgr::SPECULAR_MAP, params.mSpecularMap); + } + } + + } else if (LLPipeline::sRenderDeferred && current_shader && (current_shader == simple_shader)) + { + current_shader->uniform4f(LLShaderMgr::SPECULAR_COLOR, 1.0f, 1.0f, 1.0f, 1.0f); + current_shader->uniform1f(LLShaderMgr::ENVIRONMENT_INTENSITY, 0.0f); + LLViewerFetchedTexture::sFlatNormalImagep->addTextureStats(params.mVSize); + current_shader->bindTexture(LLShaderMgr::BUMP_MAP, LLViewerFetchedTexture::sFlatNormalImagep); + LLViewerFetchedTexture::sWhiteImagep->addTextureStats(params.mVSize); + current_shader->bindTexture(LLShaderMgr::SPECULAR_MAP, LLViewerFetchedTexture::sWhiteImagep); + } if (params.mGroup) { @@ -493,7 +561,15 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask) if (params.mTexture.notNull()) { params.mTexture->addTextureStats(params.mVSize); - gGL.getTexUnit(0)->bind(params.mTexture, TRUE) ; + if (use_shaders && mat) + { + current_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, params.mTexture); + } + else + { + gGL.getTexUnit(0)->bind(params.mTexture, TRUE); + } + if (params.mTextureMatrix) { tex_setup = true; @@ -509,9 +585,15 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask) } } - params.mVertexBuffer->setBuffer(mask); + static LLFastTimer::DeclareTimer FTM_RENDER_ALPHA_PUSH("Alpha Push Verts"); + { + LLFastTimer t(FTM_RENDER_ALPHA_PUSH); + gGL.blendFunc((LLRender::eBlendFactor) params.mBlendFuncSrc, (LLRender::eBlendFactor) params.mBlendFuncDst, mAlphaSFactor, mAlphaDFactor); + params.mVertexBuffer->setBuffer(mask & ~(params.mFullbright ? (LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2) : 0)); + params.mVertexBuffer->drawRange(params.mDrawMode, params.mStart, params.mEnd, params.mCount, params.mOffset); gPipeline.addTrianglesDrawn(params.mCount, params.mDrawMode); + } // If this alpha mesh has glow, then draw it a second time to add the destination-alpha (=glow). Interleaving these state-changing calls could be expensive, but glow must be drawn Z-sorted with alpha. if (current_shader && @@ -547,6 +629,8 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask) } } + gGL.setSceneBlendType(LLRender::BT_ALPHA); + LLVertexBuffer::unbind(); if (!light_enabled) diff --git a/indra/newview/lldrawpoolalpha.h b/indra/newview/lldrawpoolalpha.h index 570ed62e2..08e5068d4 100644 --- a/indra/newview/lldrawpoolalpha.h +++ b/indra/newview/lldrawpoolalpha.h @@ -56,11 +56,6 @@ public: LLDrawPoolAlpha(U32 type = LLDrawPool::POOL_ALPHA); /*virtual*/ ~LLDrawPoolAlpha(); - /*virtual*/ S32 getNumDeferredPasses(); - /*virtual*/ void beginDeferredPass(S32 pass); - /*virtual*/ void endDeferredPass(S32 pass); - /*virtual*/ void renderDeferred(S32 pass); - /*virtual*/ S32 getNumPostDeferredPasses(); /*virtual*/ void beginPostDeferredPass(S32 pass); /*virtual*/ void endPostDeferredPass(S32 pass); @@ -74,9 +69,9 @@ public: /*virtual*/ void prerender(); void renderGroupAlpha(LLSpatialGroup* group, U32 type, U32 mask, BOOL texture = TRUE); - void renderAlpha(U32 mask); + void renderAlpha(U32 mask, S32 pass); void renderAlphaHighlight(U32 mask); - + static BOOL sShowDebugAlpha; private: diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp index 2708a0538..9de4dee76 100644 --- a/indra/newview/lldrawpoolavatar.cpp +++ b/indra/newview/lldrawpoolavatar.cpp @@ -55,6 +55,7 @@ #include "llappviewer.h" #include "llrendersphere.h" #include "llviewerpartsim.h" +#include "llviewercontrol.h" // for gSavedSettings static U32 sDataMask = LLDrawPoolAvatar::VERTEX_DATA_MASK; static U32 sBufferUsage = GL_STREAM_DRAW_ARB; @@ -65,9 +66,11 @@ LLGLSLShader* LLDrawPoolAvatar::sVertexProgram = NULL; BOOL LLDrawPoolAvatar::sSkipOpaque = FALSE; BOOL LLDrawPoolAvatar::sSkipTransparent = FALSE; S32 LLDrawPoolAvatar::sDiffuseChannel = 0; +F32 LLDrawPoolAvatar::sMinimumAlpha = 0.2f; static bool is_deferred_render = false; +static bool is_post_deferred_render = false; extern BOOL gUseGLPick; @@ -197,6 +200,9 @@ void LLDrawPoolAvatar::beginDeferredPass(S32 pass) case 4: beginDeferredRiggedBump(); break; + default: + beginDeferredRiggedMaterial(pass-5); + break; } } @@ -229,6 +235,9 @@ void LLDrawPoolAvatar::endDeferredPass(S32 pass) case 4: endDeferredRiggedBump(); break; + default: + endDeferredRiggedMaterial(pass-5); + break; } } @@ -239,7 +248,7 @@ void LLDrawPoolAvatar::renderDeferred(S32 pass) S32 LLDrawPoolAvatar::getNumPostDeferredPasses() { - return 6; + return 10; } void LLDrawPoolAvatar::beginPostDeferredPass(S32 pass) @@ -261,9 +270,12 @@ void LLDrawPoolAvatar::beginPostDeferredPass(S32 pass) case 4: beginRiggedFullbrightAlpha(); break; - case 5: + case 9: beginRiggedGlow(); break; + default: + beginDeferredRiggedMaterialAlpha(pass-5); + break; } } @@ -276,7 +288,7 @@ void LLDrawPoolAvatar::beginPostDeferredAlpha() gPipeline.bindDeferredShader(*sVertexProgram); - sVertexProgram->setMinimumAlpha(0.2f); + sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); } @@ -289,11 +301,34 @@ void LLDrawPoolAvatar::beginDeferredRiggedAlpha() gPipeline.enableLightsDynamic(); } +void LLDrawPoolAvatar::beginDeferredRiggedMaterialAlpha(S32 pass) +{ + switch (pass) + { + case 0: pass = 1; break; + case 1: pass = 5; break; + case 2: pass = 9; break; + default: pass = 13; break; + } + pass += LLMaterial::SHADER_COUNT; + sVertexProgram = &gDeferredMaterialProgram[pass]; + if (LLPipeline::sUnderWaterRender) + { + sVertexProgram = &(gDeferredMaterialWaterProgram[pass]); + } + gPipeline.bindDeferredShader(*sVertexProgram); + sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + normal_channel = sVertexProgram->enableTexture(LLViewerShaderMgr::BUMP_MAP); + specular_channel = sVertexProgram->enableTexture(LLViewerShaderMgr::SPECULAR_MAP); + gPipeline.enableLightsDynamic(); +} void LLDrawPoolAvatar::endDeferredRiggedAlpha() { LLVertexBuffer::unbind(); gPipeline.unbindDeferredShader(*sVertexProgram); sDiffuseChannel = 0; + normal_channel = -1; + specular_channel = -1; sVertexProgram = NULL; } @@ -319,6 +354,9 @@ void LLDrawPoolAvatar::endPostDeferredPass(S32 pass) case 5: endRiggedGlow(); break; + default: + endDeferredRiggedAlpha(); + break; } } @@ -343,16 +381,22 @@ void LLDrawPoolAvatar::renderPostDeferred(S32 pass) 7, //rigged alpha 8, //rigged fullbright alpha 9, //rigged glow + 10,//rigged material alpha 2 + 11,//rigged material alpha 3 + 12,//rigged material alpha 4 + 13, //rigged glow }; - pass = actual_pass[pass]; + S32 p = actual_pass[pass]; if (LLPipeline::sImpostorRender) { //HACK for impostors so actual pass ends up being proper pass - pass -= 2; + p -= 2; } - render(pass); + is_post_deferred_render = true; + render(p); + is_post_deferred_render = false; } @@ -439,12 +483,10 @@ void LLDrawPoolAvatar::renderShadow(S32 pass) } else { - renderRigged(avatarp, RIGGED_SIMPLE); - renderRigged(avatarp, RIGGED_ALPHA); - renderRigged(avatarp, RIGGED_FULLBRIGHT); - renderRigged(avatarp, RIGGED_FULLBRIGHT_SHINY); - renderRigged(avatarp, RIGGED_SHINY); - renderRigged(avatarp, RIGGED_FULLBRIGHT_ALPHA); + for (U32 i = 0; i < NUM_RIGGED_PASSES; ++i) + { + renderRigged(avatarp, i); + } } } @@ -465,11 +507,11 @@ S32 LLDrawPoolAvatar::getNumDeferredPasses() { if (LLPipeline::sImpostorRender) { - return 3; + return 19; } else { - return 5; + return 21; } } @@ -624,7 +666,7 @@ void LLDrawPoolAvatar::beginRigid() if (sVertexProgram != NULL) { //eyeballs render with the specular shader sVertexProgram->bind(); - sVertexProgram->setMinimumAlpha(0.2f); + sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); } } else @@ -666,8 +708,9 @@ void LLDrawPoolAvatar::endDeferredImpostor() sVertexProgram->disableTexture(LLViewerShaderMgr::DEFERRED_NORMAL); sVertexProgram->disableTexture(LLViewerShaderMgr::SPECULAR_MAP); sVertexProgram->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - sVertexProgram->unbind(); - gGL.getTexUnit(0)->activate(); + gPipeline.unbindDeferredShader(*sVertexProgram); + sVertexProgram = NULL; + sDiffuseChannel = 0; } void LLDrawPoolAvatar::beginDeferredRigid() @@ -675,7 +718,7 @@ void LLDrawPoolAvatar::beginDeferredRigid() sVertexProgram = &gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram; sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); sVertexProgram->bind(); - sVertexProgram->setMinimumAlpha(0.2f); + sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); } void LLDrawPoolAvatar::endDeferredRigid() @@ -733,7 +776,7 @@ void LLDrawPoolAvatar::beginSkinned() if (LLGLSLShader::sNoFixedFunction) { - sVertexProgram->setMinimumAlpha(0.2f); + sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); } } @@ -853,6 +896,9 @@ void LLDrawPoolAvatar::beginRiggedGlow() { sDiffuseChannel = 0; sVertexProgram->bind(); + sVertexProgram->uniform1f(LLShaderMgr::TEXTURE_GAMMA, LLPipeline::sRenderDeferred ? 2.2f : 1.1f); + //F32 gamma = gSavedSettings.getF32("RenderDeferredDisplayGamma"); + //sVertexProgram->uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f/2.2f)); } } @@ -871,7 +917,14 @@ void LLDrawPoolAvatar::beginRiggedFullbright() } else { + if (LLPipeline::sRenderDeferred) + { + sVertexProgram = &gDeferredSkinnedFullbrightProgram; + } + else + { sVertexProgram = &gSkinnedObjectFullbrightProgram; + } } } else @@ -890,6 +943,16 @@ void LLDrawPoolAvatar::beginRiggedFullbright() { sDiffuseChannel = 0; sVertexProgram->bind(); + if (LLPipeline::sRenderingHUDs || !LLPipeline::sRenderDeferred) + { + sVertexProgram->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 1.0f); + } + else + { + sVertexProgram->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); + //F32 gamma = gSavedSettings.getF32("RenderDeferredDisplayGamma"); + //sVertexProgram->uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f/2.2f)); + } } } @@ -956,7 +1019,14 @@ void LLDrawPoolAvatar::beginRiggedFullbrightShiny() } else { + if (LLPipeline::sRenderDeferred) + { + sVertexProgram = &gDeferredSkinnedFullbrightShinyProgram; + } + else + { sVertexProgram = &gSkinnedObjectFullbrightShinyProgram; + } } } else @@ -976,6 +1046,16 @@ void LLDrawPoolAvatar::beginRiggedFullbrightShiny() { sVertexProgram->bind(); LLDrawPoolBump::bindCubeMap(sVertexProgram, 2, sDiffuseChannel, cube_channel, false); + if (LLPipeline::sRenderingHUDs || !LLPipeline::sRenderDeferred) + { + sVertexProgram->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 1.0f); + } + else + { + sVertexProgram->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); + //F32 gamma = gSavedSettings.getF32("RenderDeferredDisplayGamma"); + //sVertexProgram->uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f/2.2f)); + } } } @@ -1024,6 +1104,43 @@ void LLDrawPoolAvatar::endDeferredRiggedBump() sVertexProgram = NULL; } +void LLDrawPoolAvatar::beginDeferredRiggedMaterial(S32 pass) +{ + if (pass == 1 || + pass == 5 || + pass == 9 || + pass == 13) + { //skip alpha passes + return; + } + sVertexProgram = &gDeferredMaterialProgram[pass+LLMaterial::SHADER_COUNT]; + if (LLPipeline::sUnderWaterRender) + { + sVertexProgram = &(gDeferredMaterialWaterProgram[pass+LLMaterial::SHADER_COUNT]); + } + sVertexProgram->bind(); + normal_channel = sVertexProgram->enableTexture(LLViewerShaderMgr::BUMP_MAP); + specular_channel = sVertexProgram->enableTexture(LLViewerShaderMgr::SPECULAR_MAP); + sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); +} +void LLDrawPoolAvatar::endDeferredRiggedMaterial(S32 pass) +{ + if (pass == 1 || + pass == 5 || + pass == 9 || + pass == 13) + { + return; + } + LLVertexBuffer::unbind(); + sVertexProgram->disableTexture(LLViewerShaderMgr::BUMP_MAP); + sVertexProgram->disableTexture(LLViewerShaderMgr::SPECULAR_MAP); + sVertexProgram->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + sVertexProgram->unbind(); + normal_channel = -1; + sDiffuseChannel = 0; + sVertexProgram = NULL; +} void LLDrawPoolAvatar::beginDeferredSkinned() { sShaderLevel = mVertexShaderLevel; @@ -1031,7 +1148,7 @@ void LLDrawPoolAvatar::beginDeferredSkinned() sRenderingSkinned = TRUE; sVertexProgram->bind(); - sVertexProgram->setMinimumAlpha(0.2f); + sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); gGL.getTexUnit(0)->activate(); @@ -1184,6 +1301,21 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) else { renderRiggedSimple(avatarp); + if (LLPipeline::sRenderDeferred) + { //render "simple" materials + renderRigged(avatarp, RIGGED_MATERIAL); + renderRigged(avatarp, RIGGED_MATERIAL_ALPHA_MASK); + renderRigged(avatarp, RIGGED_MATERIAL_ALPHA_EMISSIVE); + renderRigged(avatarp, RIGGED_NORMMAP); + renderRigged(avatarp, RIGGED_NORMMAP_MASK); + renderRigged(avatarp, RIGGED_NORMMAP_EMISSIVE); + renderRigged(avatarp, RIGGED_SPECMAP); + renderRigged(avatarp, RIGGED_SPECMAP_MASK); + renderRigged(avatarp, RIGGED_SPECMAP_EMISSIVE); + renderRigged(avatarp, RIGGED_NORMSPEC); + renderRigged(avatarp, RIGGED_NORMSPEC_MASK); + renderRigged(avatarp, RIGGED_NORMSPEC_EMISSIVE); + } } return; } @@ -1202,6 +1334,18 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) return; } + if (is_deferred_render && pass >= 5 && pass <= 21) + { + S32 p = pass-5; + if (p != 1 && + p != 5 && + p != 9 && + p != 13) + { + renderDeferredRiggedMaterial(avatarp, p); + } + return; + } if (pass == 5) { renderRiggedShinySimple(avatarp); @@ -1214,11 +1358,26 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) return; } - if (pass >= 7 && pass < 9) + if (pass >= 7 && pass < 13) { if (pass == 7) { renderRiggedAlpha(avatarp); + if (LLPipeline::sRenderDeferred && !is_post_deferred_render) + { //render transparent materials under water + LLGLEnable blend(GL_BLEND); + gGL.setColorMask(true, true); + gGL.blendFunc(LLRender::BF_SOURCE_ALPHA, + LLRender::BF_ONE_MINUS_SOURCE_ALPHA, + LLRender::BF_ZERO, + LLRender::BF_ONE_MINUS_SOURCE_ALPHA); + renderRigged(avatarp, RIGGED_MATERIAL_ALPHA); + renderRigged(avatarp, RIGGED_SPECMAP_BLEND); + renderRigged(avatarp, RIGGED_NORMMAP_BLEND); + renderRigged(avatarp, RIGGED_NORMSPEC_BLEND); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + gGL.setColorMask(true, false); + } return; } @@ -1227,9 +1386,30 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) renderRiggedFullbrightAlpha(avatarp); return; } + if (LLPipeline::sRenderDeferred && is_post_deferred_render) + { + S32 p = 0; + switch (pass) + { + case 9: p = 1; break; + case 10: p = 5; break; + case 11: p = 9; break; + case 12: p = 13; break; + } + { + LLGLEnable blend(GL_BLEND); + renderDeferredRiggedMaterial(avatarp, p); + } + return; + } + else if (pass == 9) + { + renderRiggedGlow(avatarp); + return; + } } - if (pass == 9) + if (pass == 13) { renderRiggedGlow(avatarp); @@ -1267,27 +1447,10 @@ void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) } } -void LLDrawPoolAvatar::updateRiggedFaceVertexBuffer(LLVOAvatar* avatar, LLFace* face, const LLMeshSkinInfo* skin, LLVolume* volume, const LLVolumeFace& vol_face) +void LLDrawPoolAvatar::getRiggedGeometry(LLFace* face, LLPointer& buffer, U32 data_mask, const LLMeshSkinInfo* skin, LLVolume* volume, const LLVolumeFace& vol_face) { - LLVector4a* weight = vol_face.mWeights; - if (!weight) - { - return; - } - - LLPointer buffer = face->getVertexBuffer(); - LLDrawable* drawable = face->getDrawable(); - - U32 data_mask = face->getRiggedVertexBufferDataMask(); - - if (buffer.isNull() || - buffer->getTypeMask() != data_mask || - buffer->getNumVerts() != vol_face.mNumVertices || - buffer->getNumIndices() != vol_face.mNumIndices || - (drawable && drawable->isState(LLDrawable::REBUILD_ALL))) - { - face->setGeomIndex(0); - face->setIndicesIndex(0); + face->setGeomIndex(0); + face->setIndicesIndex(0); //rigged faces do not batch textures face->setTextureIndex(255); @@ -1334,12 +1497,55 @@ void LLDrawPoolAvatar::updateRiggedFaceVertexBuffer(LLVOAvatar* avatar, LLFace* } else { - face->setPoolType(LLDrawPool::POOL_AVATAR); + face->setPoolType(LLDrawPool::POOL_AVATAR); + } + + //llinfos << "Rebuilt face " << face->getTEOffset() << " of " << face->getDrawable() << " at " << gFrameTimeSeconds << llendl; + face->getGeometryVolume(*volume, face->getTEOffset(), mat_vert, mat_normal, offset, true); + + buffer->flush(); +} + +void LLDrawPoolAvatar::updateRiggedFaceVertexBuffer(LLVOAvatar* avatar, LLFace* face, const LLMeshSkinInfo* skin, LLVolume* volume, const LLVolumeFace& vol_face) +{ + LLVector4a* weight = vol_face.mWeights; + if (!weight) + { + return; + } + + LLPointer buffer = face->getVertexBuffer(); + LLDrawable* drawable = face->getDrawable(); + + U32 data_mask = face->getRiggedVertexBufferDataMask(); + + if (buffer.isNull() || + buffer->getTypeMask() != data_mask || + buffer->getNumVerts() != vol_face.mNumVertices || + buffer->getNumIndices() != vol_face.mNumIndices || + (drawable && drawable->isState(LLDrawable::REBUILD_ALL))) + { + if (drawable && drawable->isState(LLDrawable::REBUILD_ALL)) + { //rebuild EVERY face in the drawable, not just this one, to avoid missing drawable wide rebuild issues + for (S32 i = 0; i < drawable->getNumFaces(); ++i) + { + LLFace* facep = drawable->getFace(i); + U32 face_data_mask = facep->getRiggedVertexBufferDataMask(); + if (face_data_mask) + { + LLPointer cur_buffer = facep->getVertexBuffer(); + const LLVolumeFace& cur_vol_face = volume->getVolumeFace(i); + getRiggedGeometry(facep, cur_buffer, face_data_mask, skin, volume, cur_vol_face); + } + } + drawable->clearState(LLDrawable::REBUILD_ALL); + + buffer = face->getVertexBuffer(); + } + else + { //just rebuild this face + getRiggedGeometry(face, buffer, data_mask, skin, volume, vol_face); } - - face->getGeometryVolume(*volume, face->getTEOffset(), mat_vert, mat_normal, offset, true); - - buffer->flush(); } if (sShaderLevel <= 0 && face->mLastSkinTime < avatar->getLastSkinTime()) @@ -1426,11 +1632,6 @@ void LLDrawPoolAvatar::updateRiggedFaceVertexBuffer(LLVOAvatar* avatar, LLFace* } } } - - if (drawable && (face->getTEOffset() == drawable->getNumFaces()-1)) - { - drawable->clearState(LLDrawable::REBUILD_ALL); - } } void LLDrawPoolAvatar::renderRigged(LLVOAvatar* avatar, U32 type, bool glow) @@ -1531,13 +1732,62 @@ void LLDrawPoolAvatar::renderRigged(LLVOAvatar* avatar, U32 type, bool glow) gGL.diffuseColor4f(0,0,0,face->getTextureEntry()->getGlow()); }*/ - gGL.getTexUnit(sDiffuseChannel)->bind(face->getTexture()); - if (normal_channel > -1) + const LLTextureEntry* te = face->getTextureEntry(); + LLMaterial* mat = te->getMaterialParams().get(); + + if (mat) { - LLDrawPoolBump::bindBumpMap(face, normal_channel); + gGL.getTexUnit(sDiffuseChannel)->bind(face->getTexture(LLRender::DIFFUSE_MAP)); + gGL.getTexUnit(normal_channel)->bind(face->getTexture(LLRender::NORMAL_MAP)); + gGL.getTexUnit(specular_channel)->bind(face->getTexture(LLRender::SPECULAR_MAP)); + + LLColor4 col = mat->getSpecularLightColor(); + F32 spec = mat->getSpecularLightExponent()/255.f; + + F32 env = mat->getEnvironmentIntensity()/255.f; + + if (mat->getSpecularID().isNull()) + { + env = te->getShiny()*0.25f; + col.set(env,env,env,0); + spec = env; + } + + BOOL fullbright = te->getFullbright(); + + sVertexProgram->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, fullbright ? 1.f : 0.f); + sVertexProgram->uniform4f(LLShaderMgr::SPECULAR_COLOR, col.mV[0], col.mV[1], col.mV[2], spec); + sVertexProgram->uniform1f(LLShaderMgr::ENVIRONMENT_INTENSITY, env); + + if (mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) + { + sVertexProgram->setMinimumAlpha(mat->getAlphaMaskCutoff()/255.f); + } + else + { + sVertexProgram->setMinimumAlpha(0.f); + } + + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + { + LLViewerTexture* tex = face->getTexture(i); + if (tex) + { + tex->addTextureStats(avatar->getPixelArea()); + } + } + } + else + { + gGL.getTexUnit(sDiffuseChannel)->bind(face->getTexture()); + sVertexProgram->setMinimumAlpha(0.f); + if (normal_channel > -1) + { + LLDrawPoolBump::bindBumpMap(face, normal_channel); + } } - if (face->mTextureMatrix) + if (face->mTextureMatrix && vobj->mTexAnimMode) { gGL.matrixMode(LLRender::MM_TEXTURE); gGL.loadMatrix((F32*) face->mTextureMatrix->mMatrix); @@ -1551,6 +1801,8 @@ void LLDrawPoolAvatar::renderRigged(LLVOAvatar* avatar, U32 type, bool glow) buff->setBuffer(data_mask); buff->drawRange(LLRender::TRIANGLES, start, end, count, offset); } + + gPipeline.addTrianglesDrawn(count, LLRender::TRIANGLES); } } } @@ -1565,6 +1817,11 @@ void LLDrawPoolAvatar::renderDeferredRiggedBump(LLVOAvatar* avatar) renderRigged(avatar, RIGGED_DEFERRED_BUMP); } +void LLDrawPoolAvatar::renderDeferredRiggedMaterial(LLVOAvatar* avatar, S32 pass) +{ + renderRigged(avatar, pass); +} + static LLFastTimer::DeclareTimer FTM_RIGGED_VBO("Rigged VBO"); void LLDrawPoolAvatar::updateRiggedVertexBuffers(LLVOAvatar* avatar) diff --git a/indra/newview/lldrawpoolavatar.h b/indra/newview/lldrawpoolavatar.h index 4b93f47e8..9569a9b1f 100644 --- a/indra/newview/lldrawpoolavatar.h +++ b/indra/newview/lldrawpoolavatar.h @@ -119,6 +119,8 @@ public: void beginRiggedFullbrightAlpha(); void beginRiggedGlow(); void beginDeferredRiggedAlpha(); + void beginDeferredRiggedMaterial(S32 pass); + void beginDeferredRiggedMaterialAlpha(S32 pass); void endRiggedSimple(); void endRiggedFullbright(); @@ -128,6 +130,8 @@ public: void endRiggedFullbrightAlpha(); void endRiggedGlow(); void endDeferredRiggedAlpha(); + void endDeferredRiggedMaterial(S32 pass); + void endDeferredRiggedMaterialAlpha(S32 pass); void beginDeferredRiggedSimple(); void beginDeferredRiggedBump(); @@ -135,6 +139,7 @@ public: void endDeferredRiggedSimple(); void endDeferredRiggedBump(); + void getRiggedGeometry(LLFace* face, LLPointer& buffer, U32 data_mask, const LLMeshSkinInfo* skin, LLVolume* volume, const LLVolumeFace& vol_face); void updateRiggedFaceVertexBuffer(LLVOAvatar* avatar, LLFace* facep, const LLMeshSkinInfo* skin, @@ -152,10 +157,27 @@ public: void renderRiggedGlow(LLVOAvatar* avatar); void renderDeferredRiggedSimple(LLVOAvatar* avatar); void renderDeferredRiggedBump(LLVOAvatar* avatar); - + void renderDeferredRiggedMaterial(LLVOAvatar* avatar, S32 pass); + typedef enum { - RIGGED_SIMPLE = 0, + RIGGED_MATERIAL=0, + RIGGED_MATERIAL_ALPHA, + RIGGED_MATERIAL_ALPHA_MASK, + RIGGED_MATERIAL_ALPHA_EMISSIVE, + RIGGED_SPECMAP, + RIGGED_SPECMAP_BLEND, + RIGGED_SPECMAP_MASK, + RIGGED_SPECMAP_EMISSIVE, + RIGGED_NORMMAP, + RIGGED_NORMMAP_BLEND, + RIGGED_NORMMAP_MASK, + RIGGED_NORMMAP_EMISSIVE, + RIGGED_NORMSPEC, + RIGGED_NORMSPEC_BLEND, + RIGGED_NORMSPEC_MASK, + RIGGED_NORMSPEC_EMISSIVE, + RIGGED_SIMPLE, RIGGED_FULLBRIGHT, RIGGED_SHINY, RIGGED_FULLBRIGHT_SHINY, @@ -170,6 +192,48 @@ public: typedef enum { + RIGGED_MATERIAL_MASK = + LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_TEXCOORD0 | + LLVertexBuffer::MAP_COLOR | + LLVertexBuffer::MAP_WEIGHT4, + RIGGED_MATERIAL_ALPHA_VMASK = RIGGED_MATERIAL_MASK, + RIGGED_MATERIAL_ALPHA_MASK_MASK = RIGGED_MATERIAL_MASK, + RIGGED_MATERIAL_ALPHA_EMISSIVE_MASK = RIGGED_MATERIAL_MASK, + RIGGED_SPECMAP_VMASK = + LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_TEXCOORD0 | + LLVertexBuffer::MAP_TEXCOORD2 | + LLVertexBuffer::MAP_COLOR | + LLVertexBuffer::MAP_WEIGHT4, + RIGGED_SPECMAP_BLEND_MASK = RIGGED_SPECMAP_VMASK, + RIGGED_SPECMAP_MASK_MASK = RIGGED_SPECMAP_VMASK, + RIGGED_SPECMAP_EMISSIVE_MASK = RIGGED_SPECMAP_VMASK, + RIGGED_NORMMAP_VMASK = + LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_TANGENT | + LLVertexBuffer::MAP_TEXCOORD0 | + LLVertexBuffer::MAP_TEXCOORD1 | + LLVertexBuffer::MAP_COLOR | + LLVertexBuffer::MAP_WEIGHT4, + RIGGED_NORMMAP_BLEND_MASK = RIGGED_NORMMAP_VMASK, + RIGGED_NORMMAP_MASK_MASK = RIGGED_NORMMAP_VMASK, + RIGGED_NORMMAP_EMISSIVE_MASK = RIGGED_NORMMAP_VMASK, + RIGGED_NORMSPEC_VMASK = + LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_TANGENT | + LLVertexBuffer::MAP_TEXCOORD0 | + LLVertexBuffer::MAP_TEXCOORD1 | + LLVertexBuffer::MAP_TEXCOORD2 | + LLVertexBuffer::MAP_COLOR | + LLVertexBuffer::MAP_WEIGHT4, + RIGGED_NORMSPEC_BLEND_MASK = RIGGED_NORMSPEC_VMASK, + RIGGED_NORMSPEC_MASK_MASK = RIGGED_NORMSPEC_VMASK, + RIGGED_NORMSPEC_EMISSIVE_MASK = RIGGED_NORMSPEC_VMASK, RIGGED_SIMPLE_MASK = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0 | @@ -214,6 +278,7 @@ public: static BOOL sSkipOpaque; static BOOL sSkipTransparent; static S32 sDiffuseChannel; + static F32 sMinimumAlpha; static LLGLSLShader* sVertexProgram; }; diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp index 259c0e495..d9eac7b7b 100644 --- a/indra/newview/lldrawpoolbump.cpp +++ b/indra/newview/lldrawpoolbump.cpp @@ -456,9 +456,6 @@ void LLDrawPoolBump::unbindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; if( cube_map ) { - cube_map->disable(); - cube_map->restoreMatrix(); - if (!invisible && shader_level > 1) { shader->disableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); @@ -471,6 +468,10 @@ void LLDrawPoolBump::unbindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& } } } + // Moved below shader->disableTexture call to avoid false alarms from auto-re-enable of textures on stage 0 + // MAINT-755 + cube_map->disable(); + cube_map->restoreMatrix(); } if (!LLGLSLShader::sNoFixedFunction) @@ -521,7 +522,14 @@ void LLDrawPoolBump::beginFullbrightShiny() } else { - shader = &gObjectFullbrightShinyProgram; + if (LLPipeline::sRenderDeferred) + { + shader = &gDeferredFullbrightShinyProgram; + } + else + { + shader = &gObjectFullbrightShinyProgram; + } } LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; @@ -1381,7 +1389,7 @@ void LLBumpImageList::onSourceLoaded( BOOL success, LLViewerTexture *src_vi, LLI gNormalMapGenProgram.uniform1f(sNormScale, gSavedSettings.getF32("RenderNormalMapScale")); gNormalMapGenProgram.uniform1f(sStepX, 1.f/bump->getWidth()); - gNormalMapGenProgram.uniform1f(sStepX, 1.f/bump->getHeight()); + gNormalMapGenProgram.uniform1f(sStepY, 1.f/bump->getHeight()); LLVector2 v((F32) bump->getWidth()/gPipeline.mScreen.getWidth(), (F32) bump->getHeight()/gPipeline.mScreen.getHeight()); diff --git a/indra/newview/lldrawpoolmaterials.cpp b/indra/newview/lldrawpoolmaterials.cpp new file mode 100644 index 000000000..d1b508065 --- /dev/null +++ b/indra/newview/lldrawpoolmaterials.cpp @@ -0,0 +1,224 @@ +/** + * @file lldrawpool.cpp + * @brief LLDrawPoolMaterials class implementation + * @author Jonathan "Geenz" Goodman + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldrawpoolmaterials.h" +#include "llviewershadermgr.h" +#include "pipeline.h" + +S32 diffuse_channel = -1; + +LLDrawPoolMaterials::LLDrawPoolMaterials() +: LLRenderPass(LLDrawPool::POOL_MATERIALS) +{ + +} + +void LLDrawPoolMaterials::prerender() +{ + mVertexShaderLevel = LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); +} + +S32 LLDrawPoolMaterials::getNumDeferredPasses() +{ + return 12; +} + +void LLDrawPoolMaterials::beginDeferredPass(S32 pass) +{ + U32 shader_idx[] = + { + 0, //LLRenderPass::PASS_MATERIAL, + //1, //LLRenderPass::PASS_MATERIAL_ALPHA, + 2, //LLRenderPass::PASS_MATERIAL_ALPHA_MASK, + 3, //LLRenderPass::PASS_MATERIAL_ALPHA_GLOW, + 4, //LLRenderPass::PASS_SPECMAP, + //5, //LLRenderPass::PASS_SPECMAP_BLEND, + 6, //LLRenderPass::PASS_SPECMAP_MASK, + 7, //LLRenderPass::PASS_SPECMAP_GLOW, + 8, //LLRenderPass::PASS_NORMMAP, + //9, //LLRenderPass::PASS_NORMMAP_BLEND, + 10, //LLRenderPass::PASS_NORMMAP_MASK, + 11, //LLRenderPass::PASS_NORMMAP_GLOW, + 12, //LLRenderPass::PASS_NORMSPEC, + //13, //LLRenderPass::PASS_NORMSPEC_BLEND, + 14, //LLRenderPass::PASS_NORMSPEC_MASK, + 15, //LLRenderPass::PASS_NORMSPEC_GLOW, + }; + + mShader = &(gDeferredMaterialProgram[shader_idx[pass]]); + + if (LLPipeline::sUnderWaterRender) + { + mShader = &(gDeferredMaterialWaterProgram[shader_idx[pass]]); + } + + mShader->bind(); + + diffuse_channel = mShader->enableTexture(LLShaderMgr::DIFFUSE_MAP); + + LLFastTimer t(FTM_RENDER_MATERIALS); +} + +void LLDrawPoolMaterials::endDeferredPass(S32 pass) +{ + LLFastTimer t(FTM_RENDER_MATERIALS); + + mShader->unbind(); + + LLRenderPass::endRenderPass(pass); +} + +void LLDrawPoolMaterials::renderDeferred(S32 pass) +{ + U32 type_list[] = + { + LLRenderPass::PASS_MATERIAL, + //LLRenderPass::PASS_MATERIAL_ALPHA, + LLRenderPass::PASS_MATERIAL_ALPHA_MASK, + LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, + LLRenderPass::PASS_SPECMAP, + //LLRenderPass::PASS_SPECMAP_BLEND, + LLRenderPass::PASS_SPECMAP_MASK, + LLRenderPass::PASS_SPECMAP_EMISSIVE, + LLRenderPass::PASS_NORMMAP, + //LLRenderPass::PASS_NORMMAP_BLEND, + LLRenderPass::PASS_NORMMAP_MASK, + LLRenderPass::PASS_NORMMAP_EMISSIVE, + LLRenderPass::PASS_NORMSPEC, + //LLRenderPass::PASS_NORMSPEC_BLEND, + LLRenderPass::PASS_NORMSPEC_MASK, + LLRenderPass::PASS_NORMSPEC_EMISSIVE, + }; + + llassert(pass < sizeof(type_list)/sizeof(U32)); + + U32 type = type_list[pass]; + + U32 mask = mShader->mAttributeMask; + + LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type); + LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type); + + for (LLCullResult::drawinfo_iterator i = begin; i != end; ++i) + { + LLDrawInfo& params = **i; + + mShader->uniform4f(LLShaderMgr::SPECULAR_COLOR, params.mSpecColor.mV[0], params.mSpecColor.mV[1], params.mSpecColor.mV[2], params.mSpecColor.mV[3]); + mShader->uniform1f(LLShaderMgr::ENVIRONMENT_INTENSITY, params.mEnvIntensity); + + if (params.mNormalMap) + { + params.mNormalMap->addTextureStats(params.mVSize); + bindNormalMap(params.mNormalMap); + } + + if (params.mSpecularMap) + { + params.mSpecularMap->addTextureStats(params.mVSize); + bindSpecularMap(params.mSpecularMap); + } + + mShader->setMinimumAlpha(params.mAlphaMaskCutoff); + mShader->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, params.mFullbright ? 1.f : 0.f); + + pushBatch(params, mask, TRUE); + } +} + +void LLDrawPoolMaterials::bindSpecularMap(LLViewerTexture* tex) +{ + mShader->bindTexture(LLShaderMgr::SPECULAR_MAP, tex); +} + +void LLDrawPoolMaterials::bindNormalMap(LLViewerTexture* tex) +{ + mShader->bindTexture(LLShaderMgr::BUMP_MAP, tex); +} + +void LLDrawPoolMaterials::pushBatch(LLDrawInfo& params, U32 mask, BOOL texture, BOOL batch_textures) +{ + applyModelMatrix(params); + + bool tex_setup = false; + + if (batch_textures && params.mTextureList.size() > 1) + { + for (U32 i = 0; i < params.mTextureList.size(); ++i) + { + if (params.mTextureList[i].notNull()) + { + gGL.getTexUnit(i)->bind(params.mTextureList[i], TRUE); + } + } + } + else + { //not batching textures or batch has only 1 texture -- might need a texture matrix + if (params.mTextureMatrix) + { + //if (mShiny) + { + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + } + + gGL.loadMatrix((GLfloat*) params.mTextureMatrix->mMatrix); + gPipeline.mTextureMatrixOps++; + + tex_setup = true; + } + + if (mVertexShaderLevel > 1 && texture) + { + if (params.mTexture.notNull()) + { + gGL.getTexUnit(diffuse_channel)->bind(params.mTexture); + params.mTexture->addTextureStats(params.mVSize); + } + else + { + gGL.getTexUnit(diffuse_channel)->unbind(LLTexUnit::TT_TEXTURE); + } + } + } + + if (params.mGroup) + { + params.mGroup->rebuildMesh(); + } + params.mVertexBuffer->setBuffer(mask); + params.mVertexBuffer->drawRange(params.mDrawMode, params.mStart, params.mEnd, params.mCount, params.mOffset); + gPipeline.addTrianglesDrawn(params.mCount, params.mDrawMode); + if (tex_setup) + { + gGL.getTexUnit(0)->activate(); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + } +} + diff --git a/indra/newview/lldrawpoolmaterials.h b/indra/newview/lldrawpoolmaterials.h new file mode 100644 index 000000000..eae1aba87 --- /dev/null +++ b/indra/newview/lldrawpoolmaterials.h @@ -0,0 +1,75 @@ +/** + * @file lldrawpoolmaterials.h + * @brief LLDrawPoolMaterials class definition + * @author Jonathan "Geenz" Goodman + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLDRAWPOOLMATERIALS_H +#define LL_LLDRAWPOOLMATERIALS_H + +#include "v4coloru.h" +#include "v2math.h" +#include "v3math.h" +#include "llvertexbuffer.h" +#include "lldrawpool.h" + +class LLViewerTexture; +class LLDrawInfo; +class LLGLSLShader; + +class LLDrawPoolMaterials : public LLRenderPass +{ + LLGLSLShader *mShader; +public: + LLDrawPoolMaterials(); + + enum + { + VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_TEXCOORD0 | + LLVertexBuffer::MAP_TEXCOORD1 | + LLVertexBuffer::MAP_TEXCOORD2 | + LLVertexBuffer::MAP_COLOR | + LLVertexBuffer::MAP_TANGENT + }; + + /*virtual*/ U32 getVertexDataMask() { return VERTEX_DATA_MASK; } + + /*virtual*/ void render(S32 pass = 0) { } + /*virtual*/ S32 getNumPasses() {return 0;} + /*virtual*/ void prerender(); + + /*virtual*/ S32 getNumDeferredPasses(); + /*virtual*/ void beginDeferredPass(S32 pass); + /*virtual*/ void endDeferredPass(S32 pass); + /*virtual*/ void renderDeferred(S32 pass); + + void bindSpecularMap(LLViewerTexture* tex); + void bindNormalMap(LLViewerTexture* tex); + + /*virtual*/ void pushBatch(LLDrawInfo& params, U32 mask, BOOL texture, BOOL batch_textures = FALSE); +}; + +#endif //LL_LLDRAWPOOLMATERIALS_H diff --git a/indra/newview/lldrawpoolsimple.cpp b/indra/newview/lldrawpoolsimple.cpp index 4bc06580f..638a9dfa0 100644 --- a/indra/newview/lldrawpoolsimple.cpp +++ b/indra/newview/lldrawpoolsimple.cpp @@ -37,6 +37,7 @@ #include "llviewershadermgr.h" #include "llrender.h" +#define GE_FORCE_WORKAROUND LL_DARWIN static LLGLSLShader* simple_shader = NULL; static LLGLSLShader* fullbright_shader = NULL; @@ -47,6 +48,7 @@ static LLFastTimer::DeclareTimer FTM_RENDER_GRASS_DEFERRED("Deferred Grass"); void LLDrawPoolGlow::beginPostDeferredPass(S32 pass) { gDeferredEmissiveProgram.bind(); + gDeferredEmissiveProgram.uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); } static LLFastTimer::DeclareTimer FTM_RENDER_GLOW_PUSH("Glow Push"); @@ -110,6 +112,14 @@ void LLDrawPoolGlow::render(S32 pass) LLGLSLShader* shader = LLPipeline::sUnderWaterRender ? &gObjectEmissiveWaterProgram : &gObjectEmissiveProgram; shader->bind(); + if (LLPipeline::sRenderDeferred) + { + shader->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); + } + else + { + shader->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 1.f); + } LLGLDepthTest depth(GL_TRUE, GL_FALSE); gGL.setColorMask(false, true); @@ -146,7 +156,11 @@ void LLDrawPoolSimple::beginRenderPass(S32 pass) { LLFastTimer t(FTM_RENDER_SIMPLE); - if (LLPipeline::sUnderWaterRender) + if (LLPipeline::sImpostorRender) + { + simple_shader = &gObjectSimpleImpostorProgram; + } + else if (LLPipeline::sUnderWaterRender) { simple_shader = &gObjectSimpleWaterProgram; } @@ -198,7 +212,11 @@ void LLDrawPoolSimple::render(S32 pass) if (LLPipeline::sRenderDeferred) { //if deferred rendering is enabled, bump faces aren't registered as simple //render bump faces here as simple so bump faces will appear under water - pushBatches(LLRenderPass::PASS_BUMP, mask, TRUE, TRUE); + pushBatches(LLRenderPass::PASS_BUMP, mask, TRUE, TRUE); + pushBatches(LLRenderPass::PASS_MATERIAL, mask, TRUE, TRUE); + pushBatches(LLRenderPass::PASS_SPECMAP, mask, TRUE, TRUE); + pushBatches(LLRenderPass::PASS_NORMMAP, mask, TRUE, TRUE); + pushBatches(LLRenderPass::PASS_NORMSPEC, mask, TRUE, TRUE); } } else @@ -210,6 +228,169 @@ void LLDrawPoolSimple::render(S32 pass) } } + + + + + + + + + +static LLFastTimer::DeclareTimer FTM_RENDER_ALPHA_MASK("Alpha Mask"); + +LLDrawPoolAlphaMask::LLDrawPoolAlphaMask() : + LLRenderPass(POOL_ALPHA_MASK) +{ +} + +void LLDrawPoolAlphaMask::prerender() +{ + mVertexShaderLevel = LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); +} + +void LLDrawPoolAlphaMask::beginRenderPass(S32 pass) +{ + LLFastTimer t(FTM_RENDER_ALPHA_MASK); + + if (LLPipeline::sUnderWaterRender) + { + simple_shader = &gObjectSimpleWaterAlphaMaskProgram; + } + else + { + simple_shader = &gObjectSimpleAlphaMaskProgram; + } + + if (mVertexShaderLevel > 0) + { + simple_shader->bind(); + } + else + { + // don't use shaders! + if (gGLManager.mHasShaderObjects) + { + LLGLSLShader::bindNoShader(); + } + } +} + +void LLDrawPoolAlphaMask::endRenderPass(S32 pass) +{ + LLFastTimer t(FTM_RENDER_ALPHA_MASK); + stop_glerror(); + LLRenderPass::endRenderPass(pass); + stop_glerror(); + if (mVertexShaderLevel > 0) + { + simple_shader->unbind(); + } +} + +void LLDrawPoolAlphaMask::render(S32 pass) +{ + LLGLDisable blend(GL_BLEND); + LLFastTimer t(FTM_RENDER_ALPHA_MASK); + + if (mVertexShaderLevel > 0) + { + simple_shader->bind(); + simple_shader->setMinimumAlpha(0.33f); + + pushMaskBatches(LLRenderPass::PASS_ALPHA_MASK, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE); + pushMaskBatches(LLRenderPass::PASS_MATERIAL_ALPHA_MASK, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE); + pushMaskBatches(LLRenderPass::PASS_SPECMAP_MASK, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE); + pushMaskBatches(LLRenderPass::PASS_NORMMAP_MASK, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE); + pushMaskBatches(LLRenderPass::PASS_NORMSPEC_MASK, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE); + } + else + { + LLGLEnable test(GL_ALPHA_TEST); + pushMaskBatches(LLRenderPass::PASS_ALPHA_MASK, getVertexDataMask(), TRUE, FALSE); + gGL.setAlphaRejectSettings(LLRender::CF_DEFAULT); //OK + } +} + +LLDrawPoolFullbrightAlphaMask::LLDrawPoolFullbrightAlphaMask() : + LLRenderPass(POOL_FULLBRIGHT_ALPHA_MASK) +{ +} + +void LLDrawPoolFullbrightAlphaMask::prerender() +{ + mVertexShaderLevel = LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); +} + +void LLDrawPoolFullbrightAlphaMask::beginRenderPass(S32 pass) +{ + LLFastTimer t(FTM_RENDER_ALPHA_MASK); + + if (LLPipeline::sUnderWaterRender) + { + simple_shader = &gObjectFullbrightWaterAlphaMaskProgram; + } + else + { + simple_shader = &gObjectFullbrightAlphaMaskProgram; + } + + if (mVertexShaderLevel > 0) + { + simple_shader->bind(); + } + else + { + // don't use shaders! + if (gGLManager.mHasShaderObjects) + { + LLGLSLShader::bindNoShader(); + } + } +} + +void LLDrawPoolFullbrightAlphaMask::endRenderPass(S32 pass) +{ + LLFastTimer t(FTM_RENDER_ALPHA_MASK); + stop_glerror(); + LLRenderPass::endRenderPass(pass); + stop_glerror(); + if (mVertexShaderLevel > 0) + { + simple_shader->unbind(); + } +} + +void LLDrawPoolFullbrightAlphaMask::render(S32 pass) +{ + LLFastTimer t(FTM_RENDER_ALPHA_MASK); + + if (mVertexShaderLevel > 0) + { + if (simple_shader) + { + simple_shader->bind(); + simple_shader->setMinimumAlpha(0.33f); + if (LLPipeline::sRenderingHUDs || !LLPipeline::sRenderDeferred) + { + simple_shader->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 1.0f); + } else { + simple_shader->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); + } + } + pushMaskBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE); + //LLGLSLShader::bindNoShader(); + } + else + { + LLGLEnable test(GL_ALPHA_TEST); + gPipeline.enableLightsFullbright(LLColor4(1,1,1,1)); + pushMaskBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, getVertexDataMask(), TRUE, FALSE); + gPipeline.enableLightsDynamic(); + gGL.setAlphaRejectSettings(LLRender::CF_DEFAULT); //OK + } +} + //=============================== //DEFERRED IMPLEMENTATION //=============================== @@ -239,6 +420,28 @@ void LLDrawPoolSimple::renderDeferred(S32 pass) } } +static LLFastTimer::DeclareTimer FTM_RENDER_ALPHA_MASK_DEFERRED("Deferred Alpha Mask"); + +void LLDrawPoolAlphaMask::beginDeferredPass(S32 pass) +{ + +} + +void LLDrawPoolAlphaMask::endDeferredPass(S32 pass) +{ + +} + +void LLDrawPoolAlphaMask::renderDeferred(S32 pass) +{ + LLFastTimer t(FTM_RENDER_ALPHA_MASK_DEFERRED); + gDeferredDiffuseAlphaMaskProgram.bind(); + gDeferredDiffuseAlphaMaskProgram.setMinimumAlpha(0.33f); + pushMaskBatches(LLRenderPass::PASS_ALPHA_MASK, getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX, TRUE, TRUE); + gDeferredDiffuseAlphaMaskProgram.unbind(); +} + + // grass drawpool LLDrawPoolGrass::LLDrawPoolGrass() : LLRenderPass(POOL_GRASS) @@ -345,7 +548,15 @@ void LLDrawPoolFullbright::prerender() void LLDrawPoolFullbright::beginPostDeferredPass(S32 pass) { - gDeferredFullbrightProgram.bind(); + if (LLPipeline::sUnderWaterRender) + { + gDeferredFullbrightWaterProgram.bind(); + } + else + { + gDeferredFullbrightProgram.bind(); + } + } void LLDrawPoolFullbright::renderPostDeferred(S32 pass) @@ -362,9 +573,15 @@ void LLDrawPoolFullbright::renderPostDeferred(S32 pass) void LLDrawPoolFullbright::endPostDeferredPass(S32 pass) { - gDeferredFullbrightProgram.unbind(); + if (LLPipeline::sUnderWaterRender) + { + gDeferredFullbrightWaterProgram.unbind(); + } + else + { + gDeferredFullbrightProgram.unbind(); + } LLRenderPass::endRenderPass(pass); - } void LLDrawPoolFullbright::beginRenderPass(S32 pass) @@ -407,14 +624,24 @@ void LLDrawPoolFullbright::render(S32 pass) { fullbright_shader->bind(); fullbright_shader->uniform1f(LLViewerShaderMgr::FULLBRIGHT, 1.f); + fullbright_shader->uniform1f(LLViewerShaderMgr::TEXTURE_GAMMA, 1.f); + U32 fullbright_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TEXTURE_INDEX; pushBatches(LLRenderPass::PASS_FULLBRIGHT, fullbright_mask, TRUE, TRUE); + pushBatches(LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, fullbright_mask, TRUE, TRUE); + pushBatches(LLRenderPass::PASS_SPECMAP_EMISSIVE, fullbright_mask, TRUE, TRUE); + pushBatches(LLRenderPass::PASS_NORMMAP_EMISSIVE, fullbright_mask, TRUE, TRUE); + pushBatches(LLRenderPass::PASS_NORMSPEC_EMISSIVE, fullbright_mask, TRUE, TRUE); } else { gPipeline.enableLightsFullbright(LLColor4(1,1,1,1)); U32 fullbright_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR; renderTexture(LLRenderPass::PASS_FULLBRIGHT, fullbright_mask); + pushBatches(LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, fullbright_mask); + pushBatches(LLRenderPass::PASS_SPECMAP_EMISSIVE, fullbright_mask); + pushBatches(LLRenderPass::PASS_NORMMAP_EMISSIVE, fullbright_mask); + pushBatches(LLRenderPass::PASS_NORMSPEC_EMISSIVE, fullbright_mask); } stop_glerror(); @@ -425,3 +652,75 @@ S32 LLDrawPoolFullbright::getNumPasses() return 1; } + +void LLDrawPoolFullbrightAlphaMask::beginPostDeferredPass(S32 pass) +{ + + if (LLPipeline::sRenderingHUDs || !LLPipeline::sRenderDeferred) + { + gObjectFullbrightAlphaMaskProgram.bind(); + gObjectFullbrightAlphaMaskProgram.uniform1f(LLShaderMgr::TEXTURE_GAMMA, 1.0f); + } + else + { + +// Work-around until we can figure out why the right shader causes +// the GeForce driver to go tango uniform on OS X 10.6.8 only +// +#if GE_FORCE_WORKAROUND + gObjectFullbrightAlphaMaskProgram.bind(); + gObjectFullbrightAlphaMaskProgram.uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); +#else + if (LLPipeline::sUnderWaterRender) + { + gDeferredFullbrightAlphaMaskWaterProgram.bind(); + gDeferredFullbrightAlphaMaskWaterProgram.uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); + } + else + { + gDeferredFullbrightAlphaMaskProgram.bind(); + gDeferredFullbrightAlphaMaskProgram.uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); + } +#endif + } + +} + +void LLDrawPoolFullbrightAlphaMask::renderPostDeferred(S32 pass) +{ + LLFastTimer t(FTM_RENDER_FULLBRIGHT); + LLGLDisable blend(GL_BLEND); + U32 fullbright_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TEXTURE_INDEX; + pushMaskBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, fullbright_mask, TRUE, TRUE); +} + +void LLDrawPoolFullbrightAlphaMask::endPostDeferredPass(S32 pass) +{ + if (LLPipeline::sRenderingHUDs || !LLPipeline::sRenderDeferred) + { + gObjectFullbrightAlphaMaskProgram.unbind(); + } + else + { + +// Work-around until we can figure out why the right shader causes +// the GeForce driver to go tango uniform on OS X 10.6.8 only +// +#if GE_FORCE_WORKAROUND + gObjectFullbrightAlphaMaskProgram.unbind(); +#else + if (LLPipeline::sUnderWaterRender) + { + gDeferredFullbrightAlphaMaskWaterProgram.unbind(); + } + else + { + gDeferredFullbrightAlphaMaskProgram.unbind(); + } +#endif + + } + LLRenderPass::endRenderPass(pass); +} + + diff --git a/indra/newview/lldrawpoolsimple.h b/indra/newview/lldrawpoolsimple.h index 20a376150..f028b74ff 100644 --- a/indra/newview/lldrawpoolsimple.h +++ b/indra/newview/lldrawpoolsimple.h @@ -90,6 +90,59 @@ public: /*virtual*/ void prerender(); }; +class LLDrawPoolAlphaMask : public LLRenderPass +{ +public: + enum + { + VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_TEXCOORD0 | + LLVertexBuffer::MAP_COLOR + }; + virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; } + + LLDrawPoolAlphaMask(); + + /*virtual*/ S32 getNumDeferredPasses() { return 1; } + /*virtual*/ void beginDeferredPass(S32 pass); + /*virtual*/ void endDeferredPass(S32 pass); + /*virtual*/ void renderDeferred(S32 pass); + + /*virtual*/ S32 getNumPasses() { return 1; } + /*virtual*/ void beginRenderPass(S32 pass); + /*virtual*/ void endRenderPass(S32 pass); + /*virtual*/ void render(S32 pass = 0); + /*virtual*/ void prerender(); + +}; + +class LLDrawPoolFullbrightAlphaMask : public LLRenderPass +{ +public: + enum + { + VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_TEXCOORD0 | + LLVertexBuffer::MAP_COLOR + }; + virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; } + + LLDrawPoolFullbrightAlphaMask(); + + /*virtual*/ S32 getNumPostDeferredPasses() { return 1; } + /*virtual*/ void beginPostDeferredPass(S32 pass); + /*virtual*/ void endPostDeferredPass(S32 pass); + /*virtual*/ void renderPostDeferred(S32 pass); + + /*virtual*/ S32 getNumPasses() { return 1; } + /*virtual*/ void beginRenderPass(S32 pass); + /*virtual*/ void endRenderPass(S32 pass); + /*virtual*/ void render(S32 pass = 0); + /*virtual*/ void prerender(); +}; + + class LLDrawPoolFullbright : public LLRenderPass { public: diff --git a/indra/newview/lldrawpoolwater.cpp b/indra/newview/lldrawpoolwater.cpp index 756318583..0872f5f9d 100644 --- a/indra/newview/lldrawpoolwater.cpp +++ b/indra/newview/lldrawpoolwater.cpp @@ -564,14 +564,21 @@ void LLDrawPoolWater::shade() F32 eyedepth = LLViewerCamera::getInstance()->getOrigin().mV[2] - gAgent.getRegion()->getWaterHeight(); + if (eyedepth < 0.f && LLPipeline::sWaterReflections) + { if (deferred_render) { - shader = &gDeferredWaterProgram; + shader = &gDeferredUnderWaterProgram; } - else if (eyedepth < 0.f && LLPipeline::sWaterReflections) + else { shader = &gUnderWaterProgram; } + } + else if (deferred_render) + { + shader = &gDeferredWaterProgram; + } else { shader = &gWaterProgram; @@ -592,7 +599,7 @@ void LLDrawPoolWater::shade() sTime = (F32)LLFrameTimer::getElapsedSeconds()*0.5f; } - S32 reftex = shader->enableTexture(LLViewerShaderMgr::WATER_REFTEX); + S32 reftex = shader->enableTexture(LLShaderMgr::WATER_REFTEX); if (reftex > -1) { diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index f39812631..04d5f5115 100644 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -57,6 +57,8 @@ #include "llviewerregion.h" #include "llviewerwindow.h" #include "llviewershadermgr.h" +#include "llviewertexture.h" +#include "llvoavatar.h" #define LL_MAX_INDICES_COUNT 1000000 @@ -140,9 +142,13 @@ void LLFace::init(LLDrawable* drawablep, LLViewerObject* objp) //special value to indicate uninitialized position mIndicesIndex = 0xFFFFFFFF; + + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + { + mIndexInTex[i] = 0; + mTexture[i] = NULL; + } - mIndexInTex = 0; - mTexture = NULL; mTEOffset = -1; mTextureIndex = 255; @@ -169,9 +175,12 @@ void LLFace::destroy() gPipeline.checkReferences(this); } - if(mTexture.notNull()) + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) { - mTexture->removeFace(this) ; + if(mTexture[i].notNull()) + { + mTexture[i]->removeFace(i, this) ; + } } if (isState(LLFace::PARTICLE)) @@ -264,47 +273,76 @@ void LLFace::setPool(LLFacePool* new_pool, LLViewerTexture *texturep) setTexture(texturep) ; } -void LLFace::setTexture(LLViewerTexture* tex) +void LLFace::setTexture(U32 ch, LLViewerTexture* tex) { - if(mTexture == tex) + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + + if(mTexture[ch] == tex) { return ; } - if(mTexture.notNull()) + if(mTexture[ch].notNull()) { - mTexture->removeFace(this) ; - } + mTexture[ch]->removeFace(ch, this) ; + } if(tex) { - tex->addFace(this) ; + tex->addFace(ch, this) ; } - mTexture = tex ; + mTexture[ch] = tex ; +} + +void LLFace::setTexture(LLViewerTexture* tex) +{ + setDiffuseMap(tex); +} + +void LLFace::setDiffuseMap(LLViewerTexture* tex) +{ + setTexture(LLRender::DIFFUSE_MAP, tex); +} + +void LLFace::setNormalMap(LLViewerTexture* tex) +{ + setTexture(LLRender::NORMAL_MAP, tex); +} + +void LLFace::setSpecularMap(LLViewerTexture* tex) +{ + setTexture(LLRender::SPECULAR_MAP, tex); } void LLFace::dirtyTexture() { LLDrawable* drawablep = getDrawable(); - if (mVObjp.notNull() && mVObjp->getVolume() && - mTexture.notNull() && mTexture->getComponents() == 4) - { //dirty texture on an alpha object should be treated as an LoD update - LLVOVolume* vobj = drawablep->getVOVolume(); - if (vobj) + if (mVObjp.notNull() && mVObjp->getVolume()) + { + for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) { - vobj->mLODChanged = TRUE; + if (mTexture[ch].notNull() && mTexture[ch]->getComponents() == 4) + { //dirty texture on an alpha object should be treated as an LoD update + LLVOVolume* vobj = drawablep->getVOVolume(); + if (vobj) + { + vobj->mLODChanged = TRUE; + } + gPipeline.markRebuild(drawablep, LLDrawable::REBUILD_VOLUME, FALSE); + } } - gPipeline.markRebuild(drawablep, LLDrawable::REBUILD_VOLUME, FALSE); - } + } gPipeline.markTextured(drawablep); } -void LLFace::switchTexture(LLViewerTexture* new_texture) +void LLFace::switchTexture(U32 ch, LLViewerTexture* new_texture) { - if(mTexture == new_texture) + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + + if(mTexture[ch] == new_texture) { return ; } @@ -314,10 +352,17 @@ void LLFace::switchTexture(LLViewerTexture* new_texture) llerrs << "Can not switch to a null texture." << llendl; return; } - new_texture->addTextureStats(mTexture->getMaxVirtualSize()) ; - getViewerObject()->changeTEImage(mTEOffset, new_texture) ; - setTexture(new_texture) ; + llassert(mTexture[ch].notNull()); + + new_texture->addTextureStats(mTexture[ch]->getMaxVirtualSize()) ; + + if (ch == LLRender::DIFFUSE_MAP) + { + getViewerObject()->changeTEImage(mTEOffset, new_texture) ; + } + + setTexture(ch, new_texture) ; dirtyTexture(); } @@ -737,7 +782,7 @@ bool less_than_max_mag(const LLVector4a& vec) } BOOL LLFace::genVolumeBBoxes(const LLVolume &volume, S32 f, - const LLMatrix4& mat_vert_in, const LLMatrix3& mat_normal_in, BOOL global_volume) + const LLMatrix4& mat_vert_in, BOOL global_volume) { //get bounding box if (mDrawablep->isState(LLDrawable::REBUILD_VOLUME | LLDrawable::REBUILD_POSITION | LLDrawable::REBUILD_RIGGED)) @@ -746,16 +791,6 @@ BOOL LLFace::genVolumeBBoxes(const LLVolume &volume, S32 f, LLMatrix4a mat_vert; mat_vert.loadu(mat_vert_in); - LLMatrix4a mat_normal; - mat_normal.loadu(mat_normal_in); - - //if (mDrawablep->isState(LLDrawable::REBUILD_VOLUME)) - //{ //vertex buffer no longer valid - // mVertexBuffer = NULL; - // mLastVertexBuffer = NULL; - //} - - //VECTORIZE THIS LLVector4a min,max; if (f >= volume.getNumVolumeFaces()) @@ -772,100 +807,68 @@ BOOL LLFace::genVolumeBBoxes(const LLVolume &volume, S32 f, llassert(less_than_max_mag(max)); //min, max are in volume space, convert to drawable render space - LLVector4a center; - LLVector4a t; - t.setAdd(min, max); - t.mul(0.5f); - mat_vert.affineTransform(t, center); - LLVector4a size; - size.setSub(max, min); - size.mul(0.5f); - llassert(less_than_max_mag(min)); - llassert(less_than_max_mag(max)); + //get 8 corners of bounding box + LLVector4Logical mask[6]; - if (!global_volume) + for (U32 i = 0; i < 6; ++i) { - //VECTORIZE THIS - LLVector4a scale; - scale.load3(mDrawablep->getVObj()->getScale().mV); - size.mul(scale); + mask[i].clear(); } - // Catch potential badness from normalization before it happens - // - //llassert(mat_normal.mMatrix[0].isFinite3() && (mat_normal.mMatrix[0].dot3(mat_normal.mMatrix[0]).getF32() > F_APPROXIMATELY_ZERO)); - //llassert(mat_normal.mMatrix[1].isFinite3() && (mat_normal.mMatrix[1].dot3(mat_normal.mMatrix[1]).getF32() > F_APPROXIMATELY_ZERO)); - //llassert(mat_normal.mMatrix[2].isFinite3() && (mat_normal.mMatrix[2].dot3(mat_normal.mMatrix[2]).getF32() > F_APPROXIMATELY_ZERO)); - - mat_normal.mMatrix[0].normalize3fast(); - mat_normal.mMatrix[1].normalize3fast(); - mat_normal.mMatrix[2].normalize3fast(); + mask[0].setElement<2>(); //001 + mask[1].setElement<1>(); //010 + mask[2].setElement<1>(); //011 + mask[2].setElement<2>(); + mask[3].setElement<0>(); //100 + mask[4].setElement<0>(); //101 + mask[4].setElement<2>(); + mask[5].setElement<0>(); //110 + mask[5].setElement<1>(); - LLVector4a v[4]; + LLVector4a v[8]; - //get 4 corners of bounding box - mat_normal.rotate(size,v[0]); + v[6] = min; + v[7] = max; - //VECTORIZE THIS - LLVector4a scale; - - scale.set(-1.f, -1.f, 1.f); - scale.mul(size); - mat_normal.rotate(scale, v[1]); - - scale.set(1.f, -1.f, -1.f); - scale.mul(size); - mat_normal.rotate(scale, v[2]); - - scale.set(-1.f, 1.f, -1.f); - scale.mul(size); - mat_normal.rotate(scale, v[3]); + for (U32 i = 0; i < 6; ++i) + { + v[i].setSelectWithMask(mask[i], min, max); + } + LLVector4a tv[8]; + + //transform bounding box into drawable space + for (U32 i = 0; i < 8; ++i) + { + mat_vert.affineTransform(v[i], tv[i]); + } + + //find bounding box LLVector4a& newMin = mExtents[0]; LLVector4a& newMax = mExtents[1]; - - newMin = newMax = center; - - llassert(less_than_max_mag(center)); - - for (U32 i = 0; i < 4; i++) + + newMin = newMax = tv[0]; + + for (U32 i = 1; i < 8; ++i) { - LLVector4a delta; - delta.setAbs(v[i]); - LLVector4a min; - min.setSub(center, delta); - LLVector4a max; - max.setAdd(center, delta); - - newMin.setMin(newMin,min); - newMax.setMax(newMax,max); - - llassert(less_than_max_mag(newMin)); - llassert(less_than_max_mag(newMax)); + newMin.setMin(newMin, tv[i]); + newMax.setMax(newMax, tv[i]); } if (!mDrawablep->isActive()) - { + { // Shift position for region LLVector4a offset; offset.load3(mDrawablep->getRegion()->getOriginAgent().mV); newMin.add(offset); newMax.add(offset); - - llassert(less_than_max_mag(newMin)); - llassert(less_than_max_mag(newMax)); } - t.setAdd(newMin, newMax); + LLVector4a t; + t.setAdd(newMin,newMax); t.mul(0.5f); - llassert(less_than_max_mag(t)); - - //VECTORIZE THIS mCenterLocal.set(t.getF32ptr()); - - llassert(less_than_max_mag(newMin)); - llassert(less_than_max_mag(newMax)); t.setSub(newMax,newMin); mBoundingSphereRadius = t.getLength3().getF32()*0.5f; @@ -1273,30 +1276,34 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume, if (tep && getPoolType() != LLDrawPool::POOL_ALPHA) // <--- alpha channel MUST contain transparency, not shiny { + LLMaterial* mat = tep->getMaterialParams().get(); + bool shiny_in_alpha = false; if (LLPipeline::sRenderDeferred) { //store shiny in alpha if we don't have a specular map - //if (!mat || mat->getSpecularID().isNull()) + if (!mat || mat->getSpecularID().isNull()) { shiny_in_alpha = true; } } else { - if(LLPipeline::sRenderBump && tep->getShiny()) + if (!mat || mat->getDiffuseAlphaMode() != LLMaterial::DIFFUSE_ALPHA_MODE_MASK) { shiny_in_alpha = true; } } - if(shiny_in_alpha) + + if (shiny_in_alpha) { + GLfloat alpha[4] = { - 0.00f, - 0.25f, - 0.5f, - 0.75f + 0.00f, + 0.25f, + 0.5f, + 0.75f }; llassert(tep->getShiny() <= 3); @@ -1555,11 +1562,11 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume, break; case BE_BRIGHTNESS: case BE_DARKNESS: - if( mTexture.notNull() && mTexture->hasGLTexture()) + if( mTexture[LLRender::DIFFUSE_MAP].notNull() && mTexture[LLRender::DIFFUSE_MAP]->hasGLTexture()) { // Offset by approximately one texel - S32 cur_discard = mTexture->getDiscardLevel(); - S32 max_size = llmax( mTexture->getWidth(), mTexture->getHeight() ); + S32 cur_discard = mTexture[LLRender::DIFFUSE_MAP]->getDiscardLevel(); + S32 max_size = llmax( mTexture[LLRender::DIFFUSE_MAP]->getWidth(), mTexture[LLRender::DIFFUSE_MAP]->getHeight() ); max_size <<= cur_discard; const F32 ARTIFICIAL_OFFSET = 2.f; offset_multiple = ARTIFICIAL_OFFSET / (F32)max_size; @@ -1600,11 +1607,18 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume, U8 tex_mode = 0; + bool tex_anim = false; + + LLVOVolume* vobj = (LLVOVolume*) (LLViewerObject*) mVObjp; + tex_mode = vobj->mTexAnimMode; + + if (vobj->mTextureAnimp) + { //texture animation is in play, override specular and normal map tex coords with diffuse texcoords + tex_anim = true; + } + if (isState(TEXTURE_ANIM)) { - LLVOVolume* vobj = (LLVOVolume*) (LLViewerObject*) mVObjp; - tex_mode = vobj->mTexAnimMode; - if (!tex_mode) { clearState(TEXTURE_ANIM); @@ -1629,7 +1643,16 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume, LLVector4a scalea; scalea.load3(scale.mV); + LLMaterial* mat = tep->getMaterialParams().get(); + bool do_bump = bump_code && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD1); + + if (mat && !do_bump) + { + do_bump = mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD1) + || mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD2); + } + bool do_tex_mat = tex_mode && mTextureMatrix; if (!do_bump) @@ -1751,47 +1774,99 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume, std::vector bump_tc; + if (mat && !mat->getNormalID().isNull()) + { //writing out normal and specular texture coordinates, not bump offsets + do_bump = false; + } + LLStrider dst; - mVertexBuffer->getTexCoord0Strider(dst, mGeomIndex, mGeomCount, map_range); + for (U32 ch = 0; ch < 3; ++ch) + { + switch (ch) + { + case 0: + mVertexBuffer->getTexCoord0Strider(dst, mGeomIndex, mGeomCount, map_range); + break; + case 1: + if (mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD1)) + { + mVertexBuffer->getTexCoord1Strider(dst, mGeomIndex, mGeomCount, map_range); + if (mat && !tex_anim) + { + r = mat->getNormalRotation(); + mat->getNormalOffset(os, ot); + mat->getNormalRepeat(ms, mt); - - for (S32 i = 0; i < num_vertices; i++) - { - LLVector2 tc(vf.mTexCoords[i]); + cos_ang = cos(r); + sin_ang = sin(r); + + } + } + else + { + continue; + } + break; + case 2: + if (mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD2)) + { + mVertexBuffer->getTexCoord2Strider(dst, mGeomIndex, mGeomCount, map_range); + if (mat && !tex_anim) + { + r = mat->getSpecularRotation(); + mat->getSpecularOffset(os, ot); + mat->getSpecularRepeat(ms, mt); + + cos_ang = cos(r); + sin_ang = sin(r); + } + } + else + { + continue; + } + break; + } + + + for (S32 i = 0; i < num_vertices; i++) + { + LLVector2 tc(vf.mTexCoords[i]); - LLVector4a& norm = vf.mNormals[i]; + LLVector4a& norm = vf.mNormals[i]; - LLVector4a& center = *(vf.mCenter); + LLVector4a& center = *(vf.mCenter); - if (texgen != LLTextureEntry::TEX_GEN_DEFAULT) - { - LLVector4a vec = vf.mPositions[i]; - - vec.mul(scalea); - - if (texgen == LLTextureEntry::TEX_GEN_PLANAR) + if (texgen != LLTextureEntry::TEX_GEN_DEFAULT) { - planarProjection(tc, norm, center, vec); + LLVector4a vec = vf.mPositions[i]; + + vec.mul(scalea); + + if (texgen == LLTextureEntry::TEX_GEN_PLANAR) + { + planarProjection(tc, norm, center, vec); + } } - } - if (tex_mode && mTextureMatrix) - { - LLVector3 tmp(tc.mV[0], tc.mV[1], 0.f); - tmp = tmp * *mTextureMatrix; - tc.mV[0] = tmp.mV[0]; - tc.mV[1] = tmp.mV[1]; - } - else - { - xform(tc, cos_ang, sin_ang, os, ot, ms, mt); - } + if (tex_mode && mTextureMatrix) + { + LLVector3 tmp(tc.mV[0], tc.mV[1], 0.f); + tmp = tmp * *mTextureMatrix; + tc.mV[0] = tmp.mV[0]; + tc.mV[1] = tmp.mV[1]; + } + else + { + xform(tc, cos_ang, sin_ang, os, ot, ms, mt); + } - *dst++ = tc; - if (do_bump) - { - bump_tc.push_back(tc); + *dst++ = tc; + if (do_bump) + { + bump_tc.push_back(tc); + } } } @@ -1800,7 +1875,7 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume, mVertexBuffer->flush(); } - if (do_bump) + if (!mat && do_bump) { mVertexBuffer->getTexCoord1Strider(tex_coords1, mGeomIndex, mGeomCount, map_range); @@ -1846,20 +1921,31 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume, if (rebuild_pos) { - LLFastTimer t(FTM_FACE_GEOM_POSITION); + LLVector4a* src = vf.mPositions; + + //_mm_prefetch((char*)src, _MM_HINT_T0); + + LLVector4a* end = src+num_vertices; + //LLVector4a* end_64 = end-4; + + //LLFastTimer t(FTM_FACE_GEOM_POSITION); llassert(num_vertices > 0); mVertexBuffer->getVertexStrider(vert, mGeomIndex, mGeomCount, map_range); - LLMatrix4a mat_vert; mat_vert.loadu(mat_vert_in); - LLVector4a* src = vf.mPositions; - volatile F32* dst = (volatile F32*) vert.get(); + F32* dst = (F32*) vert.get(); + F32* end_f32 = dst+mGeomCount*4; - volatile F32* end = dst+num_vertices*4; - LLVector4a res; + //_mm_prefetch((char*)dst, _MM_HINT_NTA); + //_mm_prefetch((char*)src, _MM_HINT_NTA); + + //_mm_prefetch((char*)dst, _MM_HINT_NTA); + + + LLVector4a res0; //,res1,res2,res3; LLVector4a texIdx; @@ -1880,29 +1966,53 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume, texIdx.set(0,0,0,val); - { - LLFastTimer t(FTM_FACE_POSITION_STORE); - LLVector4a tmp; + LLVector4a tmp; - do + { + //LLFastTimer t2(FTM_FACE_POSITION_STORE); + + /*if (num_vertices > 4) + { //more than 64 bytes + while (src < end_64) + { + _mm_prefetch((char*)src + 64, _MM_HINT_T0); + _mm_prefetch((char*)dst + 64, _MM_HINT_T0); + + mat_vert.affineTransform(*src, res0); + tmp.setSelectWithMask(mask, texIdx, res0); + tmp.store4a((F32*) dst); + + mat_vert.affineTransform(*(src+1), res1); + tmp.setSelectWithMask(mask, texIdx, res1); + tmp.store4a((F32*) dst+4); + + mat_vert.affineTransform(*(src+2), res2); + tmp.setSelectWithMask(mask, texIdx, res2); + tmp.store4a((F32*) dst+8); + + mat_vert.affineTransform(*(src+3), res3); + tmp.setSelectWithMask(mask, texIdx, res3); + tmp.store4a((F32*) dst+12); + + dst += 16; + src += 4; + } + }*/ + + while (src < end) { - mat_vert.affineTransform(*src++, res); - tmp.setSelectWithMask(mask, texIdx, res); + mat_vert.affineTransform(*src++, res0); + tmp.setSelectWithMask(mask, texIdx, res0); tmp.store4a((F32*) dst); dst += 4; } - while(dst < end); } { - LLFastTimer t(FTM_FACE_POSITION_PAD); - S32 aligned_pad_vertices = mGeomCount - num_vertices; - res.set(res[0], res[1], res[2], 0.f); - - while (aligned_pad_vertices > 0) + //LLFastTimer t(FTM_FACE_POSITION_PAD); + while (dst < end_f32) { - --aligned_pad_vertices; - res.store4a((F32*) dst); + res0.store4a((F32*) dst); dst += 4; } } @@ -1916,14 +2026,16 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume, if (rebuild_normal) { - LLFastTimer t(FTM_FACE_GEOM_NORMAL); + //LLFastTimer t(FTM_FACE_GEOM_NORMAL); mVertexBuffer->getNormalStrider(norm, mGeomIndex, mGeomCount, map_range); F32* normals = (F32*) norm.get(); - for (S32 i = 0; i < num_vertices; i++) + LLVector4a* src = vf.mNormals; + LLVector4a* end = src+num_vertices; + + while (src < end) { LLVector4a normal; - mat_normal.rotate(vf.mNormals[i], normal); - normal.normalize3fast(); + mat_normal.rotate(*src++, normal); normal.store4a(normals); normals += 4; } @@ -1946,14 +2058,18 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume, mask.clear(); mask.setElement<3>(); - for (S32 i = 0; i < num_vertices; i++) + LLVector4a* src = vf.mTangents; + LLVector4a* end = vf.mTangents+num_vertices; + + while (src < end) { LLVector4a tangent_out; - mat_normal.rotate(vf.mTangents[i], tangent_out); + mat_normal.rotate(*src, tangent_out); tangent_out.normalize3fast(); - tangent_out.setSelectWithMask(mask, vf.mTangents[i], tangent_out); + tangent_out.setSelectWithMask(mask, *src, tangent_out); tangent_out.store4a(tangents); + src++; tangents += 4; } @@ -2073,16 +2189,23 @@ BOOL LLFace::hasMedia() const { return TRUE ; } - if(mTexture.notNull()) + if(mTexture[LLRender::DIFFUSE_MAP].notNull()) { - return mTexture->hasParcelMedia() ; //if has a parcel media + return mTexture[LLRender::DIFFUSE_MAP]->hasParcelMedia() ; //if has a parcel media } return FALSE ; //no media. } + const F32 LEAST_IMPORTANCE = 0.05f ; const F32 LEAST_IMPORTANCE_FOR_LARGE_IMAGE = 0.3f ; +void LLFace::resetVirtualSize() +{ + setVirtualSize(0.f); + mImportanceToCamera = 0.f; +} + F32 LLFace::getTextureVirtualSize() { F32 radius; @@ -2118,11 +2241,11 @@ F32 LLFace::getTextureVirtualSize() } face_area = LLFace::adjustPixelArea(mImportanceToCamera, face_area); - if (/*mImportanceToCamera < 1.0f && */face_area > LLViewerTexture::sMinLargeImageSize) //if is large image, shrink face_area by considering the partial overlapping. + if(face_area > LLViewerTexture::sMinLargeImageSize) //if is large image, shrink face_area by considering the partial overlapping. { - if (mImportanceToCamera > LEAST_IMPORTANCE_FOR_LARGE_IMAGE && mTexture.notNull() && mTexture->isLargeImage()) - { - face_area *= adjustPartialOverlapPixelArea(cos_angle_to_view_dir, radius); + if(mImportanceToCamera > LEAST_IMPORTANCE_FOR_LARGE_IMAGE && mTexture[LLRender::DIFFUSE_MAP].notNull() && mTexture[LLRender::DIFFUSE_MAP]->isLargeImage()) + { + face_area *= adjustPartialOverlapPixelArea(cos_angle_to_view_dir, radius ); } } @@ -2148,9 +2271,18 @@ BOOL LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) LLVector4a t; t.load3(camera->getOrigin().mV); lookAt.setSub(center, t); + F32 dist = lookAt.getLength3().getF32(); dist = llmax(dist-size.getLength3().getF32(), 0.001f); - lookAt.normalize3fast() ; + //ramp down distance for nearby objects + if (dist < 16.f) + { + dist /= 16.f; + dist *= dist; + dist *= 16.f; + } + + lookAt.normalize3fast(); //get area of circle around node F32 app_angle = atanf((F32) sqrt(size_squared) / dist); @@ -2486,9 +2618,12 @@ LLVector3 LLFace::getPositionAgent() const return mCenterLocal * getRenderMatrix(); } } -LLViewerTexture* LLFace::getTexture() const + +LLViewerTexture* LLFace::getTexture(U32 ch) const { - return mTexture ; + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + + return mTexture[ch] ; } void LLFace::setVertexBuffer(LLVertexBuffer* buffer) @@ -2506,6 +2641,22 @@ void LLFace::clearVertexBuffer() U32 LLFace::getRiggedDataMask(U32 type) { static const U32 rigged_data_mask[] = { + LLDrawPoolAvatar::RIGGED_MATERIAL_MASK, + LLDrawPoolAvatar::RIGGED_MATERIAL_ALPHA_VMASK, + LLDrawPoolAvatar::RIGGED_MATERIAL_ALPHA_MASK_MASK, + LLDrawPoolAvatar::RIGGED_MATERIAL_ALPHA_EMISSIVE_MASK, + LLDrawPoolAvatar::RIGGED_SPECMAP_VMASK, + LLDrawPoolAvatar::RIGGED_SPECMAP_BLEND_MASK, + LLDrawPoolAvatar::RIGGED_SPECMAP_MASK_MASK, + LLDrawPoolAvatar::RIGGED_SPECMAP_EMISSIVE_MASK, + LLDrawPoolAvatar::RIGGED_NORMMAP_VMASK, + LLDrawPoolAvatar::RIGGED_NORMMAP_BLEND_MASK, + LLDrawPoolAvatar::RIGGED_NORMMAP_MASK_MASK, + LLDrawPoolAvatar::RIGGED_NORMMAP_EMISSIVE_MASK, + LLDrawPoolAvatar::RIGGED_NORMSPEC_VMASK, + LLDrawPoolAvatar::RIGGED_NORMSPEC_BLEND_MASK, + LLDrawPoolAvatar::RIGGED_NORMSPEC_MASK_MASK, + LLDrawPoolAvatar::RIGGED_NORMSPEC_EMISSIVE_MASK, LLDrawPoolAvatar::RIGGED_SIMPLE_MASK, LLDrawPoolAvatar::RIGGED_FULLBRIGHT_MASK, LLDrawPoolAvatar::RIGGED_SHINY_MASK, diff --git a/indra/newview/llface.h b/indra/newview/llface.h index d290ab292..7cdb6272e 100644 --- a/indra/newview/llface.h +++ b/indra/newview/llface.h @@ -2,31 +2,25 @@ * @file llface.h * @brief LLFace class definition * - * $LicenseInfo:firstyear=2001&license=viewergpl$ - * - * Copyright (c) 2001-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * Copyright (C) 2010, Linden Research, Inc. * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -115,8 +109,12 @@ public: U16 getGeomStart() const { return mGeomIndex; } // index into draw pool void setTextureIndex(U8 index); U8 getTextureIndex() const { return mTextureIndex; } + void setTexture(U32 ch, LLViewerTexture* tex); void setTexture(LLViewerTexture* tex) ; - void switchTexture(LLViewerTexture* new_texture); + void setDiffuseMap(LLViewerTexture* tex); + void setNormalMap(LLViewerTexture* tex); + void setSpecularMap(LLViewerTexture* tex); + void switchTexture(U32 ch, LLViewerTexture* new_texture); void dirtyTexture(); LLXformMatrix* getXform() const { return mXform; } BOOL hasGeometry() const { return mGeomCount > 0; } @@ -135,8 +133,8 @@ public: F32 getVirtualSize() const { return mVSize; } F32 getPixelArea() const { return mPixelArea; } - S32 getIndexInTex() const {return mIndexInTex ;} - void setIndexInTex(S32 index) { mIndexInTex = index ;} + S32 getIndexInTex(U32 ch) const {llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); return mIndexInTex[ch];} + void setIndexInTex(U32 ch, S32 index) { llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); mIndexInTex[ch] = index ;} void renderSetColor() const; S32 renderElements(const U16 *index_array) const; @@ -154,7 +152,7 @@ public: S32 getLOD() const { return mVObjp.notNull() ? mVObjp->getLOD() : 0; } void setPoolType(U32 type) { mPoolType = type; } S32 getTEOffset() { return mTEOffset; } - LLViewerTexture* getTexture() const; + LLViewerTexture* getTexture(U32 ch = LLRender::DIFFUSE_MAP) const; void setViewerObject(LLViewerObject* object); void setPool(LLFacePool *pool, LLViewerTexture *texturep); @@ -196,10 +194,9 @@ public: S32 getColors(LLStrider &colors); S32 getIndices(LLStrider &indices); - void setSize(S32 numVertices, const S32 num_indices = 0, bool align = false); + void setSize(S32 numVertices, S32 num_indices = 0, bool align = false); - BOOL genVolumeBBoxes(const LLVolume &volume, S32 f, - const LLMatrix4& mat, const LLMatrix3& inv_trans_mat, BOOL global_volume = FALSE); + BOOL genVolumeBBoxes(const LLVolume &volume, S32 f,const LLMatrix4& mat, BOOL global_volume = FALSE); void init(LLDrawable* drawablep, LLViewerObject* objp); void destroy(); @@ -224,9 +221,13 @@ public: F32 getTextureVirtualSize() ; F32 getImportanceToCamera()const {return mImportanceToCamera ;} + void resetVirtualSize(); void setHasMedia(bool has_media) { mHasMedia = has_media ;} BOOL hasMedia() const ; + + BOOL switchTexture() ; + //vertex buffer tracking void setVertexBuffer(LLVertexBuffer* buffer); void clearVertexBuffer(); //sets mVertexBuffer and mLastVertexBuffer to NULL @@ -258,6 +259,8 @@ public: F32 mLastSkinTime; F32 mLastMoveTime; LLMatrix4* mTextureMatrix; + LLMatrix4* mSpecMapMatrix; + LLMatrix4* mNormalMapMatrix; LLDrawInfo* mDrawInfo; private: @@ -273,10 +276,12 @@ private: U8 mTextureIndex; // index of texture channel to use for pseudo-atlasing U32 mIndicesCount; U32 mIndicesIndex; // index into draw pool for indices (yeah, I know!) - S32 mIndexInTex ; + S32 mIndexInTex[LLRender::NUM_TEXTURE_CHANNELS]; LLXformMatrix* mXform; - LLPointer mTexture; + + LLPointer mTexture[LLRender::NUM_TEXTURE_CHANNELS]; + LLPointer mDrawablep; LLPointer mVObjp; S32 mTEOffset; diff --git a/indra/newview/llface.inl b/indra/newview/llface.inl index 176c73e38..85c35a88f 100644 --- a/indra/newview/llface.inl +++ b/indra/newview/llface.inl @@ -2,31 +2,25 @@ * @file llface.inl * @brief Inline functions for LLFace * - * $LicenseInfo:firstyear=2001&license=viewergpl$ - * - * Copyright (c) 2001-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * Copyright (C) 2010, Linden Research, Inc. * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ diff --git a/indra/newview/llmaterialmgr.cpp b/indra/newview/llmaterialmgr.cpp index 0648b902a..b9d5177fc 100644 --- a/indra/newview/llmaterialmgr.cpp +++ b/indra/newview/llmaterialmgr.cpp @@ -50,6 +50,7 @@ #define MATERIALS_CAP_MATERIAL_FIELD "Material" #define MATERIALS_CAP_OBJECT_ID_FIELD "ID" #define MATERIALS_CAP_MATERIAL_ID_FIELD "MaterialID" +#define SIM_FEATURE_MAX_MATERIALS_PER_TRANSACTION "MaxMaterialsPerTransaction" #define MATERIALS_GET_MAX_ENTRIES 50 #define MATERIALS_GET_TIMEOUT (60.f * 20) @@ -339,36 +340,29 @@ const LLMaterialPtr LLMaterialMgr::setMaterial(const LLUUID& region_id, const LL itMaterial = ret.first; } - // we may have cleared our queues on leaving a region before we recv'd our - // update for this material...too late now! - // - if (isGetPending(region_id, material_id)) - { - - TEMaterialPair te_mat_pair; - te_mat_pair.materialID = material_id; + TEMaterialPair te_mat_pair; + te_mat_pair.materialID = material_id; - U32 i = 0; - while (i < LLTEContents::MAX_TES) + U32 i = 0; + while (i < LLTEContents::MAX_TES) + { + te_mat_pair.te = i++; + get_callback_te_map_t::iterator itCallbackTE = mGetTECallbacks.find(te_mat_pair); + if (itCallbackTE != mGetTECallbacks.end()) { - te_mat_pair.te = i++; - get_callback_te_map_t::iterator itCallbackTE = mGetTECallbacks.find(te_mat_pair); - if (itCallbackTE != mGetTECallbacks.end()) - { - (*itCallbackTE->second)(material_id, itMaterial->second, te_mat_pair.te); - delete itCallbackTE->second; - mGetTECallbacks.erase(itCallbackTE); - } + (*itCallbackTE->second)(material_id, itMaterial->second, te_mat_pair.te); + delete itCallbackTE->second; + mGetTECallbacks.erase(itCallbackTE); } + } - get_callback_map_t::iterator itCallback = mGetCallbacks.find(material_id); - if (itCallback != mGetCallbacks.end()) - { - (*itCallback->second)(material_id, itMaterial->second); + get_callback_map_t::iterator itCallback = mGetCallbacks.find(material_id); + if (itCallback != mGetCallbacks.end()) + { + (*itCallback->second)(material_id, itMaterial->second); - delete itCallback->second; - mGetCallbacks.erase(itCallback); - } + delete itCallback->second; + mGetCallbacks.erase(itCallback); } mGetPending.erase(pending_material_t(region_id, material_id)); @@ -555,11 +549,9 @@ void LLMaterialMgr::onIdle(void*) instancep->processGetAllQueue(); } - static LLFrameTimer mPutTimer; - if ( (!instancep->mPutQueue.empty()) && (mPutTimer.hasExpired()) ) + if (!instancep->mPutQueue.empty()) { instancep->processPutQueue(); - mPutTimer.reset(MATERIALS_PUT_THROTTLE_SECS); } } @@ -576,14 +568,14 @@ void LLMaterialMgr::processGetQueue() continue; } - const LLViewerRegion* regionp = LLWorld::instance().getRegionFromID(region_id); + LLViewerRegion* regionp = LLWorld::instance().getRegionFromID(region_id); if (!regionp) { LL_WARNS("Materials") << "Unknown region with id " << region_id.asString() << LL_ENDL; mGetQueue.erase(itRegionQueue); continue; } - else if (!regionp->capabilitiesReceived()) + else if (!regionp->capabilitiesReceived() || regionp->materialsCapThrottled()) { continue; } @@ -606,8 +598,9 @@ void LLMaterialMgr::processGetQueue() LLSD materialsData = LLSD::emptyArray(); material_queue_t& materials = itRegionQueue->second; + U32 max_entries = regionp->getMaxMaterialsPerTransaction(); material_queue_t::iterator loopMaterial = materials.begin(); - while ( (materials.end() != loopMaterial) && (materialsData.size() <= MATERIALS_GET_MAX_ENTRIES) ) + while ( (materials.end() != loopMaterial) && (materialsData.size() < (int)max_entries) ) { material_queue_t::iterator itMaterial = loopMaterial++; materialsData.append((*itMaterial).asLLSD()); @@ -639,6 +632,7 @@ void LLMaterialMgr::processGetQueue() LL_DEBUGS("Materials") << "POSTing to region '" << regionp->getName() << "' at '"<< capURL << " for " << materialsData.size() << " materials." << "\ndata: " << ll_pretty_print_sd(materialsData) << LL_ENDL; LLHTTPClient::post(capURL, postData, materialsResponder); + regionp->resetMaterialsCapThrottle(); } } @@ -657,7 +651,7 @@ void LLMaterialMgr::processGetAllQueue() clearGetQueues(region_id); // Invalidates region_id continue; } - else if (!regionp->capabilitiesReceived()) + else if (!regionp->capabilitiesReceived() || regionp->materialsCapThrottled()) { continue; } @@ -674,6 +668,7 @@ void LLMaterialMgr::processGetAllQueue() LL_DEBUGS("Materials") << "GET all for region " << region_id << "url " << capURL << LL_ENDL; LLHTTPClient::ResponderPtr materialsResponder = new LLMaterialsResponder("GET", capURL, boost::bind(&LLMaterialMgr::onGetAllResponse, this, _1, _2, *itRegion)); LLHTTPClient::get(capURL, materialsResponder); + regionp->resetMaterialsCapThrottle(); mGetAllPending.insert(std::pair(region_id, LLFrameTimer::getTotalSeconds())); mGetAllQueue.erase(itRegion); // Invalidates region_id } @@ -681,7 +676,7 @@ void LLMaterialMgr::processGetAllQueue() void LLMaterialMgr::processPutQueue() { - typedef std::map regionput_request_map; + typedef std::map regionput_request_map; regionput_request_map requests; put_queue_t::iterator loopQueue = mPutQueue.begin(); @@ -691,25 +686,27 @@ void LLMaterialMgr::processPutQueue() const LLUUID& object_id = itQueue->first; const LLViewerObject* objectp = gObjectList.findObject(object_id); - if ( (!objectp) || (!objectp->getRegion()) ) + if ( !objectp ) { - LL_WARNS("Materials") << "Object or object region is NULL" << LL_ENDL; - + LL_WARNS("Materials") << "Object is NULL" << LL_ENDL; mPutQueue.erase(itQueue); - continue; } - - const LLViewerRegion* regionp = objectp->getRegion(); - if (!regionp->capabilitiesReceived()) + else { - continue; + LLViewerRegion* regionp = objectp->getRegion(); + if ( !regionp ) + { + LL_WARNS("Materials") << "Object region is NULL" << LL_ENDL; + mPutQueue.erase(itQueue); } - + else if ( regionp->capabilitiesReceived() && !regionp->materialsCapThrottled()) + { LLSD& facesData = requests[regionp]; facematerial_map_t& face_map = itQueue->second; + U32 max_entries = regionp->getMaxMaterialsPerTransaction(); facematerial_map_t::iterator itFace = face_map.begin(); - while ( (face_map.end() != itFace) && (facesData.size() < MATERIALS_GET_MAX_ENTRIES) ) + while ( (face_map.end() != itFace) && (facesData.size() < (int)max_entries) ) { LLSD faceData = LLSD::emptyMap(); faceData[MATERIALS_CAP_FACE_FIELD] = static_cast(itFace->first); @@ -726,14 +723,17 @@ void LLMaterialMgr::processPutQueue() mPutQueue.erase(itQueue); } } + } + } for (regionput_request_map::const_iterator itRequest = requests.begin(); itRequest != requests.end(); ++itRequest) { - std::string capURL = itRequest->first->getCapability(MATERIALS_CAPABILITY_NAME); + LLViewerRegion* regionp = itRequest->first; + std::string capURL = regionp->getCapability(MATERIALS_CAPABILITY_NAME); if (capURL.empty()) { LL_WARNS("Materials") << "Capability '" << MATERIALS_CAPABILITY_NAME - << "' is not defined on region '" << itRequest->first->getName() << "'" << LL_ENDL; + << "' is not defined on region '" << regionp->getName() << "'" << LL_ENDL; continue; } @@ -756,6 +756,7 @@ void LLMaterialMgr::processPutQueue() LL_DEBUGS("Materials") << "put for " << itRequest->second.size() << " faces to region " << itRequest->first->getName() << LL_ENDL; LLHTTPClient::ResponderPtr materialsResponder = new LLMaterialsResponder("PUT", capURL, boost::bind(&LLMaterialMgr::onPutResponse, this, _1, _2)); LLHTTPClient::put(capURL, putData, materialsResponder); + regionp->resetMaterialsCapThrottle(); } else { diff --git a/indra/newview/llmaterialmgr.h b/indra/newview/llmaterialmgr.h index e317a791a..e83f1f4e0 100644 --- a/indra/newview/llmaterialmgr.h +++ b/indra/newview/llmaterialmgr.h @@ -124,6 +124,8 @@ protected: put_queue_t mPutQueue; material_map_t mMaterials; + + U32 getMaxEntries(const LLViewerRegion* regionp); }; #endif // LL_LLMATERIALMGR_H diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp index e15e08c5a..d08f847a8 100644 --- a/indra/newview/llspatialpartition.cpp +++ b/indra/newview/llspatialpartition.cpp @@ -2386,7 +2386,7 @@ void pushBufferVerts(LLVertexBuffer* buffer, U32 mask) } } -void pushBufferVerts(LLSpatialGroup* group, U32 mask) +void pushBufferVerts(LLSpatialGroup* group, U32 mask, bool push_alpha = true) { if (group->mSpatialPartition->mRenderByGroup) { @@ -2395,7 +2395,10 @@ void pushBufferVerts(LLSpatialGroup* group, U32 mask) LLDrawInfo* params = *(group->mDrawMap.begin()->second.begin()); LLRenderPass::applyModelMatrix(*params); - pushBufferVerts(group->mVertexBuffer, mask); + if (push_alpha) + { + pushBufferVerts(group->mVertexBuffer, mask); + } for (LLSpatialGroup::buffer_map_t::iterator i = group->mBufferMap.begin(); i != group->mBufferMap.end(); ++i) { @@ -2858,20 +2861,23 @@ void renderBoundingBox(LLDrawable* drawable, BOOL set_color = TRUE) const LLVector4a* ext; LLVector4a pos, size; - //render face bounding boxes - for (S32 i = 0; i < drawable->getNumFaces(); i++) + if (drawable->getVOVolume()) { - LLFace* facep = drawable->getFace(i); - if (facep) + //render face bounding boxes + for (S32 i = 0; i < drawable->getNumFaces(); i++) { - ext = facep->mExtents; + LLFace* facep = drawable->getFace(i); + if (facep) + { + ext = facep->mExtents; - pos.setAdd(ext[0], ext[1]); - pos.mul(0.5f); - size.setSub(ext[1], ext[0]); - size.mul(0.5f); + pos.setAdd(ext[0], ext[1]); + pos.mul(0.5f); + size.setSub(ext[1], ext[0]); + size.mul(0.5f); - drawBoxOutline(pos,size); + drawBoxOutline(pos,size); + } } } @@ -4442,7 +4448,16 @@ LLDrawInfo::LLDrawInfo(U16 start, U16 end, U32 count, U32 offset, mGroup(NULL), mFace(NULL), mDistance(0.f), - mDrawMode(LLRender::TRIANGLES) + mDrawMode(LLRender::TRIANGLES), + mMaterial(NULL), + mShaderMask(0), + mSpecColor(1.0f, 1.0f, 1.0f, 0.5f), + mBlendFuncSrc(LLRender::BF_SOURCE_ALPHA), + mBlendFuncDst(LLRender::BF_ONE_MINUS_SOURCE_ALPHA), + mHasGlow(FALSE), + mEnvIntensity(0.0f), + mAlphaMaskCutoff(0.5f), + mDiffuseAlphaMode(0) { mVertexBuffer->validateRange(mStart, mEnd, mCount, mOffset); diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h index f41c3a57c..3ab2d2b68 100644 --- a/indra/newview/llspatialpartition.h +++ b/indra/newview/llspatialpartition.h @@ -117,6 +117,7 @@ public: U32 mOffset; BOOL mFullbright; U8 mBump; + U8 mShiny; BOOL mParticle; F32 mPartSize; F32 mVSize; @@ -124,6 +125,21 @@ public: LL_ALIGN_16(LLFace* mFace); //associated face F32 mDistance; U32 mDrawMode; + LLMaterialPtr mMaterial; // If this is null, the following parameters are unused. + LLMaterialID mMaterialID; + U32 mShaderMask; + U32 mBlendFuncSrc; + U32 mBlendFuncDst; + BOOL mHasGlow; + LLPointer mSpecularMap; + const LLMatrix4* mSpecularMapMatrix; + LLPointer mNormalMap; + const LLMatrix4* mNormalMapMatrix; + LLVector4 mSpecColor; // XYZ = Specular RGB, W = Specular Exponent + F32 mEnvIntensity; + F32 mAlphaMaskCutoff; + U8 mDiffuseAlphaMode; + struct CompareTexture { diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index eecabfb9f..92278c941 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -1158,6 +1158,7 @@ void render_hud_attachments() if (LLPipeline::sShowHUDAttachments && !gDisconnected && setup_hud_matrices()) { + LLPipeline::sRenderingHUDs = TRUE; LLCamera hud_cam = *LLViewerCamera::getInstance(); hud_cam.setOrigin(-1.f,0,0); hud_cam.setAxes(LLVector3(1,0,0), LLVector3(0,1,0), LLVector3(0,0,1)); @@ -1203,10 +1204,13 @@ void render_hud_attachments() gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_SIMPLE); gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_VOLUME); gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA_MASK); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_FULLBRIGHT_ALPHA_MASK); gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_FULLBRIGHT); gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA); gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK); gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_BUMP); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_MATERIAL); gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT); gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK); gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY); @@ -1231,6 +1235,7 @@ void render_hud_attachments() gPipeline.toggleRenderDebugFeature((void*) LLPipeline::RENDER_DEBUG_FEATURE_UI); } LLPipeline::sUseOcclusion = use_occlusion; + LLPipeline::sRenderingHUDs = FALSE; } gGL.matrixMode(LLRender::MM_PROJECTION); gGL.popMatrix(); diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 45d486784..b81deb190 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -208,6 +208,8 @@ LLViewerObject::LLViewerObject(const LLUUID &id, const LLPCode pcode, LLViewerRe mTotalCRC(0), mListIndex(-1), mTEImages(NULL), + mTENormalMaps(NULL), + mTESpecularMaps(NULL), mGLName(0), mbCanSelect(TRUE), mFlags(0), @@ -336,6 +338,18 @@ void LLViewerObject::deleteTEImages() { delete[] mTEImages; mTEImages = NULL; + + if (mTENormalMaps != NULL) + { + delete[] mTENormalMaps; + mTENormalMaps = NULL; + } + + if (mTESpecularMaps != NULL) + { + delete[] mTESpecularMaps; + mTESpecularMaps = NULL; + } } void LLViewerObject::markDead() @@ -1600,6 +1614,8 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, dp->setPassFlags(value); dp->unpackUUID(owner_id, "Owner"); + mOwnerID = owner_id; + if (value & 0x80) { dp->unpackVector3(new_angv, "Omega"); @@ -1688,9 +1704,9 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, // if (value & 0x8) { - unpackParticleSource(*dp, owner_id); + unpackParticleSource(*dp, owner_id, true); } - else + else if (!(value & 0x400)) { deleteParticleSource(); } @@ -4048,25 +4064,39 @@ void LLViewerObject::setNumTEs(const U8 num_tes) { LLPointer *new_images; new_images = new LLPointer[num_tes]; + + LLPointer *new_normmaps; + new_normmaps = new LLPointer[num_tes]; + + LLPointer *new_specmaps; + new_specmaps = new LLPointer[num_tes]; for (i = 0; i < num_tes; i++) { if (i < getNumTEs()) { new_images[i] = mTEImages[i]; + new_normmaps[i] = mTENormalMaps[i]; + new_specmaps[i] = mTESpecularMaps[i]; } else if (getNumTEs()) { new_images[i] = mTEImages[getNumTEs()-1]; + new_normmaps[i] = mTENormalMaps[getNumTEs()-1]; + new_specmaps[i] = mTESpecularMaps[getNumTEs()-1]; } else { new_images[i] = NULL; + new_normmaps[i] = NULL; + new_specmaps[i] = NULL; } } deleteTEImages(); mTEImages = new_images; + mTENormalMaps = new_normmaps; + mTESpecularMaps = new_specmaps; } else { @@ -4161,12 +4191,18 @@ void LLViewerObject::sendTEUpdate() const void LLViewerObject::setTE(const U8 te, const LLTextureEntry &texture_entry) { LLPrimitive::setTE(te, texture_entry); -// This doesn't work, don't get any textures. -// if (mDrawable.notNull() && mDrawable->isVisible()) -// { - const LLUUID& image_id = getTE(te)->getID(); + + const LLUUID& image_id = getTE(te)->getID(); mTEImages[te] = LLViewerTextureManager::getFetchedTexture(image_id, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); -// } + + if (getTE(te)->getMaterialParams().notNull()) + { + const LLUUID& norm_id = getTE(te)->getMaterialParams()->getNormalID(); + mTENormalMaps[te] = LLViewerTextureManager::getFetchedTexture(norm_id, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + + const LLUUID& spec_id = getTE(te)->getMaterialParams()->getSpecularID(); + mTESpecularMaps[te] = LLViewerTextureManager::getFetchedTexture(spec_id, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + } } void LLViewerObject::setTEImage(const U8 te, LLViewerTexture *imagep) @@ -4201,6 +4237,52 @@ S32 LLViewerObject::setTETextureCore(const U8 te, LLViewerTexture *image) return retval; } +S32 LLViewerObject::setTENormalMapCore(const U8 te, LLViewerTexture *image) +{ + S32 retval = TEM_CHANGE_TEXTURE; + const LLUUID& uuid = image ? image->getID() : LLUUID::null; + if (uuid != getTE(te)->getID() || + uuid == LLUUID::null) + { + LLTextureEntry* tep = getTE(te); + LLMaterial* mat = NULL; + if (tep) + { + mat = tep->getMaterialParams(); + } + + if (mat) + { + mat->setNormalID(uuid); + } + } + changeTENormalMap(te,image); + return retval; +} + +S32 LLViewerObject::setTESpecularMapCore(const U8 te, LLViewerTexture *image) +{ + S32 retval = TEM_CHANGE_TEXTURE; + const LLUUID& uuid = image ? image->getID() : LLUUID::null; + if (uuid != getTE(te)->getID() || + uuid == LLUUID::null) + { + LLTextureEntry* tep = getTE(te); + LLMaterial* mat = NULL; + if (tep) + { + mat = tep->getMaterialParams(); + } + + if (mat) + { + mat->setSpecularID(uuid); + } + } + changeTESpecularMap(te, image); + return retval; +} + //virtual void LLViewerObject::changeTEImage(S32 index, LLViewerTexture* new_image) { @@ -4211,6 +4293,25 @@ void LLViewerObject::changeTEImage(S32 index, LLViewerTexture* new_image) mTEImages[index] = new_image ; } +void LLViewerObject::changeTENormalMap(S32 index, LLViewerTexture* new_image) +{ + if(index < 0 || index >= getNumTEs()) + { + return ; + } + mTENormalMaps[index] = new_image ; + refreshMaterials(); +} + +void LLViewerObject::changeTESpecularMap(S32 index, LLViewerTexture* new_image) +{ + if(index < 0 || index >= getNumTEs()) + { + return ; + } + mTESpecularMaps[index] = new_image ; + refreshMaterials(); +} S32 LLViewerObject::setTETexture(const U8 te, const LLUUID& uuid) { // Invalid host == get from the agent's sim @@ -4219,6 +4320,19 @@ S32 LLViewerObject::setTETexture(const U8 te, const LLUUID& uuid) return setTETextureCore(te,image); } +S32 LLViewerObject::setTENormalMap(const U8 te, const LLUUID& uuid) +{ + LLViewerFetchedTexture *image = (uuid == LLUUID::null) ? NULL : LLViewerTextureManager::getFetchedTexture( + uuid, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, LLHost::invalid); + return setTENormalMapCore(te, image); +} + +S32 LLViewerObject::setTESpecularMap(const U8 te, const LLUUID& uuid) +{ + LLViewerFetchedTexture *image = (uuid == LLUUID::null) ? NULL : LLViewerTextureManager::getFetchedTexture( + uuid, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, LLHost::invalid); + return setTESpecularMapCore(te, image); +} S32 LLViewerObject::setTEColor(const U8 te, const LLColor3& color) { @@ -4379,6 +4493,59 @@ S32 LLViewerObject::setTEGlow(const U8 te, const F32 glow) return retval; } +S32 LLViewerObject::setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS("Material") << "No texture entry for te " << (S32)te + << ", object " << mID + << ", material " << pMaterialID + << LL_ENDL; + } + //else if (pMaterialID != tep->getMaterialID()) + { + LL_DEBUGS("Material") << "Changing texture entry for te " << (S32)te + << ", object " << mID + << ", material " << pMaterialID + << LL_ENDL; + retval = LLPrimitive::setTEMaterialID(te, pMaterialID); + refreshMaterials(); + } + return retval; +} + +S32 LLViewerObject::setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + llwarns << "No texture entry for te " << (S32)te << ", object " << mID << llendl; + return 0; + } + + retval = LLPrimitive::setTEMaterialParams(te, pMaterialParams); + LL_DEBUGS("Material") << "Changing material params for te " << (S32)te + << ", object " << mID + << " (" << retval << ")" + << LL_ENDL; + setTENormalMap(te, (pMaterialParams) ? pMaterialParams->getNormalID() : LLUUID::null); + setTESpecularMap(te, (pMaterialParams) ? pMaterialParams->getSpecularID() : LLUUID::null); + + refreshMaterials(); + return retval; +} + +void LLViewerObject::refreshMaterials() +{ + setChanged(TEXTURE); + if (mDrawable.notNull()) + { + gPipeline.markTextured(mDrawable); + } +} S32 LLViewerObject::setTEScale(const U8 te, const F32 s, const F32 t) { @@ -4480,6 +4647,74 @@ LLViewerTexture *LLViewerObject::getTEImage(const U8 face) const } +bool LLViewerObject::isImageAlphaBlended(const U8 te) const +{ + LLViewerTexture* image = getTEImage(te); + LLGLenum format = image ? image->getPrimaryFormat() : GL_RGB; + switch (format) + { + case GL_RGBA: + case GL_ALPHA: + { + return true; + } + break; + + case GL_RGB: break; + default: + { + llwarns << "Unexpected tex format in LLViewerObject::isImageAlphaBlended...returning no alpha." << llendl; + } + break; + } + + return false; +} + +LLViewerTexture *LLViewerObject::getTENormalMap(const U8 face) const +{ + // llassert(mTEImages); + + if (face < getNumTEs()) + { + LLViewerTexture* image = mTENormalMaps[face]; + if (image) + { + return image; + } + else + { + return (LLViewerTexture*)(LLViewerFetchedTexture::sDefaultImagep); + } + } + + llerrs << llformat("Requested Image from invalid face: %d/%d",face,getNumTEs()) << llendl; + + return NULL; +} + +LLViewerTexture *LLViewerObject::getTESpecularMap(const U8 face) const +{ + // llassert(mTEImages); + + if (face < getNumTEs()) + { + LLViewerTexture* image = mTESpecularMaps[face]; + if (image) + { + return image; + } + else + { + return (LLViewerTexture*)(LLViewerFetchedTexture::sDefaultImagep); + } + } + + llerrs << llformat("Requested Image from invalid face: %d/%d",face,getNumTEs()) << llendl; + + return NULL; +} + void LLViewerObject::fitFaceTexture(const U8 face) { llinfos << "fitFaceTexture not implemented" << llendl; @@ -4766,7 +5001,7 @@ void LLViewerObject::unpackParticleSource(const S32 block_num, const LLUUID& own } } -void LLViewerObject::unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_id) +void LLViewerObject::unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_id, bool legacy) { if (!mPartSourcep.isNull() && mPartSourcep->isDead()) { @@ -4775,7 +5010,7 @@ void LLViewerObject::unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_ if (mPartSourcep) { // If we've got one already, just update the existing source (or remove it) - if (!LLViewerPartSourceScript::unpackPSS(this, mPartSourcep, dp)) + if (!LLViewerPartSourceScript::unpackPSS(this, mPartSourcep, dp, legacy)) { mPartSourcep->setDead(); mPartSourcep = NULL; @@ -4783,7 +5018,7 @@ void LLViewerObject::unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_ } else { - LLPointer pss = LLViewerPartSourceScript::unpackPSS(this, NULL, dp); + LLPointer pss = LLViewerPartSourceScript::unpackPSS(this, NULL, dp, legacy); //If the owner is muted, don't create the system if(LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagParticles)) return; // We need to be able to deal with a particle source that hasn't changed, but still got an update! @@ -5633,6 +5868,11 @@ F32 LLAlphaObject::getPartSize(S32 idx) return 0.f; } +void LLAlphaObject::getBlendFunc(S32 face, U32& src, U32& dst) +{ + +} + // virtual void LLStaticViewerObject::updateDrawable(BOOL force_damped) { diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index d2ea9512d..2aa0e3869 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -224,6 +224,7 @@ public: virtual BOOL isFlexible() const { return FALSE; } virtual BOOL isSculpted() const { return FALSE; } virtual BOOL isMesh() const { return FALSE; } + virtual BOOL hasLightTexture() const { return FALSE; } // This method returns true if the object is over land owned by // the agent, one of its groups, or it encroaches and @@ -306,7 +307,11 @@ public: /*virtual*/ void setNumTEs(const U8 num_tes); /*virtual*/ void setTE(const U8 te, const LLTextureEntry &texture_entry); /*virtual*/ S32 setTETexture(const U8 te, const LLUUID &uuid); - S32 setTETextureCore(const U8 te, LLViewerTexture *image); + /*virtual*/ S32 setTENormalMap(const U8 te, const LLUUID &uuid); + /*virtual*/ S32 setTESpecularMap(const U8 te, const LLUUID &uuid); + S32 setTETextureCore(const U8 te, LLViewerTexture *image); + S32 setTENormalMapCore(const U8 te, LLViewerTexture *image); + S32 setTESpecularMapCore(const U8 te, LLViewerTexture *image); /*virtual*/ S32 setTEColor(const U8 te, const LLColor3 &color); /*virtual*/ S32 setTEColor(const U8 te, const LLColor4 &color); /*virtual*/ S32 setTEScale(const U8 te, const F32 s, const F32 t); @@ -323,11 +328,25 @@ public: /*virtual*/ S32 setTEFullbright(const U8 te, const U8 fullbright ); /*virtual*/ S32 setTEMediaFlags(const U8 te, const U8 media_flags ); /*virtual*/ S32 setTEGlow(const U8 te, const F32 glow); + /*virtual*/ S32 setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID); + /*virtual*/ S32 setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams); + + // Used by Materials update functions to properly kick off rebuilds + // of VBs etc when materials updates require changes. + // + void refreshMaterials(); + /*virtual*/ BOOL setMaterial(const U8 material); virtual void setTEImage(const U8 te, LLViewerTexture *imagep); // Not derived from LLPrimitive - virtual void changeTEImage(S32 index, LLViewerTexture* new_image) ; + virtual void changeTEImage(S32 index, LLViewerTexture* new_image) ; + virtual void changeTENormalMap(S32 index, LLViewerTexture* new_image) ; + virtual void changeTESpecularMap(S32 index, LLViewerTexture* new_image) ; LLViewerTexture *getTEImage(const U8 te) const; + LLViewerTexture *getTENormalMap(const U8 te) const; + LLViewerTexture *getTESpecularMap(const U8 te) const; + bool isImageAlphaBlended(const U8 te) const; + void fitFaceTexture(const U8 face); void sendTEUpdate() const; // Sends packed representation of all texture entry information @@ -596,6 +615,7 @@ public: } EPhysicsShapeType; LLUUID mID; + LLUUID mOwnerID; //null if unknown // unique within region, not unique across regions // Local ID = 0 is not used @@ -608,6 +628,8 @@ public: S32 mListIndex; LLPointer *mTEImages; + LLPointer *mTENormalMaps; + LLPointer *mTESpecularMaps; // Selection, picking and rendering variables U32 mGLName; // GL "name" used by selection code @@ -679,7 +701,7 @@ protected: BOOL isOnMap(); void unpackParticleSource(const S32 block_num, const LLUUID& owner_id); - void unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_id); + void unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_id, bool legacy); void deleteParticleSource(); void setParticleSource(const LLPartSysData& particle_parameters, const LLUUID& owner_id); @@ -847,8 +869,11 @@ public: LLStrider& normalsp, LLStrider& texcoordsp, LLStrider& colorsp, + LLStrider& emissivep, LLStrider& indicesp) = 0; + virtual void getBlendFunc(S32 face, U32& src, U32& dst); + F32 mDepth; }; diff --git a/indra/newview/llviewerpartsim.cpp b/indra/newview/llviewerpartsim.cpp index 92abe4bf2..660e48f22 100644 --- a/indra/newview/llviewerpartsim.cpp +++ b/indra/newview/llviewerpartsim.cpp @@ -86,12 +86,31 @@ LLViewerPart::LLViewerPart() : mImagep(NULL) { mPartSourcep = NULL; - + mParent = NULL; + mChild = NULL; ++LLViewerPartSim::sParticleCount2 ; } LLViewerPart::~LLViewerPart() { + if (mPartSourcep.notNull() && mPartSourcep->mLastPart == this) + { + mPartSourcep->mLastPart = NULL; + } + + //patch up holes in the ribbon + if (mParent) + { + llassert(mParent->mChild == this); + mParent->mChild = mChild; + } + + if (mChild) + { + llassert (mChild->mParent == this); + mChild->mParent = mParent; + } + mPartSourcep = NULL; --LLViewerPartSim::sParticleCount2 ; @@ -147,7 +166,11 @@ LLViewerPartGroup::LLViewerPartGroup(const LLVector3 ¢er_agent, const F32 bo } mVOPartGroupp->setViewerPartGroup(this); mVOPartGroupp->setPositionAgent(getCenterAgent()); + + mBoxSide = box_side; + F32 scale = box_side * 0.5f; + mVOPartGroupp->setScale(LLVector3(scale,scale,scale)); //gPipeline.addObject(mVOPartGroupp); @@ -373,6 +396,9 @@ void LLViewerPartGroup::updateParticles(const F32 lastdt) part->mScale += frac*part->mEndScale; } + // Do glow interpolation + part->mGlow.mV[3] = (U8) llround(lerp(part->mStartGlow, part->mEndGlow, frac)*255.f); + // Set the last update time to now. part->mLastUpdateTime = cur_time; @@ -491,11 +517,16 @@ void LLViewerPartSim::destroyClass() mViewerPartSources.clear(); } +//static BOOL LLViewerPartSim::shouldAddPart() { + if (sParticleCount >= MAX_PART_COUNT) + { + return FALSE; + } + if (sParticleCount > PART_THROTTLE_THRESHOLD*sMaxParticleCount) { - F32 frac = (F32)sParticleCount/(F32)sMaxParticleCount; frac -= PART_THROTTLE_THRESHOLD; frac *= PART_THROTTLE_RESCALE; @@ -505,7 +536,10 @@ BOOL LLViewerPartSim::shouldAddPart() return FALSE; } } - if (sParticleCount >= MAX_PART_COUNT) + + // Check frame rate, and don't add more if the viewer is really slow + const F32 MIN_FRAME_RATE_FOR_NEW_PARTICLES = 4.f; + if (gFPSClamped < MIN_FRAME_RATE_FOR_NEW_PARTICLES) { return FALSE; } @@ -621,6 +655,9 @@ void LLViewerPartSim::updateSimulation() { static LLFrameTimer update_timer; + //reset VBO cursor + LLVOPartGroup::sVBSlotCursor = 0; + const F32 dt = llmin(update_timer.getElapsedTimeAndResetF32(), 0.1f); if (!(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES))) diff --git a/indra/newview/llviewerpartsim.h b/indra/newview/llviewerpartsim.h index 7e625f2c0..73d5096d7 100644 --- a/indra/newview/llviewerpartsim.h +++ b/indra/newview/llviewerpartsim.h @@ -71,15 +71,22 @@ public: LLVPCallback mVPCallback; // Callback function for more complicated behaviors LLPointer mPartSourcep; // Particle source used for this object - + + LLViewerPart* mParent; // particle to connect to if this is part of a particle ribbon + LLViewerPart* mChild; // child particle for clean reference destruction // Current particle state (possibly used for rendering) LLPointer mImagep; LLVector3 mPosAgent; LLVector3 mVelocity; LLVector3 mAccel; + LLVector3 mAxis; LLColor4 mColor; LLVector2 mScale; + F32 mStartGlow; + F32 mEndGlow; + LLColor4U mGlow; + static U32 sNextPartID; }; @@ -104,6 +111,9 @@ public: void shift(const LLVector3 &offset); + F32 getBoxRadius() { return mBoxRadius; } + F32 getBoxSide() { return mBoxSide; } + typedef std::vector part_list_t; part_list_t mParticles; @@ -124,6 +134,7 @@ public: protected: LLVector3 mCenterAgent; F32 mBoxRadius; + F32 mBoxSide; LLVector3 mMinObjPos; LLVector3 mMaxObjPos; @@ -148,7 +159,7 @@ public: void cleanupRegion(LLViewerRegion *regionp); - BOOL shouldAddPart(); // Just decides whether this particle should be added or not (for particle count capping) + static BOOL shouldAddPart(); // Just decides whether this particle should be added or not (for particle count capping) F32 maxRate() // Return maximum particle generation rate { if (sParticleCount >= MAX_PART_COUNT) diff --git a/indra/newview/llviewerpartsource.cpp b/indra/newview/llviewerpartsource.cpp index d712138fd..ebda6fd78 100644 --- a/indra/newview/llviewerpartsource.cpp +++ b/indra/newview/llviewerpartsource.cpp @@ -58,6 +58,8 @@ LLViewerPartSource::LLViewerPartSource(const U32 type) : static U32 id_seed = 0; mID = ++id_seed; + mLastPart = NULL; + mDelay = 0 ; } @@ -285,6 +287,22 @@ void LLViewerPartSourceScript::update(const F32 dt) { part->mFlags |= LLPartData::LL_PART_HUD; } + + if (part->mFlags & LLPartData::LL_PART_RIBBON_MASK && mLastPart) + { //set previous particle's parent to this particle to chain ribbon together + mLastPart->mParent = part; + part->mChild = mLastPart; + part->mAxis = LLVector3(0,0,1); + + if (mSourceObjectp.notNull()) + { + LLQuaternion rot = mSourceObjectp->getRenderRotation(); + part->mAxis *= rot; + } + } + + mLastPart = part; + part->mMaxAge = mPartSysData.mPartData.mMaxAge; part->mStartColor = mPartSysData.mPartData.mStartColor; part->mEndColor = mPartSysData.mPartData.mEndColor; @@ -296,6 +314,13 @@ void LLViewerPartSourceScript::update(const F32 dt) part->mAccel = mPartSysData.mPartAccel; + part->mBlendFuncDest = mPartSysData.mPartData.mBlendFuncDest; + part->mBlendFuncSource = mPartSysData.mPartData.mBlendFuncSource; + + part->mStartGlow = mPartSysData.mPartData.mStartGlow; + part->mEndGlow = mPartSysData.mPartData.mEndGlow; + part->mGlow = LLColor4U(0, 0, 0, (U8) llround(part->mStartGlow*255.f)); + if (mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_DROP) { part->mPosAgent = mPosAgent; @@ -436,28 +461,51 @@ LLPointer LLViewerPartSourceScript::unpackPSS(LLViewer } -LLPointer LLViewerPartSourceScript::unpackPSS(LLViewerObject *source_objp, LLPointer pssp, LLDataPacker &dp) +LLPointer LLViewerPartSourceScript::unpackPSS(LLViewerObject *source_objp, LLPointer pssp, LLDataPacker &dp, bool legacy) { if (!pssp) { LLPointer new_pssp = new LLViewerPartSourceScript(source_objp); - if (!new_pssp->mPartSysData.unpack(dp)) + if (legacy) { - return NULL; + if (!new_pssp->mPartSysData.unpackLegacy(dp)) + { + return NULL; + } } + else + { + if (!new_pssp->mPartSysData.unpack(dp)) + { + return NULL; + } + } + if (new_pssp->mPartSysData.mTargetUUID.notNull()) { LLViewerObject *target_objp = gObjectList.findObject(new_pssp->mPartSysData.mTargetUUID); new_pssp->setTargetObject(target_objp); } + return new_pssp; } else { - if (!pssp->mPartSysData.unpack(dp)) + if (legacy) { - return NULL; + if (!pssp->mPartSysData.unpackLegacy(dp)) + { + return NULL; + } } + else + { + if (!pssp->mPartSysData.unpack(dp)) + { + return NULL; + } + } + if (pssp->mPartSysData.mTargetUUID.notNull()) { LLViewerObject *target_objp = gObjectList.findObject(pssp->mPartSysData.mTargetUUID); @@ -575,6 +623,11 @@ void LLViewerPartSourceSpiral::update(const F32 dt) part->mScale.mV[0] = 0.25f; part->mScale.mV[1] = 0.25f; part->mParameter = ll_frand(F_TWO_PI); + part->mBlendFuncDest = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; + part->mBlendFuncSource = LLRender::BF_SOURCE_ALPHA; + part->mStartGlow = 0.f; + part->mEndGlow = 0.f; + part->mGlow = LLColor4U(0, 0, 0, 0); LLViewerPartSim::getInstance()->addPart(part); } @@ -727,6 +780,12 @@ void LLViewerPartSourceBeam::update(const F32 dt) part->mPosAgent = mPosAgent; part->mVelocity = mTargetPosAgent - mPosAgent; + part->mBlendFuncDest = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; + part->mBlendFuncSource = LLRender::BF_SOURCE_ALPHA; + part->mStartGlow = 0.f; + part->mEndGlow = 0.f; + part->mGlow = LLColor4U(0, 0, 0, 0); + LLViewerPartSim::getInstance()->addPart(part); } } @@ -831,6 +890,12 @@ void LLViewerPartSourceChat::update(const F32 dt) part->mScale.mV[0] = 0.25f; part->mScale.mV[1] = 0.25f; part->mParameter = ll_frand(F_TWO_PI); + part->mBlendFuncDest = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; + part->mBlendFuncSource = LLRender::BF_SOURCE_ALPHA; + part->mStartGlow = 0.f; + part->mEndGlow = 0.f; + part->mGlow = LLColor4U(0, 0, 0, 0); + LLViewerPartSim::getInstance()->addPart(part); } diff --git a/indra/newview/llviewerpartsource.h b/indra/newview/llviewerpartsource.h index 540e30eb1..d05d50d36 100644 --- a/indra/newview/llviewerpartsource.h +++ b/indra/newview/llviewerpartsource.h @@ -82,6 +82,7 @@ public: LLVector3 mLastUpdatePosAgent; LLPointer mSourceObjectp; U32 mID; + LLViewerPart* mLastPart; //last particle emitted (for making particle ribbons) protected: U32 mType; @@ -120,7 +121,7 @@ public: // Returns a new particle source to attach to an object... static LLPointer unpackPSS(LLViewerObject *source_objp, LLPointer pssp, const S32 block_num); - static LLPointer unpackPSS(LLViewerObject *source_objp, LLPointer pssp, LLDataPacker &dp); + static LLPointer unpackPSS(LLViewerObject *source_objp, LLPointer pssp, LLDataPacker &dp, bool legacy); static LLPointer createPSS(LLViewerObject *source_objp, const LLPartSysData& particle_parameters); LLViewerTexture *getImage() const { return mImagep; } diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 9402996b4..161ac8f91 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -2093,3 +2093,45 @@ boost::signals2::connection LLViewerRegion::setFeaturesReceivedCallback(const fe return mFeaturesReceivedSignal.connect(cb); } +void LLViewerRegion::resetMaterialsCapThrottle() +{ + F32 requests_per_sec = 1.0f; // original default; + if ( mSimulatorFeatures.has("RenderMaterialsCapability") + && mSimulatorFeatures["RenderMaterialsCapability"].isReal() ) + { + requests_per_sec = mSimulatorFeatures["RenderMaterialsCapability"].asReal(); + if ( requests_per_sec == 0.0f ) + { + requests_per_sec = 1.0f; + LL_WARNS("Materials") + << "region '" << getName() + << "' returned zero for RenderMaterialsCapability; using default " + << requests_per_sec << " per second" + << LL_ENDL; + } + LL_DEBUGS("Materials") << "region '" << getName() + << "' RenderMaterialsCapability " << requests_per_sec + << LL_ENDL; + } + else + { + LL_DEBUGS("Materials") + << "region '" << getName() + << "' did not return RenderMaterialsCapability, using default " + << requests_per_sec << " per second" + << LL_ENDL; + } + + mMaterialsCapThrottleTimer.resetWithExpiry( 1.0f / requests_per_sec ); +} + +U32 LLViewerRegion::getMaxMaterialsPerTransaction() const +{ + U32 max_entries = 50; // original hard coded default + if ( mSimulatorFeatures.has( "MaxMaterialsPerTransaction" ) + && mSimulatorFeatures[ "MaxMaterialsPerTransaction" ].isInteger()) + { + max_entries = mSimulatorFeatures[ "MaxMaterialsPerTransaction" ].asInteger(); + } + return max_entries; +} \ No newline at end of file diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index 6b0e73b49..c2cc7fe39 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -358,7 +358,12 @@ public: void setGamingData(const LLSD& info); const U32 getGamingFlags() const { return mGamingFlags; } - + + // implements the materials capability throttle + bool materialsCapThrottled() const { return !mMaterialsCapThrottleTimer.hasExpired(); } + void resetMaterialsCapThrottle(); + + U32 getMaxMaterialsPerTransaction() const; public: struct CompareDistance { @@ -458,6 +463,9 @@ private: LLSD mSimulatorFeatures; U32 mGamingFlags; + // the materials capability throttle + LLFrameTimer mMaterialsCapThrottleTimer; +LLFrameTimer mRenderInfoRequestTimer; }; inline BOOL LLViewerRegion::getRegionProtocol(U64 protocol) const diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 61c4fb06d..57d7e2cc9 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -106,6 +106,7 @@ LLGLSLShader gSolidColorProgram(LLViewerShaderMgr::SHADER_INTERFACE); //object shaders LLGLSLShader gObjectSimpleProgram(LLViewerShaderMgr::SHADER_OBJECT); +LLGLSLShader gObjectSimpleImpostorProgram(LLViewerShaderMgr::SHADER_OBJECT); LLGLSLShader gObjectPreviewProgram(LLViewerShaderMgr::SHADER_OBJECT); LLGLSLShader gObjectSimpleWaterProgram(LLViewerShaderMgr::SHADER_OBJECT); LLGLSLShader gObjectSimpleAlphaMaskProgram(LLViewerShaderMgr::SHADER_OBJECT); @@ -185,6 +186,7 @@ LLGLSLShader gPostVignetteProgram(LLViewerShaderMgr::SHADER_EFFECT); //Not in // Deferred rendering shaders LLGLSLShader gDeferredImpostorProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredWaterProgram(LLViewerShaderMgr::SHADER_DEFERRED); //calculatesAtmospherics +LLGLSLShader gDeferredUnderWaterProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredDiffuseProgram(LLViewerShaderMgr::SHADER_DEFERRED);//Not in mShaderList LLGLSLShader gDeferredDiffuseAlphaMaskProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredNonIndexedDiffuseProgram(LLViewerShaderMgr::SHADER_DEFERRED); @@ -200,30 +202,44 @@ LLGLSLShader gDeferredTreeShadowProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredAvatarProgram(LLViewerShaderMgr::SHADER_DEFERRED); //Not in mShaderList LLGLSLShader gDeferredAvatarAlphaProgram(LLViewerShaderMgr::SHADER_DEFERRED); //calculatesAtmospherics LLGLSLShader gDeferredLightProgram(LLViewerShaderMgr::SHADER_DEFERRED); -LLGLSLShader gDeferredMultiLightProgram(LLViewerShaderMgr::SHADER_DEFERRED); +LLGLSLShaderArray gDeferredMultiLightProgram[16]; LLGLSLShader gDeferredSpotLightProgram(LLViewerShaderMgr::SHADER_DEFERRED); //Not in mShaderList LLGLSLShader gDeferredMultiSpotLightProgram(LLViewerShaderMgr::SHADER_DEFERRED); //Not in mShaderList LLGLSLShader gDeferredSunProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredBlurLightProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredSoftenProgram(LLViewerShaderMgr::SHADER_DEFERRED); +LLGLSLShader gDeferredSoftenWaterProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredShadowProgram(LLViewerShaderMgr::SHADER_DEFERRED); //Not in mShaderList LLGLSLShader gDeferredShadowCubeProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredShadowAlphaMaskProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredAvatarShadowProgram(LLViewerShaderMgr::SHADER_DEFERRED);//Not in mShaderList LLGLSLShader gDeferredAttachmentShadowProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredAlphaProgram(LLViewerShaderMgr::SHADER_DEFERRED); //calculatesAtmospherics +LLGLSLShader gDeferredAlphaImpostorProgram(LLViewerShaderMgr::SHADER_DEFERRED); +LLGLSLShader gDeferredAlphaWaterProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredFullbrightProgram(LLViewerShaderMgr::SHADER_DEFERRED); +LLGLSLShader gDeferredFullbrightAlphaMaskProgram(LLViewerShaderMgr::SHADER_DEFERRED); +LLGLSLShader gDeferredFullbrightWaterProgram(LLViewerShaderMgr::SHADER_DEFERRED); +LLGLSLShader gDeferredFullbrightAlphaMaskWaterProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredEmissiveProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredPostProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredCoFProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredDoFCombineProgram(LLViewerShaderMgr::SHADER_DEFERRED); +LLGLSLShader gDeferredPostGammaCorrectProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gFXAAProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredPostNoDoFProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredWLSkyProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredWLCloudProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gDeferredStarProgram(LLViewerShaderMgr::SHADER_DEFERRED); +LLGLSLShader gDeferredFullbrightShinyProgram(LLViewerShaderMgr::SHADER_DEFERRED); +LLGLSLShader gDeferredSkinnedFullbrightShinyProgram(LLViewerShaderMgr::SHADER_DEFERRED); +LLGLSLShader gDeferredSkinnedFullbrightProgram(LLViewerShaderMgr::SHADER_DEFERRED); LLGLSLShader gNormalMapGenProgram(LLViewerShaderMgr::SHADER_DEFERRED); +// Deferred materials shaders +LLGLSLShaderArray gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2]; +LLGLSLShaderArray gDeferredMaterialWaterProgram[LLMaterial::SHADER_COUNT*2]; + LLViewerShaderMgr::LLViewerShaderMgr() : mVertexShaderLevel(SHADER_COUNT, 0), mMaxAvatarShaderLevel(0) @@ -343,6 +359,7 @@ void LLViewerShaderMgr::setShaders() { //using shaders, disable fixed function LLGLSLShader::sNoFixedFunction = true; + S32 light_class = 2; S32 env_class = 2; S32 obj_class = 2; @@ -352,6 +369,12 @@ void LLViewerShaderMgr::setShaders() S32 deferred_class = 0; S32 transform_class = gGLManager.mHasTransformFeedback ? 1 : 0; + static LLCachedControl use_transform_feedback(gSavedSettings, "RenderUseTransformFeedback"); + if (!use_transform_feedback) + { + transform_class = 0; + } + if (LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") && gSavedSettings.getBOOL("RenderDeferred") && gSavedSettings.getBOOL("RenderAvatarVP") && @@ -582,9 +605,6 @@ BOOL LLViewerShaderMgr::loadBasicShaders() // Load basic dependency shaders first // All of these have to load for any shaders to function -#if LL_DARWIN // Mac can't currently handle all 8 lights, - S32 sum_lights_class = 2; -#else S32 sum_lights_class = 3; // class one cards will get the lower sum lights @@ -595,7 +615,6 @@ BOOL LLViewerShaderMgr::loadBasicShaders() { sum_lights_class = 2; } -#endif // If we have sun and moon only checked, then only sum those lights. if (gPipeline.getLightingDetail() == 0) @@ -603,6 +622,14 @@ BOOL LLViewerShaderMgr::loadBasicShaders() sum_lights_class = 1; } +#if LL_DARWIN + // Work around driver crashes on older Macs when using deferred rendering + // NORSPEC-59 + // + if (gGLManager.mIsMobileGF) + sum_lights_class = 3; +#endif + // Use the feature table to mask out the max light level to use. Also make sure it's at least 1. S32 max_light_class = gSavedSettings.getS32("RenderShaderLightingMaxLevel"); sum_lights_class = llclamp(sum_lights_class, 1, max_light_class); @@ -1084,20 +1111,19 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() if (success) { gDeferredSkinnedAlphaProgram.mName = "Deferred Skinned Alpha Shader"; - gDeferredSkinnedAlphaProgram.mFeatures.atmosphericHelpers = true; gDeferredSkinnedAlphaProgram.mFeatures.hasObjectSkinning = true; - gDeferredSkinnedAlphaProgram.mFeatures.calculatesAtmospherics = true; - gDeferredSkinnedAlphaProgram.mFeatures.hasGamma = true; - gDeferredSkinnedAlphaProgram.mFeatures.hasAtmospherics = true; gDeferredSkinnedAlphaProgram.mFeatures.calculatesLighting = false; gDeferredSkinnedAlphaProgram.mFeatures.hasLighting = false; gDeferredSkinnedAlphaProgram.mFeatures.isAlphaLighting = true; gDeferredSkinnedAlphaProgram.mFeatures.disableTextureIndex = true; gDeferredSkinnedAlphaProgram.mShaderFiles.clear(); - gDeferredSkinnedAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaSkinnedV.glsl", GL_VERTEX_SHADER_ARB)); - gDeferredSkinnedAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaNonIndexedF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredSkinnedAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredSkinnedAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER_ARB)); gDeferredSkinnedAlphaProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; - + gDeferredSkinnedAlphaProgram.addPermutation("USE_DIFFUSE_TEX", "1"); + gDeferredSkinnedAlphaProgram.addPermutation("HAS_SKIN", "1"); + gDeferredSkinnedAlphaProgram.addPermutation("USE_VERTEX_COLOR", "1"); + gDeferredSkinnedAlphaProgram.addPermutation("HAS_SHADOW", mVertexShaderLevel[SHADER_DEFERRED] > 1 ? "1" : "0"); success = gDeferredSkinnedAlphaProgram.createShader(NULL, NULL); // Hack to include uniforms for lighting without linking in lighting file @@ -1114,7 +1140,100 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() gDeferredBumpProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; success = gDeferredBumpProgram.createShader(NULL, NULL); } + + gDeferredMaterialProgram[1].mFeatures.hasLighting = false; + gDeferredMaterialProgram[5].mFeatures.hasLighting = false; + gDeferredMaterialProgram[9].mFeatures.hasLighting = false; + gDeferredMaterialProgram[13].mFeatures.hasLighting = false; + gDeferredMaterialProgram[1+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + gDeferredMaterialProgram[5+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + gDeferredMaterialProgram[9+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + gDeferredMaterialProgram[13+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + gDeferredMaterialWaterProgram[1].mFeatures.hasLighting = false; + gDeferredMaterialWaterProgram[5].mFeatures.hasLighting = false; + gDeferredMaterialWaterProgram[9].mFeatures.hasLighting = false; + gDeferredMaterialWaterProgram[13].mFeatures.hasLighting = false; + gDeferredMaterialWaterProgram[1+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + gDeferredMaterialWaterProgram[5+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + gDeferredMaterialWaterProgram[9+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + gDeferredMaterialWaterProgram[13+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + + for (U32 i = 0; i < LLMaterial::SHADER_COUNT*2; ++i) + { + if (success) + { + gDeferredMaterialProgram[i].mName = llformat("Deferred Material Shader %d", i); + + U32 alpha_mode = i & 0x3; + + gDeferredMaterialProgram[i].mShaderFiles.clear(); + gDeferredMaterialProgram[i].mShaderFiles.push_back(make_pair("deferred/materialV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredMaterialProgram[i].mShaderFiles.push_back(make_pair("deferred/materialF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredMaterialProgram[i].mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + gDeferredMaterialProgram[i].addPermutation("HAS_NORMAL_MAP", i & 0x8? "1" : "0"); + gDeferredMaterialProgram[i].addPermutation("HAS_SPECULAR_MAP", i & 0x4 ? "1" : "0"); + gDeferredMaterialProgram[i].addPermutation("DIFFUSE_ALPHA_MODE", llformat("%d", alpha_mode)); + gDeferredMaterialProgram[i].addPermutation("HAS_SUN_SHADOW", mVertexShaderLevel[SHADER_DEFERRED] > 1 ? "1" : "0"); + bool has_skin = i & 0x10; + gDeferredMaterialProgram[i].addPermutation("HAS_SKIN",has_skin ? "1" : "0"); + + if (has_skin) + { + gDeferredMaterialProgram[i].mFeatures.hasObjectSkinning = true; + } + + success = gDeferredMaterialProgram[i].createShader(NULL, NULL); + } + + if (success) + { + gDeferredMaterialWaterProgram[i].mName = llformat("Deferred Underwater Material Shader %d", i); + + U32 alpha_mode = i & 0x3; + + gDeferredMaterialWaterProgram[i].mShaderFiles.clear(); + gDeferredMaterialWaterProgram[i].mShaderFiles.push_back(make_pair("deferred/materialV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredMaterialWaterProgram[i].mShaderFiles.push_back(make_pair("deferred/materialF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredMaterialWaterProgram[i].mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + gDeferredMaterialWaterProgram[i].mShaderGroup = LLGLSLShader::SG_WATER; + + gDeferredMaterialWaterProgram[i].addPermutation("HAS_NORMAL_MAP", i & 0x8? "1" : "0"); + gDeferredMaterialWaterProgram[i].addPermutation("HAS_SPECULAR_MAP", i & 0x4 ? "1" : "0"); + gDeferredMaterialWaterProgram[i].addPermutation("DIFFUSE_ALPHA_MODE", llformat("%d", alpha_mode)); + gDeferredMaterialWaterProgram[i].addPermutation("HAS_SUN_SHADOW", mVertexShaderLevel[SHADER_DEFERRED] > 1 ? "1" : "0"); + bool has_skin = i & 0x10; + gDeferredMaterialWaterProgram[i].addPermutation("HAS_SKIN",has_skin ? "1" : "0"); + gDeferredMaterialWaterProgram[i].addPermutation("WATER_FOG","1"); + + if (has_skin) + { + gDeferredMaterialWaterProgram[i].mFeatures.hasObjectSkinning = true; + } + + success = gDeferredMaterialWaterProgram[i].createShader(NULL, NULL);//&mWLUniforms); + } + } + + gDeferredMaterialProgram[1].mFeatures.hasLighting = true; + gDeferredMaterialProgram[5].mFeatures.hasLighting = true; + gDeferredMaterialProgram[9].mFeatures.hasLighting = true; + gDeferredMaterialProgram[13].mFeatures.hasLighting = true; + gDeferredMaterialProgram[1+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + gDeferredMaterialProgram[5+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + gDeferredMaterialProgram[9+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + gDeferredMaterialProgram[13+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + + gDeferredMaterialWaterProgram[1].mFeatures.hasLighting = true; + gDeferredMaterialWaterProgram[5].mFeatures.hasLighting = true; + gDeferredMaterialWaterProgram[9].mFeatures.hasLighting = true; + gDeferredMaterialWaterProgram[13].mFeatures.hasLighting = true; + gDeferredMaterialWaterProgram[1+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + gDeferredMaterialWaterProgram[5+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + gDeferredMaterialWaterProgram[9+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + gDeferredMaterialWaterProgram[13+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + + if (success) { gDeferredTreeProgram.mName = "Deferred Tree Shader"; @@ -1152,17 +1271,22 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() gDeferredLightProgram.mShaderFiles.push_back(make_pair("deferred/pointLightV.glsl", GL_VERTEX_SHADER_ARB)); gDeferredLightProgram.mShaderFiles.push_back(make_pair("deferred/pointLightF.glsl", GL_FRAGMENT_SHADER_ARB)); gDeferredLightProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + success = gDeferredLightProgram.createShader(NULL, NULL); } + for (U32 i = 0; i < LL_DEFERRED_MULTI_LIGHT_COUNT; i++) + { if (success) { - gDeferredMultiLightProgram.mName = "Deferred MultiLight Shader"; - gDeferredMultiLightProgram.mShaderFiles.clear(); - gDeferredMultiLightProgram.mShaderFiles.push_back(make_pair("deferred/multiPointLightV.glsl", GL_VERTEX_SHADER_ARB)); - gDeferredMultiLightProgram.mShaderFiles.push_back(make_pair("deferred/multiPointLightF.glsl", GL_FRAGMENT_SHADER_ARB)); - gDeferredMultiLightProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; - success = gDeferredMultiLightProgram.createShader(NULL, NULL); + gDeferredMultiLightProgram[i].mName = llformat("Deferred MultiLight Shader %d", i); + gDeferredMultiLightProgram[i].mShaderFiles.clear(); + gDeferredMultiLightProgram[i].mShaderFiles.push_back(make_pair("deferred/multiPointLightV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredMultiLightProgram[i].mShaderFiles.push_back(make_pair("deferred/multiPointLightF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredMultiLightProgram[i].mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + gDeferredMultiLightProgram[i].addPermutation("LIGHT_COUNT", llformat("%d", i+1)); + success = gDeferredMultiLightProgram[i].createShader(NULL, NULL); + } } if (success) @@ -1224,11 +1348,8 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() if (success) { gDeferredAlphaProgram.mName = "Deferred Alpha Shader"; - gDeferredAlphaProgram.mFeatures.atmosphericHelpers = true; + gDeferredAlphaProgram.mFeatures.calculatesLighting = false; - gDeferredAlphaProgram.mFeatures.calculatesAtmospherics = true; - gDeferredAlphaProgram.mFeatures.hasGamma = true; - gDeferredAlphaProgram.mFeatures.hasAtmospherics = true; gDeferredAlphaProgram.mFeatures.hasLighting = false; gDeferredAlphaProgram.mFeatures.isAlphaLighting = true; gDeferredAlphaProgram.mFeatures.disableTextureIndex = true; //hack to disable auto-setup of texture channels @@ -1244,6 +1365,9 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() gDeferredAlphaProgram.mShaderFiles.clear(); gDeferredAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER_ARB)); gDeferredAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredAlphaProgram.addPermutation("USE_INDEXED_TEX", "1"); + gDeferredAlphaProgram.addPermutation("HAS_SHADOW", mVertexShaderLevel[SHADER_DEFERRED] > 1 ? "1" : "0"); + gDeferredAlphaProgram.addPermutation("USE_VERTEX_COLOR", "1"); gDeferredAlphaProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; success = gDeferredAlphaProgram.createShader(NULL, NULL); @@ -1253,6 +1377,86 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() gDeferredAlphaProgram.mFeatures.hasLighting = true; } + if (success) + { + gDeferredAlphaImpostorProgram.mName = "Deferred Alpha Shader"; + + gDeferredAlphaImpostorProgram.mFeatures.calculatesLighting = false; + gDeferredAlphaImpostorProgram.mFeatures.hasLighting = false; + gDeferredAlphaImpostorProgram.mFeatures.isAlphaLighting = true; + gDeferredAlphaImpostorProgram.mFeatures.disableTextureIndex = true; //hack to disable auto-setup of texture channels + if (mVertexShaderLevel[SHADER_DEFERRED] < 1) + { + gDeferredAlphaImpostorProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + } + else + { //shave off some texture units for shadow maps + gDeferredAlphaImpostorProgram.mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels - 6, 1); + } + + gDeferredAlphaImpostorProgram.mShaderFiles.clear(); + gDeferredAlphaImpostorProgram.mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredAlphaImpostorProgram.mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredAlphaImpostorProgram.addPermutation("USE_INDEXED_TEX", "1"); + gDeferredAlphaImpostorProgram.addPermutation("HAS_SHADOW", mVertexShaderLevel[SHADER_DEFERRED] > 1 ? "1" : "0"); + gDeferredAlphaImpostorProgram.addPermutation("USE_VERTEX_COLOR", "1"); + gDeferredAlphaImpostorProgram.addPermutation("FOR_IMPOSTOR", "1"); + + gDeferredAlphaImpostorProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + + success = gDeferredAlphaImpostorProgram.createShader(NULL, NULL); + + // Hack + gDeferredAlphaImpostorProgram.mFeatures.calculatesLighting = true; + gDeferredAlphaImpostorProgram.mFeatures.hasLighting = true; + } + + if (success) + { + gDeferredAlphaWaterProgram.mName = "Deferred Alpha Underwater Shader"; + gDeferredAlphaWaterProgram.mFeatures.calculatesLighting = false; + gDeferredAlphaWaterProgram.mFeatures.hasLighting = false; + gDeferredAlphaWaterProgram.mFeatures.isAlphaLighting = true; + gDeferredAlphaWaterProgram.mFeatures.disableTextureIndex = true; //hack to disable auto-setup of texture channels + if (mVertexShaderLevel[SHADER_DEFERRED] < 1) + { + gDeferredAlphaWaterProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + } + else + { //shave off some texture units for shadow maps + gDeferredAlphaWaterProgram.mFeatures.mIndexedTextureChannels = llmax(LLGLSLShader::sIndexedTextureChannels - 6, 1); + } + gDeferredAlphaWaterProgram.mShaderGroup = LLGLSLShader::SG_WATER; + gDeferredAlphaWaterProgram.mShaderFiles.clear(); + gDeferredAlphaWaterProgram.mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredAlphaWaterProgram.mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredAlphaWaterProgram.addPermutation("USE_INDEXED_TEX", "1"); + gDeferredAlphaWaterProgram.addPermutation("WATER_FOG", "1"); + gDeferredAlphaWaterProgram.addPermutation("USE_VERTEX_COLOR", "1"); + gDeferredAlphaWaterProgram.addPermutation("HAS_SHADOW", mVertexShaderLevel[SHADER_DEFERRED] > 1 ? "1" : "0"); + gDeferredAlphaWaterProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + + success = gDeferredAlphaWaterProgram.createShader(NULL, NULL); + + // Hack + gDeferredAlphaWaterProgram.mFeatures.calculatesLighting = true; + gDeferredAlphaWaterProgram.mFeatures.hasLighting = true; + } + + /*if (success) + { + gDeferredAvatarEyesProgram.mName = "Deferred Avatar Eyes Shader"; + gDeferredAvatarEyesProgram.mFeatures.calculatesAtmospherics = true; + gDeferredAvatarEyesProgram.mFeatures.hasGamma = true; + gDeferredAvatarEyesProgram.mFeatures.hasTransport = true; + gDeferredAvatarEyesProgram.mFeatures.disableTextureIndex = true; + gDeferredAvatarEyesProgram.mShaderFiles.clear(); + gDeferredAvatarEyesProgram.mShaderFiles.push_back(make_pair("deferred/avatarEyesV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredAvatarEyesProgram.mShaderFiles.push_back(make_pair("deferred/diffuseF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredAvatarEyesProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + success = gDeferredAvatarEyesProgram.createShader(NULL, NULL); + }*/ + if (success) { gDeferredFullbrightProgram.mName = "Deferred Fullbright Shader"; @@ -1267,6 +1471,98 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() success = gDeferredFullbrightProgram.createShader(NULL, NULL); } + if (success) + { + gDeferredFullbrightAlphaMaskProgram.mName = "Deferred Fullbright Alpha Masking Shader"; + gDeferredFullbrightAlphaMaskProgram.mFeatures.calculatesAtmospherics = true; + gDeferredFullbrightAlphaMaskProgram.mFeatures.hasGamma = true; + gDeferredFullbrightAlphaMaskProgram.mFeatures.hasTransport = true; + gDeferredFullbrightAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gDeferredFullbrightAlphaMaskProgram.mShaderFiles.clear(); + gDeferredFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredFullbrightAlphaMaskProgram.addPermutation("HAS_ALPHA_MASK","1"); + gDeferredFullbrightAlphaMaskProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + success = gDeferredFullbrightAlphaMaskProgram.createShader(NULL, NULL); + } + + if (success) + { + gDeferredFullbrightWaterProgram.mName = "Deferred Fullbright Underwater Shader"; + gDeferredFullbrightWaterProgram.mFeatures.calculatesAtmospherics = true; + gDeferredFullbrightWaterProgram.mFeatures.hasGamma = true; + gDeferredFullbrightWaterProgram.mFeatures.hasTransport = true; + gDeferredFullbrightWaterProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gDeferredFullbrightWaterProgram.mShaderFiles.clear(); + gDeferredFullbrightWaterProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredFullbrightWaterProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredFullbrightWaterProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + gDeferredFullbrightWaterProgram.mShaderGroup = LLGLSLShader::SG_WATER; + gDeferredFullbrightWaterProgram.addPermutation("WATER_FOG","1"); + success = gDeferredFullbrightWaterProgram.createShader(NULL, NULL); + } + + if (success) + { + gDeferredFullbrightAlphaMaskWaterProgram.mName = "Deferred Fullbright Underwater Alpha Masking Shader"; + gDeferredFullbrightAlphaMaskWaterProgram.mFeatures.calculatesAtmospherics = true; + gDeferredFullbrightAlphaMaskWaterProgram.mFeatures.hasGamma = true; + gDeferredFullbrightAlphaMaskWaterProgram.mFeatures.hasTransport = true; + gDeferredFullbrightAlphaMaskWaterProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gDeferredFullbrightAlphaMaskWaterProgram.mShaderFiles.clear(); + gDeferredFullbrightAlphaMaskWaterProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredFullbrightAlphaMaskWaterProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredFullbrightAlphaMaskWaterProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + gDeferredFullbrightAlphaMaskWaterProgram.mShaderGroup = LLGLSLShader::SG_WATER; + gDeferredFullbrightAlphaMaskWaterProgram.addPermutation("HAS_ALPHA_MASK","1"); + gDeferredFullbrightAlphaMaskWaterProgram.addPermutation("WATER_FOG","1"); + success = gDeferredFullbrightAlphaMaskWaterProgram.createShader(NULL, NULL); + } + + if (success) + { + gDeferredFullbrightShinyProgram.mName = "Deferred FullbrightShiny Shader"; + gDeferredFullbrightShinyProgram.mFeatures.calculatesAtmospherics = true; + gDeferredFullbrightShinyProgram.mFeatures.hasGamma = true; + gDeferredFullbrightShinyProgram.mFeatures.hasTransport = true; + gDeferredFullbrightShinyProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels-1; + gDeferredFullbrightShinyProgram.mShaderFiles.clear(); + gDeferredFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredFullbrightShinyProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + success = gDeferredFullbrightShinyProgram.createShader(NULL, NULL); + } + + if (success) + { + gDeferredSkinnedFullbrightProgram.mName = "Skinned Fullbright Shader"; + gDeferredSkinnedFullbrightProgram.mFeatures.calculatesAtmospherics = true; + gDeferredSkinnedFullbrightProgram.mFeatures.hasGamma = true; + gDeferredSkinnedFullbrightProgram.mFeatures.hasTransport = true; + gDeferredSkinnedFullbrightProgram.mFeatures.hasObjectSkinning = true; + gDeferredSkinnedFullbrightProgram.mFeatures.disableTextureIndex = true; + gDeferredSkinnedFullbrightProgram.mShaderFiles.clear(); + gDeferredSkinnedFullbrightProgram.mShaderFiles.push_back(make_pair("objects/fullbrightSkinnedV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredSkinnedFullbrightProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredSkinnedFullbrightProgram.mShaderLevel = mVertexShaderLevel[SHADER_OBJECT]; + success = gDeferredSkinnedFullbrightProgram.createShader(NULL, NULL); + } + + if (success) + { + gDeferredSkinnedFullbrightShinyProgram.mName = "Skinned Fullbright Shiny Shader"; + gDeferredSkinnedFullbrightShinyProgram.mFeatures.calculatesAtmospherics = true; + gDeferredSkinnedFullbrightShinyProgram.mFeatures.hasGamma = true; + gDeferredSkinnedFullbrightShinyProgram.mFeatures.hasTransport = true; + gDeferredSkinnedFullbrightShinyProgram.mFeatures.hasObjectSkinning = true; + gDeferredSkinnedFullbrightShinyProgram.mFeatures.disableTextureIndex = true; + gDeferredSkinnedFullbrightShinyProgram.mShaderFiles.clear(); + gDeferredSkinnedFullbrightShinyProgram.mShaderFiles.push_back(make_pair("objects/fullbrightShinySkinnedV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredSkinnedFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredSkinnedFullbrightShinyProgram.mShaderLevel = mVertexShaderLevel[SHADER_OBJECT]; + success = gDeferredSkinnedFullbrightShinyProgram.createShader(NULL, NULL); + } + if (success) { gDeferredEmissiveProgram.mName = "Deferred Emissive Shader"; @@ -1285,9 +1581,7 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() { // load water shader gDeferredWaterProgram.mName = "Deferred Water Shader"; - gDeferredWaterProgram.mFeatures.atmosphericHelpers = true; gDeferredWaterProgram.mFeatures.calculatesAtmospherics = true; - gDeferredWaterProgram.mFeatures.hasAtmospherics = true; gDeferredWaterProgram.mFeatures.hasGamma = true; gDeferredWaterProgram.mFeatures.hasTransport = true; gDeferredWaterProgram.mShaderFiles.clear(); @@ -1297,6 +1591,20 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() success = gDeferredWaterProgram.createShader(NULL, NULL); } + if (success) + { + // load water shader + gDeferredUnderWaterProgram.mName = "Deferred Under Water Shader"; + gDeferredUnderWaterProgram.mFeatures.calculatesAtmospherics = true; + gDeferredUnderWaterProgram.mFeatures.hasGamma = true; + gDeferredUnderWaterProgram.mFeatures.hasTransport = true; + gDeferredUnderWaterProgram.mShaderFiles.clear(); + gDeferredUnderWaterProgram.mShaderFiles.push_back(make_pair("deferred/waterV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredUnderWaterProgram.mShaderFiles.push_back(make_pair("deferred/underWaterF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredUnderWaterProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + success = gDeferredUnderWaterProgram.createShader(NULL, NULL); + } + if (success) { gDeferredSoftenProgram.mName = "Deferred Soften Shader"; @@ -1314,6 +1622,25 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() success = gDeferredSoftenProgram.createShader(NULL, NULL); } + if (success) + { + gDeferredSoftenWaterProgram.mName = "Deferred Soften Underwater Shader"; + gDeferredSoftenWaterProgram.mShaderFiles.clear(); + gDeferredSoftenWaterProgram.mShaderFiles.push_back(make_pair("deferred/softenLightV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredSoftenWaterProgram.mShaderFiles.push_back(make_pair("deferred/softenLightF.glsl", GL_FRAGMENT_SHADER_ARB)); + + gDeferredSoftenWaterProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + gDeferredSoftenWaterProgram.addPermutation("WATER_FOG", "1"); + gDeferredSoftenWaterProgram.mShaderGroup = LLGLSLShader::SG_WATER; + + if (gSavedSettings.getBOOL("RenderDeferredSSAO")) + { //if using SSAO, take screen space light map into account as if shadows are enabled + gDeferredSoftenWaterProgram.mShaderLevel = llmax(gDeferredSoftenWaterProgram.mShaderLevel, 2); + } + + success = gDeferredSoftenWaterProgram.createShader(NULL, NULL); + } + if (success) { gDeferredShadowProgram.mName = "Deferred Shadow Shader"; @@ -1391,18 +1718,17 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() if (success) { gDeferredAvatarAlphaProgram.mName = "Avatar Alpha Shader"; - gDeferredAvatarAlphaProgram.mFeatures.atmosphericHelpers = true; gDeferredAvatarAlphaProgram.mFeatures.hasSkinning = true; gDeferredAvatarAlphaProgram.mFeatures.calculatesLighting = false; - gDeferredAvatarAlphaProgram.mFeatures.calculatesAtmospherics = true; - gDeferredAvatarAlphaProgram.mFeatures.hasGamma = true; - gDeferredAvatarAlphaProgram.mFeatures.hasAtmospherics = true; gDeferredAvatarAlphaProgram.mFeatures.hasLighting = false; gDeferredAvatarAlphaProgram.mFeatures.isAlphaLighting = true; gDeferredAvatarAlphaProgram.mFeatures.disableTextureIndex = true; gDeferredAvatarAlphaProgram.mShaderFiles.clear(); - gDeferredAvatarAlphaProgram.mShaderFiles.push_back(make_pair("deferred/avatarAlphaNoColorV.glsl", GL_VERTEX_SHADER_ARB)); - gDeferredAvatarAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaNonIndexedNoColorF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredAvatarAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredAvatarAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredAvatarAlphaProgram.addPermutation("USE_DIFFUSE_TEX", "1"); + gDeferredAvatarAlphaProgram.addPermutation("IS_AVATAR_SKIN", "1"); + gDeferredAvatarAlphaProgram.addPermutation("HAS_SHADOW", mVertexShaderLevel[SHADER_DEFERRED] > 1 ? "1" : "0"); gDeferredAvatarAlphaProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; success = gDeferredAvatarAlphaProgram.createShader(NULL, NULL); @@ -1410,6 +1736,16 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() gDeferredAvatarAlphaProgram.mFeatures.calculatesLighting = true; gDeferredAvatarAlphaProgram.mFeatures.hasLighting = true; } + + if (success) + { + gDeferredPostGammaCorrectProgram.mName = "Deferred Gamma Correction Post Process"; + gDeferredPostGammaCorrectProgram.mShaderFiles.clear(); + gDeferredPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER_ARB)); + gDeferredPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredGammaCorrect.glsl", GL_FRAGMENT_SHADER_ARB)); + gDeferredPostGammaCorrectProgram.mShaderLevel = mVertexShaderLevel[SHADER_DEFERRED]; + success = gDeferredPostGammaCorrectProgram.createShader(NULL, NULL); + } if (success) { @@ -1488,10 +1824,8 @@ BOOL LLViewerShaderMgr::loadShadersDeferred() { gDeferredStarProgram.mName = "Deferred Star Program"; static std::vector shaderUniforms; - static bool bUniformsInitted = false; - if(!bUniformsInitted) + if(shaderUniforms.empty()) { - bUniformsInitted = true; shaderUniforms.push_back(LLStaticHashedString("custom_alpha")); } gDeferredStarProgram.mShaderFiles.clear(); @@ -1903,6 +2237,27 @@ BOOL LLViewerShaderMgr::loadShadersObject() success = gObjectSimpleProgram.createShader(NULL, NULL); } + if (success) + { + gObjectSimpleImpostorProgram.mName = "Simple Impostor Shader"; + gObjectSimpleImpostorProgram.mFeatures.calculatesLighting = true; + gObjectSimpleImpostorProgram.mFeatures.calculatesAtmospherics = true; + gObjectSimpleImpostorProgram.mFeatures.hasGamma = true; + gObjectSimpleImpostorProgram.mFeatures.hasAtmospherics = true; + gObjectSimpleImpostorProgram.mFeatures.hasLighting = true; + gObjectSimpleImpostorProgram.mFeatures.mIndexedTextureChannels = 0; + // force alpha mask version of lighting so we can weed out + // transparent pixels from impostor temp buffer + // + gObjectSimpleImpostorProgram.mFeatures.hasAlphaMask = true; + gObjectSimpleImpostorProgram.mShaderFiles.clear(); + gObjectSimpleImpostorProgram.mShaderFiles.push_back(make_pair("objects/simpleV.glsl", GL_VERTEX_SHADER_ARB)); + gObjectSimpleImpostorProgram.mShaderFiles.push_back(make_pair("objects/simpleF.glsl", GL_FRAGMENT_SHADER_ARB)); + gObjectSimpleImpostorProgram.mShaderLevel = mVertexShaderLevel[SHADER_OBJECT]; + + success = gObjectSimpleImpostorProgram.createShader(NULL, NULL); + } + if (success) { gObjectSimpleWaterProgram.mName = "Simple Water Shader"; diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h index b5319ae16..8ccd77d4b 100644 --- a/indra/newview/llviewershadermgr.h +++ b/indra/newview/llviewershadermgr.h @@ -2,31 +2,25 @@ * @file llviewershadermgr.h * @brief Viewer Shader Manager * - * $LicenseInfo:firstyear=2001&license=viewergpl$ - * - * Copyright (c) 2001-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * Copyright (C) 2010, Linden Research, Inc. * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -34,6 +28,9 @@ #define LL_VIEWER_SHADER_MGR_H #include "llshadermgr.h" +#include "llmaterial.h" + +#define LL_DEFERRED_MULTI_LIGHT_COUNT 16 class LLViewerShaderMgr: public LLShaderMgr { @@ -178,6 +175,14 @@ inline bool operator != (LLViewerShaderMgr::shader_iter const & a, LLViewerShade extern LLVector4 gShinyOrigin; +//For arrays, since default ctor must be used for them. +template +class LLGLSLShaderArray : public LLGLSLShader +{ +public: + LLGLSLShaderArray() : LLGLSLShader(type) {}; +}; + //transform shaders extern LLGLSLShader gTransformPositionProgram; extern LLGLSLShader gTransformTexCoordProgram; @@ -204,6 +209,7 @@ extern LLGLSLShader gOneTextureNoColorProgram; //object shaders extern LLGLSLShader gObjectSimpleProgram; +extern LLGLSLShader gObjectSimpleImpostorProgram; extern LLGLSLShader gObjectPreviewProgram; extern LLGLSLShader gObjectSimpleAlphaMaskProgram; extern LLGLSLShader gObjectSimpleWaterProgram; @@ -287,6 +293,7 @@ extern LLGLSLShader gPostGaussianBlurProgram; // Deferred rendering shaders extern LLGLSLShader gDeferredImpostorProgram; extern LLGLSLShader gDeferredWaterProgram; +extern LLGLSLShader gDeferredUnderWaterProgram; extern LLGLSLShader gDeferredDiffuseProgram; extern LLGLSLShader gDeferredDiffuseAlphaMaskProgram; extern LLGLSLShader gDeferredNonIndexedDiffuseAlphaMaskProgram; @@ -300,13 +307,14 @@ extern LLGLSLShader gDeferredTerrainProgram; extern LLGLSLShader gDeferredTreeProgram; extern LLGLSLShader gDeferredTreeShadowProgram; extern LLGLSLShader gDeferredLightProgram; -extern LLGLSLShader gDeferredMultiLightProgram; +extern LLGLSLShaderArray gDeferredMultiLightProgram[LL_DEFERRED_MULTI_LIGHT_COUNT]; extern LLGLSLShader gDeferredSpotLightProgram; extern LLGLSLShader gDeferredMultiSpotLightProgram; extern LLGLSLShader gDeferredSunProgram; extern LLGLSLShader gDeferredBlurLightProgram; extern LLGLSLShader gDeferredAvatarProgram; extern LLGLSLShader gDeferredSoftenProgram; +extern LLGLSLShader gDeferredSoftenWaterProgram; extern LLGLSLShader gDeferredShadowProgram; extern LLGLSLShader gDeferredShadowCubeProgram; extern LLGLSLShader gDeferredShadowAlphaMaskProgram; @@ -315,15 +323,27 @@ extern LLGLSLShader gDeferredCoFProgram; extern LLGLSLShader gDeferredDoFCombineProgram; extern LLGLSLShader gFXAAProgram; extern LLGLSLShader gDeferredPostNoDoFProgram; +extern LLGLSLShader gDeferredPostGammaCorrectProgram; extern LLGLSLShader gDeferredAvatarShadowProgram; extern LLGLSLShader gDeferredAttachmentShadowProgram; extern LLGLSLShader gDeferredAlphaProgram; +extern LLGLSLShader gDeferredAlphaImpostorProgram; extern LLGLSLShader gDeferredFullbrightProgram; +extern LLGLSLShader gDeferredFullbrightAlphaMaskProgram; +extern LLGLSLShader gDeferredAlphaWaterProgram; +extern LLGLSLShader gDeferredFullbrightWaterProgram; +extern LLGLSLShader gDeferredFullbrightAlphaMaskWaterProgram; extern LLGLSLShader gDeferredEmissiveProgram; extern LLGLSLShader gDeferredAvatarAlphaProgram; extern LLGLSLShader gDeferredWLSkyProgram; extern LLGLSLShader gDeferredWLCloudProgram; extern LLGLSLShader gDeferredStarProgram; +extern LLGLSLShader gDeferredFullbrightShinyProgram; +extern LLGLSLShader gDeferredSkinnedFullbrightShinyProgram; +extern LLGLSLShader gDeferredSkinnedFullbrightProgram; extern LLGLSLShader gNormalMapGenProgram; +// Deferred materials shaders +extern LLGLSLShaderArray gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2]; +extern LLGLSLShaderArray gDeferredMaterialWaterProgram[LLMaterial::SHADER_COUNT*2]; #endif diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index d14a47fa9..a1fbd85a3 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -55,13 +55,12 @@ #include "llappviewer.h" #include "llface.h" #include "llviewercamera.h" -//#include "lltextureatlas.h" -//#include "lltextureatlasmanager.h" #include "lltextureentry.h" #include "lltexturemanagerbridge.h" #include "llmediaentry.h" #include "llvovolume.h" #include "llviewermedia.h" +#include "lltexturecache.h" /////////////////////////////////////////////////////////////////////////////// // statics @@ -71,6 +70,7 @@ LLPointer LLViewerFetchedTexture::sMissingAssetImagep = LLPointer LLViewerFetchedTexture::sWhiteImagep = NULL; LLPointer LLViewerFetchedTexture::sDefaultImagep = NULL; LLPointer LLViewerFetchedTexture::sSmokeImagep = NULL; +LLPointer LLViewerFetchedTexture::sFlatNormalImagep = NULL; LLViewerMediaTexture::media_map_t LLViewerMediaTexture::sMediaMap ; #if 0 LLTexturePipelineTester* LLViewerTextureManager::sTesterp = NULL ; @@ -408,6 +408,7 @@ void LLViewerTextureManager::cleanup() LLViewerFetchedTexture::sSmokeImagep = NULL; LLViewerFetchedTexture::sMissingAssetImagep = NULL; LLViewerFetchedTexture::sWhiteImagep = NULL; + LLViewerFetchedTexture::sFlatNormalImagep = NULL; LLViewerMediaTexture::cleanUpClass() ; } @@ -614,9 +615,14 @@ void LLViewerTexture::init(bool firstinit) mMaxVirtualSizeResetCounter = mMaxVirtualSizeResetInterval ; mAdditionalDecodePriority = 0.f ; mParcelMedia = NULL ; - mNumFaces = 0 ; + mNumVolumes = 0; - mFaceList.clear() ; + mFaceList[LLRender::DIFFUSE_MAP].clear() ; + mFaceList[LLRender::NORMAL_MAP].clear() ; + mFaceList[LLRender::SPECULAR_MAP].clear() ; + mNumFaces[LLRender::DIFFUSE_MAP] = + mNumFaces[LLRender::NORMAL_MAP] = + mNumFaces[LLRender::SPECULAR_MAP] = 0 ; mVolumeList.clear(); } @@ -628,7 +634,9 @@ S8 LLViewerTexture::getType() const void LLViewerTexture::cleanup() { - mFaceList.clear() ; + mFaceList[LLRender::DIFFUSE_MAP].clear() ; + mFaceList[LLRender::NORMAL_MAP].clear() ; + mFaceList[LLRender::SPECULAR_MAP].clear() ; mVolumeList.clear(); } @@ -727,38 +735,57 @@ void LLViewerTexture::setKnownDrawSize(S32 width, S32 height) } //virtual -void LLViewerTexture::addFace(LLFace* facep) +void LLViewerTexture::addFace(U32 ch, LLFace* facep) { - if(mNumFaces >= mFaceList.size()) + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + + if(mNumFaces[ch] >= mFaceList[ch].size()) { - mFaceList.resize(2 * mNumFaces + 1) ; + mFaceList[ch].resize(2 * mNumFaces[ch] + 1) ; } - mFaceList[mNumFaces] = facep ; - facep->setIndexInTex(mNumFaces) ; - mNumFaces++ ; + mFaceList[ch][mNumFaces[ch]] = facep ; + facep->setIndexInTex(ch, mNumFaces[ch]) ; + mNumFaces[ch]++ ; mLastFaceListUpdateTimer.reset() ; } //virtual -void LLViewerTexture::removeFace(LLFace* facep) +void LLViewerTexture::removeFace(U32 ch, LLFace* facep) { - if(mNumFaces > 1) + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + + if(mNumFaces[ch] > 1) { - S32 index = facep->getIndexInTex() ; - mFaceList[index] = mFaceList[--mNumFaces] ; - mFaceList[index]->setIndexInTex(index) ; + S32 index = facep->getIndexInTex(ch) ; + llassert(index < mFaceList[ch].size()); + llassert(index < mNumFaces[ch]); + mFaceList[ch][index] = mFaceList[ch][--mNumFaces[ch]] ; + mFaceList[ch][index]->setIndexInTex(ch, index) ; } else { - mFaceList.clear() ; - mNumFaces = 0 ; + mFaceList[ch].clear() ; + mNumFaces[ch] = 0 ; } mLastFaceListUpdateTimer.reset() ; } -S32 LLViewerTexture::getNumFaces() const +S32 LLViewerTexture::getTotalNumFaces() const { - return mNumFaces ; + S32 ret = 0; + + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + { + ret += mNumFaces[i]; + } + + return ret; +} + +S32 LLViewerTexture::getNumFaces(U32 ch) const +{ + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + return mNumFaces[ch]; } @@ -781,6 +808,8 @@ void LLViewerTexture::removeVolume(LLVOVolume* volumep) if(mNumVolumes > 1) { S32 index = volumep->getIndexInTex() ; + llassert(index < mVolumeList.size()); + llassert(index < mNumVolumes); mVolumeList[index] = mVolumeList[--mNumVolumes] ; mVolumeList[index]->setIndexInTex(index) ; } @@ -802,18 +831,22 @@ void LLViewerTexture::reorganizeFaceList() static const F32 MAX_WAIT_TIME = 20.f; // seconds static const U32 MAX_EXTRA_BUFFER_SIZE = 4 ; - if(mNumFaces + MAX_EXTRA_BUFFER_SIZE > mFaceList.size()) - { - return ; - } - if(mLastFaceListUpdateTimer.getElapsedTimeF32() < MAX_WAIT_TIME) { - return ; + return; } + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + { + if(mNumFaces[i] + MAX_EXTRA_BUFFER_SIZE > mFaceList[i].size()) + { + return ; + } + + mFaceList[i].erase(mFaceList[i].begin() + mNumFaces[i], mFaceList[i].end()); + } + mLastFaceListUpdateTimer.reset() ; - mFaceList.erase(mFaceList.begin() + mNumFaces, mFaceList.end()); } void LLViewerTexture::reorganizeVolumeList() @@ -1141,12 +1174,13 @@ void LLViewerFetchedTexture::addToCreateTexture() mGLTexturep->setComponents(mComponents) ; force_update = true ; - for(U32 i = 0 ; i < mNumFaces ; i++) + for (U32 j = 0; j < LLRender::NUM_TEXTURE_CHANNELS; ++j) { - LLFace* facep = mFaceList[i]; - if(facep) + llassert(mNumFaces[j] <= mFaceList[j].size()); + + for(U32 i = 0 ; i < mNumFaces[j]; i++) { - facep->dirtyTexture() ; + mFaceList[j][i]->dirtyTexture() ; } } @@ -1595,13 +1629,30 @@ void LLViewerFetchedTexture::updateVirtualSize() addTextureStats(0.f, FALSE) ;//reset } - for(U32 i = 0 ; i < mNumFaces ; i++) - { - LLFace* facep = mFaceList[i] ; - if(facep && facep->getDrawable() && facep->getDrawable()->isRecentlyVisible()) - { - addTextureStats(facep->getVirtualSize()) ; - setAdditionalDecodePriority(facep->getImportanceToCamera()) ; + for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) + { + llassert(mNumFaces[ch] <= mFaceList[ch].size()); + + for(U32 i = 0 ; i < mNumFaces[ch]; i++) + { + LLFace* facep = mFaceList[ch][i] ; + if( facep ) + { + LLDrawable* drawable = facep->getDrawable(); + if (drawable) + { + if(drawable->isRecentlyVisible()) + { + if (getBoostLevel() == LLViewerTexture::BOOST_NONE && + drawable->getVObj() && drawable->getVObj()->isSelected()) + { + setBoostLevel(LLViewerTexture::BOOST_SELECTED); + } + addTextureStats(facep->getVirtualSize()) ; + setAdditionalDecodePriority(facep->getImportanceToCamera()) ; + } + } + } } } @@ -3197,11 +3248,14 @@ BOOL LLViewerMediaTexture::findFaces() LLViewerTexture* tex = gTextureList.findImage(mID) ; if(tex) //this media is a parcel media for tex. { - const ll_face_list_t* face_list = tex->getFaceList() ; - U32 end = tex->getNumFaces() ; - for(U32 i = 0 ; i < end ; i++) + for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) { - mMediaFaceList.push_back((*face_list)[i]) ; + const ll_face_list_t* face_list = tex->getFaceList(ch) ; + U32 end = tex->getNumFaces(ch) ; + for(U32 i = 0 ; i < end ; i++) + { + mMediaFaceList.push_back((*face_list)[i]) ; + } } } @@ -3266,7 +3320,7 @@ void LLViewerMediaTexture::addMediaToFace(LLFace* facep) return ; //no need to add the face because the media is not in playing. } - switchTexture(facep) ; + switchTexture(LLRender::DIFFUSE_MAP, facep) ; } void LLViewerMediaTexture::removeMediaFromFace(LLFace* facep) @@ -3283,19 +3337,19 @@ void LLViewerMediaTexture::removeMediaFromFace(LLFace* facep) } mIsPlaying = FALSE ; //set to remove the media from the face. - switchTexture(facep) ; + switchTexture(LLRender::DIFFUSE_MAP, facep) ; mIsPlaying = TRUE ; //set the flag back. - if(getNumFaces() < 1) //no face referencing to this media + if(getTotalNumFaces() < 1) //no face referencing to this media { stopPlaying() ; } } //virtual -void LLViewerMediaTexture::addFace(LLFace* facep) +void LLViewerMediaTexture::addFace(U32 ch, LLFace* facep) { - LLViewerTexture::addFace(facep) ; + LLViewerTexture::addFace(ch, facep) ; const LLTextureEntry* te = facep->getTextureEntry() ; if(te && te->getID().notNull()) @@ -3322,9 +3376,9 @@ void LLViewerMediaTexture::addFace(LLFace* facep) } //virtual -void LLViewerMediaTexture::removeFace(LLFace* facep) +void LLViewerMediaTexture::removeFace(U32 ch, LLFace* facep) { - LLViewerTexture::removeFace(facep) ; + LLViewerTexture::removeFace(ch, facep) ; const LLTextureEntry* te = facep->getTextureEntry() ; if(te && te->getID().notNull()) @@ -3342,24 +3396,35 @@ void LLViewerMediaTexture::removeFace(LLFace* facep) } } - // - //we have some trouble here: the texture of the face is changed. - //we need to find the former texture, and remove it from the list to avoid memory leaking. - if(!mNumFaces) + std::vector te_list; + + for (U32 ch = 0; ch < 3; ++ch) + { + // + //we have some trouble here: the texture of the face is changed. + //we need to find the former texture, and remove it from the list to avoid memory leaking. + + llassert(mNumFaces[ch] <= mFaceList[ch].size()); + + for(U32 j = 0 ; j < mNumFaces[ch] ; j++) + { + te_list.push_back(mFaceList[ch][j]->getTextureEntry());//all textures are in use. + } + } + + if (te_list.empty()) { mTextureList.clear() ; return ; } - S32 end = getNumFaces() ; - std::vector te_list(end) ; - S32 i = 0 ; - for(U32 j = 0 ; j < mNumFaces ; j++) - { - te_list[i++] = mFaceList[j]->getTextureEntry() ;//all textures are in use. - } + + S32 end = te_list.size(); + for(std::list< LLPointer >::iterator iter = mTextureList.begin(); iter != mTextureList.end(); ++iter) { + S32 i = 0; + for(i = 0 ; i < end ; i++) { if(te_list[i] && te_list[i]->getID() == (*iter)->getID())//the texture is in use. @@ -3404,7 +3469,7 @@ void LLViewerMediaTexture::stopPlaying() mIsPlaying = FALSE ; } -void LLViewerMediaTexture::switchTexture(LLFace* facep) +void LLViewerMediaTexture::switchTexture(U32 ch, LLFace* facep) { if(facep) { @@ -3420,7 +3485,7 @@ void LLViewerMediaTexture::switchTexture(LLFace* facep) if(mIsPlaying) //old textures switch to the media texture { - facep->switchTexture(this) ; + facep->switchTexture(ch, this) ; } else //switch to old textures. { @@ -3436,7 +3501,7 @@ void LLViewerMediaTexture::switchTexture(LLFace* facep) { tex = LLViewerFetchedTexture::sDefaultImagep ; } - facep->switchTexture(tex) ; + facep->switchTexture(ch, tex) ; } } } @@ -3475,14 +3540,17 @@ void LLViewerMediaTexture::setPlaying(BOOL playing) for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter) { - switchTexture(*iter) ; + switchTexture(LLRender::DIFFUSE_MAP, *iter) ; } } else //stop playing this media { - for(U32 i = mNumFaces ; i ; i--) + U32 ch = LLRender::DIFFUSE_MAP; + + llassert(mNumFaces[ch] <= mFaceList[ch].size()); + for(U32 i = mNumFaces[ch] ; i ; i--) { - switchTexture(mFaceList[i - 1]) ; //current face could be removed in this function. + switchTexture(ch, mFaceList[ch][i - 1]) ; //current face could be removed in this function. } } return ; @@ -3504,14 +3572,18 @@ F32 LLViewerMediaTexture::getMaxVirtualSize() if(mIsPlaying) //media is playing { - for(U32 i = 0 ; i < mNumFaces ; i++) + for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) { - LLFace* facep = mFaceList[i] ; - if(facep->getDrawable()->isRecentlyVisible()) + llassert(mNumFaces[ch] <= mFaceList[ch].size()); + for(U32 i = 0 ; i < mNumFaces[ch] ; i++) { - addTextureStats(facep->getVirtualSize()) ; - } - } + LLFace* facep = mFaceList[ch][i] ; + if(facep->getDrawable()->isRecentlyVisible()) + { + addTextureStats(facep->getVirtualSize()) ; + } + } + } } else //media is not in playing { diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index b378f578d..94eeaf19e 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -36,6 +36,7 @@ #if 0 #include "llmetricperformancetester.h" #endif +#include "llface.h" #include #include @@ -43,7 +44,6 @@ #define MIN_VIDEO_RAM_IN_MEGA_BYTES 32 #define MAX_VIDEO_RAM_IN_MEGA_BYTES 512 // 512MB max for performance reasons. -class LLFace; class LLImageGL ; class LLImageRaw; class LLViewerObject; @@ -59,6 +59,7 @@ class LLVFile; class LLMessageSystem; class LLViewerMediaImpl ; class LLVOVolume ; +class LLFace ; //But llface.h is already included...(?) class LLLoadedCallbackEntry { @@ -147,10 +148,11 @@ public: /*virtual*/ void setKnownDrawSize(S32 width, S32 height); - virtual void addFace(LLFace* facep) ; - virtual void removeFace(LLFace* facep) ; - S32 getNumFaces() const; - const ll_face_list_t* getFaceList() const {return &mFaceList;} + virtual void addFace(U32 channel, LLFace* facep) ; + virtual void removeFace(U32 channel, LLFace* facep) ; + S32 getTotalNumFaces() const; + S32 getNumFaces(U32 ch) const; + const ll_face_list_t* getFaceList(U32 channel) const {llassert(channel < LLRender::NUM_TEXTURE_CHANNELS); return &mFaceList[channel];} virtual void addVolume(LLVOVolume* volumep); virtual void removeVolume(LLVOVolume* volumep); @@ -188,8 +190,8 @@ protected: mutable F32 mAdditionalDecodePriority; // priority add to mDecodePriority. LLFrameTimer mLastReferencedTimer; - ll_face_list_t mFaceList ; //reverse pointer pointing to the faces using this image as texture - U32 mNumFaces ; + ll_face_list_t mFaceList[LLRender::NUM_TEXTURE_CHANNELS]; //reverse pointer pointing to the faces using this image as texture + U32 mNumFaces[LLRender::NUM_TEXTURE_CHANNELS]; LLFrameTimer mLastFaceListUpdateTimer ; ll_volume_list_t mVolumeList; @@ -483,6 +485,7 @@ public: static LLPointer sWhiteImagep; // Texture to show NOTHING (whiteness) static LLPointer sDefaultImagep; // "Default" texture for error cases, the only case of fetched texture which is generated in local. static LLPointer sSmokeImagep; // Old "Default" translucent texture + static LLPointer sFlatNormalImagep; // Flat normal map denoting no bumpiness on a surface }; // @@ -540,12 +543,12 @@ public: void addMediaToFace(LLFace* facep) ; void removeMediaFromFace(LLFace* facep) ; - /*virtual*/ void addFace(LLFace* facep) ; - /*virtual*/ void removeFace(LLFace* facep) ; + /*virtual*/ void addFace(U32 ch, LLFace* facep) ; + /*virtual*/ void removeFace(U32 ch, LLFace* facep) ; /*virtual*/ F32 getMaxVirtualSize() ; private: - void switchTexture(LLFace* facep) ; + void switchTexture(U32 ch, LLFace* facep) ; BOOL findFaces() ; void stopPlaying() ; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 17f4343f4..e86dbfd21 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -126,6 +126,7 @@ void LLViewerTextureList::doPreloadImages() LLTexUnit::sWhiteTexture = LLViewerFetchedTexture::sWhiteImagep->getTexName(); LLUIImageList* image_list = LLUIImageList::getInstance(); + LLViewerFetchedTexture::sFlatNormalImagep = LLViewerTextureManager::getFetchedTextureFromFile("flatnormal.tga", MIPMAP_NO, LLViewerFetchedTexture::BOOST_BUMP); image_list->initFromFile(); // turn off clamping and bilinear filtering for uv picking images diff --git a/indra/newview/llvoclouds.cpp b/indra/newview/llvoclouds.cpp index 59f1b959a..0947e6025 100644 --- a/indra/newview/llvoclouds.cpp +++ b/indra/newview/llvoclouds.cpp @@ -208,6 +208,7 @@ void LLVOClouds::getGeometry(S32 idx, LLStrider& normalsp, LLStrider& texcoordsp, LLStrider& colorsp, + LLStrider& emissivep, LLStrider& indicesp) { diff --git a/indra/newview/llvoclouds.h b/indra/newview/llvoclouds.h index 089f3efc1..95e9dc331 100644 --- a/indra/newview/llvoclouds.h +++ b/indra/newview/llvoclouds.h @@ -60,6 +60,7 @@ public: LLStrider& normalsp, LLStrider& texcoordsp, LLStrider& colorsp, + LLStrider& emissivep, LLStrider& indicesp); /*virtual*/ BOOL isActive() const; // Whether this object needs to do an idleUpdate. diff --git a/indra/newview/llvograss.cpp b/indra/newview/llvograss.cpp index 769f75978..f45fc6de2 100644 --- a/indra/newview/llvograss.cpp +++ b/indra/newview/llvograss.cpp @@ -500,6 +500,7 @@ void LLVOGrass::getGeometry(S32 idx, LLStrider& normalsp, LLStrider& texcoordsp, LLStrider& colorsp, + LLStrider& emissivep, LLStrider& indicesp) { if(!mNumBlades)//stop rendering grass @@ -733,7 +734,11 @@ void LLGrassPartition::getGeometry(LLSpatialGroup* group) facep->setIndicesIndex(index_count); facep->setVertexBuffer(buffer); facep->setPoolType(LLDrawPool::POOL_ALPHA); - object->getGeometry(facep->getTEOffset(), verticesp, normalsp, texcoordsp, colorsp, indicesp); + + //dummy parameter (unused by this implementation) + LLStrider emissivep; + + object->getGeometry(facep->getTEOffset(), verticesp, normalsp, texcoordsp, colorsp, emissivep, indicesp); vertex_count += facep->getGeomCount(); index_count += facep->getIndicesCount(); diff --git a/indra/newview/llvograss.h b/indra/newview/llvograss.h index 941e075bf..7f9b9bee8 100644 --- a/indra/newview/llvograss.h +++ b/indra/newview/llvograss.h @@ -69,6 +69,7 @@ public: LLStrider& normalsp, LLStrider& texcoordsp, LLStrider& colorsp, + LLStrider& emissivep, LLStrider& indicesp); void updateFaceSize(S32 idx) { } diff --git a/indra/newview/llvopartgroup.cpp b/indra/newview/llvopartgroup.cpp index f16436493..d9d3f2e18 100644 --- a/indra/newview/llvopartgroup.cpp +++ b/indra/newview/llvopartgroup.cpp @@ -55,23 +55,26 @@ const F32 MAX_PART_LIFETIME = 120.f; extern U64 gFrameTime; LLPointer LLVOPartGroup::sVB = NULL; -S32 LLVOPartGroup::sVBSlotFree[]; -S32* LLVOPartGroup::sVBSlotCursor = NULL; +/*S32 LLVOPartGroup::sVBSlotFree[]; +S32* LLVOPartGroup::sVBSlotCursor = NULL;*/ +S32 LLVOPartGroup::sVBSlotCursor = 0; void LLVOPartGroup::initClass() { - for (S32 i = 0; i < LL_MAX_PARTICLE_COUNT; ++i) + /*for (S32 i = 0; i < LL_MAX_PARTICLE_COUNT; ++i) { sVBSlotFree[i] = i; } - sVBSlotCursor = sVBSlotFree; + sVBSlotCursor = sVBSlotFree;*/ } //static void LLVOPartGroup::restoreGL() { - sVB = new LLVertexBuffer(VERTEX_DATA_MASK, GL_STREAM_DRAW_ARB); + + //TODO: optimize out binormal mask here. Specular and normal coords as well. + sVB = new LLVertexBuffer(VERTEX_DATA_MASK | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, GL_STREAM_DRAW_ARB); U32 count = LL_MAX_PARTICLE_COUNT; sVB->allocateBuffer(count*4, count*6, true); @@ -128,20 +131,22 @@ void LLVOPartGroup::destroyGL() //static S32 LLVOPartGroup::findAvailableVBSlot() { - if (sVBSlotCursor >= sVBSlotFree+LL_MAX_PARTICLE_COUNT) + if (sVBSlotCursor >= /*sVBSlotFree+*/LL_MAX_PARTICLE_COUNT) { //no more available slots return -1; } - S32 ret = *sVBSlotCursor; + /*S32 ret = *sVBSlotCursor; sVBSlotCursor++; - return ret; + return ret;*/ + + return sVBSlotCursor++; } bool ll_is_part_idx_allocated(S32 idx, S32* start, S32* end) { - while (start < end) + /*while (start < end) { if (*start == idx) { //not allocated (in free list) @@ -151,13 +156,14 @@ bool ll_is_part_idx_allocated(S32 idx, S32* start, S32* end) } //allocated (not in free list) - return true; + return true;*/ + return false; } //static void LLVOPartGroup::freeVBSlot(S32 idx) { - llassert(idx < LL_MAX_PARTICLE_COUNT && idx >= 0); + /*llassert(idx < LL_MAX_PARTICLE_COUNT && idx >= 0); llassert(sVBSlotCursor > sVBSlotFree); llassert(ll_is_part_idx_allocated(idx, sVBSlotCursor, sVBSlotFree+LL_MAX_PARTICLE_COUNT)); @@ -165,7 +171,7 @@ void LLVOPartGroup::freeVBSlot(S32 idx) { sVBSlotCursor--; *sVBSlotCursor = idx; - } + }*/ } LLVOPartGroup::LLVOPartGroup(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) @@ -189,17 +195,28 @@ BOOL LLVOPartGroup::isActive() const F32 LLVOPartGroup::getBinRadius() { - return mScale.mV[0]*2.f; + return mViewerPartGroupp->getBoxSide(); } void LLVOPartGroup::updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) { const LLVector3& pos_agent = getPositionAgent(); - newMin.load3( (pos_agent - mScale).mV); - newMax.load3( (pos_agent + mScale).mV); - LLVector4a pos; - pos.load3(pos_agent.mV); - mDrawable->setPositionGroup(pos); + + LLVector4a scale; + LLVector4a p; + + p.load3(pos_agent.mV); + + scale.splat(mScale.mV[0]+mViewerPartGroupp->getBoxSide()*0.5f); + + newMin.setSub(p, scale); + newMax.setAdd(p,scale); + + llassert(newMin.isFinite3()); + llassert(newMax.isFinite3()); + + llassert(p.isFinite3()); + mDrawable->setPositionGroup(p); } void LLVOPartGroup::idleUpdate(LLAgent &agent, LLWorld &world, const F64 &time) @@ -238,6 +255,37 @@ LLDrawable* LLVOPartGroup::createDrawable(LLPipeline *pipeline) const F32 MAX_PARTICLE_AREA_SCALE = 0.02f; // some tuned constant, limits on how much particle area to draw + LLUUID LLVOPartGroup::getPartOwner(S32 idx) + { + LLUUID ret = LLUUID::null; + + if (idx < (S32) mViewerPartGroupp->mParticles.size()) + { + ret = mViewerPartGroupp->mParticles[idx]->mPartSourcep->getOwnerUUID(); + } + + return ret; + } + + LLUUID LLVOPartGroup::getPartSource(S32 idx) + { + LLUUID ret = LLUUID::null; + + if (idx < (S32) mViewerPartGroupp->mParticles.size()) + { + LLViewerPart* part = mViewerPartGroupp->mParticles[idx]; + if (part && part->mPartSourcep.notNull() && + part->mPartSourcep->mSourceObjectp.notNull()) + { + LLViewerObject* source = part->mPartSourcep->mSourceObjectp; + ret = source->getID(); + } + } + + return ret; + } + + F32 LLVOPartGroup::getPartSize(S32 idx) { if (idx < (S32) mViewerPartGroupp->mParticles.size()) @@ -248,6 +296,16 @@ F32 LLVOPartGroup::getPartSize(S32 idx) return 0.f; } +void LLVOPartGroup::getBlendFunc(S32 idx, U32& src, U32& dst) +{ + if (idx < (S32) mViewerPartGroupp->mParticles.size()) + { + LLViewerPart* part = mViewerPartGroupp->mParticles[idx]; + src = part->mBlendFuncSource; + dst = part->mBlendFuncDest; + } +} + LLVector3 LLVOPartGroup::getCameraPosition() const { return gAgentCamera.getCameraPositionAgent(); @@ -307,19 +365,52 @@ BOOL LLVOPartGroup::updateGeometry(LLDrawable *drawable) mDepth = 0.f; S32 i = 0 ; LLVector3 camera_agent = getCameraPosition(); + + F32 max_scale = 0.f; + + for (i = 0 ; i < (S32)mViewerPartGroupp->mParticles.size(); i++) { const LLViewerPart *part = mViewerPartGroupp->mParticles[i]; + + //remember the largest particle + max_scale = llmax(max_scale, part->mScale.mV[0], part->mScale.mV[1]); + + if (part->mFlags & LLPartData::LL_PART_RIBBON_MASK) + { //include ribbon segment length in scale + const LLVector3* pos_agent = NULL; + if (part->mParent) + { + pos_agent = &(part->mParent->mPosAgent); + } + else if (part->mPartSourcep.notNull()) + { + pos_agent = &(part->mPartSourcep->mPosAgent); + } + + if (pos_agent) + { + F32 dist = (*pos_agent-part->mPosAgent).length(); + + max_scale = llmax(max_scale, dist); + } + } + LLVector3 part_pos_agent(part->mPosAgent); LLVector3 at(part_pos_agent - camera_agent); + F32 camera_dist_squared = at.lengthSquared(); F32 inv_camera_dist_squared; if (camera_dist_squared > 1.f) inv_camera_dist_squared = 1.f / camera_dist_squared; else inv_camera_dist_squared = 1.f; + + llassert(llfinite(inv_camera_dist_squared)); + llassert(!llisnan(inv_camera_dist_squared)); + F32 area = part->mScale.mV[0] * part->mScale.mV[1] * inv_camera_dist_squared; tot_area = llmax(tot_area, area); @@ -386,28 +477,129 @@ BOOL LLVOPartGroup::updateGeometry(LLDrawable *drawable) facep->setSize(0, 0); } + //record max scale (used to stretch bounding box for visibility culling) + + mScale.set(max_scale, max_scale, max_scale); + mDrawable->movePartition(); LLPipeline::sCompiles++; return TRUE; } -void LLVOPartGroup::getGeometry(S32 idx, - LLStrider& verticesp, - LLStrider& normalsp, - LLStrider& texcoordsp, - LLStrider& colorsp, - LLStrider& indicesp) + +BOOL LLVOPartGroup::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face, + BOOL pick_transparent, + S32* face_hit, + LLVector4a* intersection, + LLVector2* tex_coord, + LLVector4a* normal, + LLVector4a* bi_normal) { - if (idx >= (S32) mViewerPartGroupp->mParticles.size()) + LLVector4a dir; + dir.setSub(end, start); + + F32 closest_t = 2.f; + BOOL ret = FALSE; + + for (U32 idx = 0; idx < mViewerPartGroupp->mParticles.size(); ++idx) { - return; + const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx])); + + LLVector4a v[4]; + LLStrider verticesp; + verticesp = v; + + getGeometry(part, verticesp); + + F32 a,b,t; + if (LLTriangleRayIntersect(v[0], v[1], v[2], start, dir, a,b,t) || + LLTriangleRayIntersect(v[1], v[3], v[2], start, dir, a,b,t)) + { + if (t >= 0.f && + t <= 1.f && + t < closest_t) + { + ret = TRUE; + closest_t = t; + if (face_hit) + { + *face_hit = idx; + } + + if (intersection) + { + LLVector4a intersect = dir; + intersect.mul(closest_t); + intersection->setAdd(intersect, start); + } + } + } } - const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx])); + return ret; +} - LLVector4a part_pos_agent; - part_pos_agent.load3(part.mPosAgent.mV); - LLVector4a camera_agent; +void LLVOPartGroup::getGeometry(const LLViewerPart& part, + LLStrider& verticesp) +{ + if (part.mFlags & LLPartData::LL_PART_RIBBON_MASK) + { + LLVector4a axis, pos, paxis, ppos; + F32 scale, pscale; + + pos.load3(part.mPosAgent.mV); + axis.load3(part.mAxis.mV); + scale = part.mScale.mV[0]; + + if (part.mParent) + { + ppos.load3(part.mParent->mPosAgent.mV); + paxis.load3(part.mParent->mAxis.mV); + pscale = part.mParent->mScale.mV[0]; + } + else + { //use source object as position + + if (part.mPartSourcep->mSourceObjectp.notNull()) + { + LLVector3 v = LLVector3(0,0,1); + v *= part.mPartSourcep->mSourceObjectp->getRenderRotation(); + paxis.load3(v.mV); + ppos.load3(part.mPartSourcep->mPosAgent.mV); + pscale = part.mStartScale.mV[0]; + } + else + { //no source object, no parent, nothing to draw + ppos = pos; + pscale = scale; + paxis = axis; + } + } + + LLVector4a p0, p1, p2, p3; + + scale *= 0.5f; + pscale *= 0.5f; + + axis.mul(scale); + paxis.mul(pscale); + + p0.setAdd(pos, axis); + p1.setSub(pos,axis); + p2.setAdd(ppos, paxis); + p3.setSub(ppos, paxis); + + (*verticesp++) = p2; + (*verticesp++) = p3; + (*verticesp++) = p0; + (*verticesp++) = p1; + } + else + { + LLVector4a part_pos_agent; + part_pos_agent.load3(part.mPosAgent.mV); + LLVector4a camera_agent; camera_agent.load3(getCameraPosition().mV); LLVector4a at; at.setSub(part_pos_agent, camera_agent); @@ -451,45 +643,97 @@ void LLVOPartGroup::getGeometry(S32 idx, right.normalize3fast(); } - right.mul(0.5f*part.mScale.mV[0]); - up.mul(0.5f*part.mScale.mV[1]); + right.mul(0.5f*part.mScale.mV[0]); + up.mul(0.5f*part.mScale.mV[1]); - LLVector3 normal = -LLViewerCamera::getInstance()->getXAxis(); + //HACK -- the verticesp->mV[3] = 0.f here are to set the texture index to 0 (particles don't use texture batching, maybe they should) + // this works because there is actually a 4th float stored after the vertex position which is used as a texture index + // also, somebody please VECTORIZE THIS - //HACK -- the verticesp->mV[3] = 0.f here are to set the texture index to 0 (particles don't use texture batching, maybe they should) - // this works because there is actually a 4th float stored after the vertex position which is used as a texture index - // also, somebody please VECTORIZE THIS + LLVector4a ppapu; + LLVector4a ppamu; - LLVector4a ppapu; - LLVector4a ppamu; + ppapu.setAdd(part_pos_agent, up); + ppamu.setSub(part_pos_agent, up); - ppapu.setAdd(part_pos_agent, up); - ppamu.setSub(part_pos_agent, up); + verticesp->setSub(ppapu, right); + (*verticesp++).getF32ptr()[3] = 0.f; + verticesp->setSub(ppamu, right); + (*verticesp++).getF32ptr()[3] = 0.f; + verticesp->setAdd(ppapu, right); + (*verticesp++).getF32ptr()[3] = 0.f; + verticesp->setAdd(ppamu, right); + (*verticesp++).getF32ptr()[3] = 0.f; + } +} - verticesp->setSub(ppapu, right); - (*verticesp++).getF32ptr()[3] = 0.f; - verticesp->setSub(ppamu, right); - (*verticesp++).getF32ptr()[3] = 0.f; - verticesp->setAdd(ppapu, right); - (*verticesp++).getF32ptr()[3] = 0.f; - verticesp->setAdd(ppamu, right); - (*verticesp++).getF32ptr()[3] = 0.f; - *colorsp++ = part.mColor; - *colorsp++ = part.mColor; - *colorsp++ = part.mColor; - *colorsp++ = part.mColor; + +void LLVOPartGroup::getGeometry(S32 idx, + LLStrider& verticesp, + LLStrider& normalsp, + LLStrider& texcoordsp, + LLStrider& colorsp, + LLStrider& emissivep, + LLStrider& indicesp) +{ + if (idx >= (S32) mViewerPartGroupp->mParticles.size()) + { + return; + } + + const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx])); + + getGeometry(part, verticesp); + + LLColor4U pcolor; + LLColor4U color = part.mColor; + + LLColor4U pglow; + + if (part.mFlags & LLPartData::LL_PART_RIBBON_MASK) + { //make sure color blends properly + if (part.mParent) + { + pglow = part.mParent->mGlow; + pcolor = part.mParent->mColor; + } + else + { + pglow = LLColor4U(0, 0, 0, (U8) llround(255.f*part.mStartGlow)); + pcolor = part.mStartColor; + } + } + else + { + pglow = part.mGlow; + pcolor = color; + } + + *colorsp++ = pcolor; + *colorsp++ = pcolor; + *colorsp++ = color; + *colorsp++ = color; + + //if (pglow.mV[3] || part.mGlow.mV[3]) + { //only write glow if it is not zero + *emissivep++ = pglow; + *emissivep++ = pglow; + *emissivep++ = part.mGlow; + *emissivep++ = part.mGlow; + } + if (!(part.mFlags & LLPartData::LL_PART_EMISSIVE_MASK)) { //not fullbright, needs normal + LLVector3 normal = -LLViewerCamera::getInstance()->getXAxis(); *normalsp++ = normal; *normalsp++ = normal; *normalsp++ = normal; *normalsp++ = normal; } } - U32 LLVOPartGroup::getPartitionType() const { return LLViewerRegion::PARTITION_PARTICLE; @@ -622,10 +866,13 @@ void LLParticlePartition::getGeometry(LLSpatialGroup* group) LLStrider normalsp; LLStrider texcoordsp; LLStrider colorsp; + LLStrider emissivep; buffer->getVertexStrider(verticesp); buffer->getNormalStrider(normalsp); buffer->getColorStrider(colorsp); + buffer->getEmissiveStrider(emissivep); + LLSpatialGroup::drawmap_elem_t& draw_vec = group->mDrawMap[mRenderPass]; @@ -634,7 +881,7 @@ void LLParticlePartition::getGeometry(LLSpatialGroup* group) LLFace* facep = *i; LLAlphaObject* object = (LLAlphaObject*) facep->getViewerObject(); - if (!facep->isState(LLFace::PARTICLE)) + //if (!facep->isState(LLFace::PARTICLE)) { //set the indices of this face S32 idx = LLVOPartGroup::findAvailableVBSlot(); if (idx >= 0) @@ -643,7 +890,7 @@ void LLParticlePartition::getGeometry(LLSpatialGroup* group) facep->setIndicesIndex(idx*6); facep->setVertexBuffer(LLVOPartGroup::sVB); facep->setPoolType(LLDrawPool::POOL_ALPHA); - facep->setState(LLFace::PARTICLE); + //facep->setState(LLFace::PARTICLE); } else { @@ -658,9 +905,19 @@ void LLParticlePartition::getGeometry(LLSpatialGroup* group) LLStrider cur_norm = normalsp + geom_idx; LLStrider cur_tc = texcoordsp + geom_idx; LLStrider cur_col = colorsp + geom_idx; + LLStrider cur_glow = emissivep + geom_idx; - object->getGeometry(facep->getTEOffset(), cur_vert, cur_norm, cur_tc, cur_col, cur_idx); + LLColor4U* start_glow = cur_glow.get(); + + object->getGeometry(facep->getTEOffset(), cur_vert, cur_norm, cur_tc, cur_col, cur_glow, cur_idx); + BOOL has_glow = FALSE; + + if (cur_glow.get() != start_glow) + { + has_glow = TRUE; + } + llassert(facep->getGeomCount() == 4); llassert(facep->getIndicesCount() == 6); @@ -675,24 +932,37 @@ void LLParticlePartition::getGeometry(LLSpatialGroup* group) bool batched = false; - if (idx >= 0 && - draw_vec[idx]->mTexture == facep->getTexture() && - draw_vec[idx]->mFullbright == fullbright) + U32 bf_src = LLRender::BF_SOURCE_ALPHA; + U32 bf_dst = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; + + object->getBlendFunc(facep->getTEOffset(), bf_src, bf_dst); + + + if (idx >= 0) { - if (draw_vec[idx]->mEnd == facep->getGeomIndex()-1) + LLDrawInfo* info = draw_vec[idx]; + + if (info->mTexture == facep->getTexture() && + info->mHasGlow == has_glow && + info->mFullbright == fullbright && + info->mBlendFuncDst == bf_dst && + info->mBlendFuncSrc == bf_src) { - batched = true; - draw_vec[idx]->mCount += facep->getIndicesCount(); - draw_vec[idx]->mEnd += facep->getGeomCount(); - draw_vec[idx]->mVSize = llmax(draw_vec[idx]->mVSize, vsize); - } - else if (draw_vec[idx]->mStart == facep->getGeomIndex()+facep->getGeomCount()+1) - { - batched = true; - draw_vec[idx]->mCount += facep->getIndicesCount(); - draw_vec[idx]->mStart -= facep->getGeomCount(); - draw_vec[idx]->mOffset = facep->getIndicesStart(); - draw_vec[idx]->mVSize = llmax(draw_vec[idx]->mVSize, vsize); + if (draw_vec[idx]->mEnd == facep->getGeomIndex()-1) + { + batched = true; + info->mCount += facep->getIndicesCount(); + info->mEnd += facep->getGeomCount(); + info->mVSize = llmax(draw_vec[idx]->mVSize, vsize); + } + else if (draw_vec[idx]->mStart == facep->getGeomIndex()+facep->getGeomCount()+1) + { + batched = true; + info->mCount += facep->getIndicesCount(); + info->mStart -= facep->getGeomCount(); + info->mOffset = facep->getIndicesStart(); + info->mVSize = llmax(draw_vec[idx]->mVSize, vsize); + } } } @@ -709,6 +979,10 @@ void LLParticlePartition::getGeometry(LLSpatialGroup* group) info->mExtents[0] = group->mObjectExtents[0]; info->mExtents[1] = group->mObjectExtents[1]; info->mVSize = vsize; + info->mBlendFuncDst = bf_dst; + info->mBlendFuncSrc = bf_src; + info->mHasGlow = has_glow; + info->mParticle = TRUE; draw_vec.push_back(info); //for alpha sorting facep->setDrawInfo(info); diff --git a/indra/newview/llvopartgroup.h b/indra/newview/llvopartgroup.h index ecf149c67..7e3e220a7 100644 --- a/indra/newview/llvopartgroup.h +++ b/indra/newview/llvopartgroup.h @@ -48,8 +48,9 @@ public: //vertex buffer for holding all particles static LLPointer sVB; - static S32 sVBSlotFree[LL_MAX_PARTICLE_COUNT]; - static S32* sVBSlotCursor; + /*static S32 sVBSlotFree[LL_MAX_PARTICLE_COUNT]; + static S32* sVBSlotCursor;*/ + static S32 sVBSlotCursor; static void initClass(); static void restoreGL(); @@ -63,6 +64,7 @@ public: LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | + LLVertexBuffer::MAP_EMISSIVE | LLVertexBuffer::MAP_TEXTURE_INDEX }; @@ -75,20 +77,37 @@ public: virtual void updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax); virtual U32 getPartitionType() const; + /*virtual*/ BOOL lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face, + BOOL pick_transparent, + S32* face_hit, + LLVector4a* intersection, + LLVector2* tex_coord, + LLVector4a* normal, + LLVector4a* tangent); + /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); /*virtual*/ void updateTextures(); /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); /*virtual*/ BOOL updateGeometry(LLDrawable *drawable); + void getGeometry(const LLViewerPart& part, + LLStrider& verticesp); + void getGeometry(S32 idx, LLStrider& verticesp, LLStrider& normalsp, LLStrider& texcoordsp, LLStrider& colorsp, + LLStrider& emissivep, LLStrider& indicesp); void updateFaceSize(S32 idx) { } F32 getPartSize(S32 idx); + void getBlendFunc(S32 idx, U32& src, U32& dst); + LLUUID getPartOwner(S32 idx); + LLUUID getPartSource(S32 idx); + void setViewerPartGroup(LLViewerPartGroup *part_groupp) { mViewerPartGroupp = part_groupp; } LLViewerPartGroup* getViewerPartGroup() { return mViewerPartGroupp; } diff --git a/indra/newview/llvosky.cpp b/indra/newview/llvosky.cpp index ea222c34d..2c3e6fbf9 100644 --- a/indra/newview/llvosky.cpp +++ b/indra/newview/llvosky.cpp @@ -299,7 +299,7 @@ void LLSkyTex::create(const F32 brightness) void LLSkyTex::createGLImage(S32 which) { mTexture[which]->setNeedsAlphaAndPickMask(false); //Needed, else analyzeAlpha is called every frame for each texture. - mTexture[which]->createGLTexture(0, mImageRaw[which], 0, TRUE, LLViewerTexture::LOCAL); + mTexture[which]->createGLTexture(0, mImageRaw[which], 0, TRUE, LLGLTexture::LOCAL); mTexture[which]->setAddressMode(LLTexUnit::TAM_CLAMP); } diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 97802746e..0bc4f4b91 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -340,15 +340,9 @@ U32 LLVOVolume::processUpdateMessage(LLMessageSystem *mesgsys, if (!mTextureAnimp) { mTextureAnimp = new LLViewerTextureAnim(this); + mTexAnimMode = 0; } - else - { - if (!(mTextureAnimp->mMode & LLTextureAnim::SMOOTH)) - { - mTextureAnimp->reset(); - } - } - mTexAnimMode = 0; + mTextureAnimp->unpackTAMessage(mesgsys, block_num); } else @@ -461,6 +455,11 @@ U32 LLVOVolume::processUpdateMessage(LLMessageSystem *mesgsys, mFaceMappingChanged = TRUE; mTexAnimMode = 0; } + + if (value & 0x400) + { //particle system (new) + unpackParticleSource(*dp, mOwnerID, false); + } } else { @@ -905,6 +904,12 @@ LLFace* LLVOVolume::addFace(S32 f) { const LLTextureEntry* te = getTE(f); LLViewerTexture* imagep = getTEImage(f); + if (te->getMaterialParams().notNull()) + { + LLViewerTexture* normalp = getTENormalMap(f); + LLViewerTexture* specularp = getTESpecularMap(f); + return mDrawable->addFace(te, imagep, normalp, specularp); + } return mDrawable->addFace(te, imagep); } @@ -1120,7 +1125,13 @@ void LLVOVolume::sculpt() S32 max_discard = mSculptTexture->getMaxDiscardLevel(); if (discard_level > max_discard) - discard_level = max_discard; // clamp to the best we can do + { + discard_level = max_discard; // clamp to the best we can do + } + if(discard_level > MAX_DISCARD_LEVEL) + { + return; //we think data is not ready yet. + } S32 current_discard = getVolume()->getSculptLevel() ; if(current_discard < -2) @@ -1383,6 +1394,11 @@ void LLVOVolume::regenFaces() facep->setTEOffset(i); facep->setTexture(getTEImage(i)); + if (facep->getTextureEntry()->getMaterialParams().notNull()) + { + facep->setNormalMap(getTENormalMap(i)); + facep->setSpecularMap(getTESpecularMap(i)); + } facep->setViewerObject(this); // If the face had media on it, this will have broken the link between the LLViewerMediaTexture and the face. @@ -1432,7 +1448,7 @@ BOOL LLVOVolume::genBBoxes(BOOL force_global) continue; } res &= face->genVolumeBBoxes(*volume, i, - mRelativeXform, mRelativeXformInvTrans, + mRelativeXform, (mVolumeImpl && mVolumeImpl->isVolumeGlobal()) || force_global); if (rebuild) @@ -1725,6 +1741,11 @@ BOOL LLVOVolume::updateGeometry(LLDrawable *drawable) void LLVOVolume::updateFaceSize(S32 idx) { + if( mDrawable->getNumFaces() <= idx ) + { + return; + } + LLFace* facep = mDrawable->getFace(idx); if (facep) { @@ -2565,6 +2586,7 @@ void LLVOVolume::setLightTextureID(LLUUID id) if (hasLightTexture()) { setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE, FALSE, true); + parameterChanged(LLNetworkData::PARAMS_LIGHT_IMAGE, true); mLightTexture = NULL; } } @@ -2582,7 +2604,8 @@ void LLVOVolume::setSpotLightParams(LLVector3 params) void LLVOVolume::setIsLight(BOOL is_light) { - if (is_light != getIsLight()) + BOOL was_light = getIsLight(); + if (is_light != was_light) { if (is_light) { @@ -2767,7 +2790,7 @@ void LLVOVolume::updateSpotLightPriority() bool LLVOVolume::isLightSpotlight() const { LLLightImageParams* params = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); - if (params) + if (params && getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) { return params->isLightSpotlight(); } @@ -3705,8 +3728,30 @@ BOOL LLVOVolume::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& { LLFace* face = mDrawable->getFace(face_hit); + bool ignore_alpha = false; + + const LLTextureEntry* te = face->getTextureEntry(); + if (te) + { + LLMaterial* mat = te->getMaterialParams(); + if (mat) + { + U8 mode = mat->getDiffuseAlphaMode(); + + if (mode == LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE || + mode == LLMaterial::DIFFUSE_ALPHA_MODE_NONE) + { + ignore_alpha = true; + } + } + } + if (face && - (pick_transparent || !face->getTexture() || !face->getTexture()->hasGLTexture() || face->getTexture()->getMask(face->surfaceToTexture(tc, p, n)))) + (ignore_alpha || + pick_transparent || + !face->getTexture() || + !face->getTexture()->hasGLTexture() || + face->getTexture()->getMask(face->surfaceToTexture(tc, p, n)))) { local_end = p; if (face_hitp != NULL) @@ -4025,6 +4070,11 @@ bool can_batch_texture(LLFace* facep) return false; } + if (facep->getTextureEntry()->getMaterialParams().notNull()) + { //materials don't work with texture batching yet + return false; + } + if (facep->getTexture() && facep->getTexture()->getPrimaryFormat() == GL_ALPHA) { //can't batch invisiprims return false; @@ -4043,6 +4093,10 @@ static LLFastTimer::DeclareTimer FTM_REGISTER_FACE("Register Face"); void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, U32 type) { LLFastTimer t(FTM_REGISTER_FACE); + if (type == LLRenderPass::PASS_ALPHA && facep->getTextureEntry()->getMaterialParams().notNull() && !facep->getVertexBuffer()->hasDataType(LLVertexBuffer::TYPE_TANGENT)) + { + LL_WARNS("RenderMaterials") << "Oh no! No binormals for this alpha blended face!" << LL_ENDL; + } // if (facep->getViewerObject()->isSelected() && gHideSelectedObjects) // [RLVa:KB] - Checked: 2010-11-29 (RLVa-1.3.0c) | Modified: RLVa-1.3.0c @@ -4099,18 +4153,39 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, //drawable->getVObj()->setDebugText(llformat("%d", drawable->isState(LLDrawable::ANIMATED_CHILD))); U8 bump = (type == LLRenderPass::PASS_BUMP || type == LLRenderPass::PASS_POST_BUMP) ? facep->getTextureEntry()->getBumpmap() : 0; - + U8 shiny = facep->getTextureEntry()->getShiny(); + LLViewerTexture* tex = facep->getTexture(); U8 index = facep->getTextureIndex(); + LLMaterial* mat = facep->getTextureEntry()->getMaterialParams().get(); LLMaterialID mat_id = facep->getTextureEntry()->getMaterialID(); bool batchable = false; + U32 shader_mask = 0xFFFFFFFF; //no shader + + if (mat) + { + if (type == LLRenderPass::PASS_ALPHA) + { + shader_mask = mat->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_BLEND); + } + else + { + shader_mask = mat->getShaderMask(); + } + } + + if (index < 255 && idx >= 0) { - if (index < draw_vec[idx]->mTextureList.size()) + if (mat || draw_vec[idx]->mMaterial) + { //can't batch textures when materials are present (yet) + batchable = false; + } + else if (index < draw_vec[idx]->mTextureList.size()) { if (draw_vec[idx]->mTextureList[index].isNull()) { @@ -4136,10 +4211,14 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, draw_vec[idx]->mEnd - draw_vec[idx]->mStart + facep->getGeomCount() <= (U32) gGLManager.mGLMaxVertexRange && draw_vec[idx]->mCount + facep->getIndicesCount() <= (U32) gGLManager.mGLMaxIndexRange && #endif + draw_vec[idx]->mMaterial == mat && + draw_vec[idx]->mMaterialID == mat_id && draw_vec[idx]->mFullbright == fullbright && - draw_vec[idx]->mBump == bump && + draw_vec[idx]->mBump == bump && + (!mat || (draw_vec[idx]->mShiny == shiny)) && // need to break batches when a material is shared, but legacy settings are different draw_vec[idx]->mTextureMatrix == tex_mat && - draw_vec[idx]->mModelMatrix == model_mat) + draw_vec[idx]->mModelMatrix == model_mat && + draw_vec[idx]->mShaderMask == shader_mask) { draw_vec[idx]->mCount += facep->getIndicesCount(); draw_vec[idx]->mEnd += facep->getGeomCount(); @@ -4167,6 +4246,60 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, draw_vec.push_back(draw_info); draw_info->mTextureMatrix = tex_mat; draw_info->mModelMatrix = model_mat; + + draw_info->mBump = bump; + draw_info->mShiny = shiny; + + float alpha[4] = + { + 0.00f, + 0.25f, + 0.5f, + 0.75f + }; + float spec = alpha[shiny & TEM_SHINY_MASK]; + LLVector4 specColor(spec, spec, spec, spec); + draw_info->mSpecColor = specColor; + draw_info->mEnvIntensity = spec; + draw_info->mSpecularMap = NULL; + draw_info->mMaterial = mat; + draw_info->mShaderMask = shader_mask; + + if (mat) + { + draw_info->mMaterialID = mat_id; + + // We have a material. Update our draw info accordingly. + + if (!mat->getSpecularID().isNull()) + { + LLVector4 specColor; + specColor.mV[0] = mat->getSpecularLightColor().mV[0] * (1.f / 255.f); + specColor.mV[1] = mat->getSpecularLightColor().mV[1] * (1.f / 255.f); + specColor.mV[2] = mat->getSpecularLightColor().mV[2] * (1.f / 255.f); + specColor.mV[3] = mat->getSpecularLightExponent() * (1.f / 255.f); + draw_info->mSpecColor = specColor; + draw_info->mEnvIntensity = mat->getEnvironmentIntensity() * (1.f / 255.f); + draw_info->mSpecularMap = facep->getViewerObject()->getTESpecularMap(facep->getTEOffset()); + } + + draw_info->mAlphaMaskCutoff = mat->getAlphaMaskCutoff() * (1.f / 255.f); + draw_info->mDiffuseAlphaMode = mat->getDiffuseAlphaMode(); + draw_info->mNormalMap = facep->getViewerObject()->getTENormalMap(facep->getTEOffset()); + + } + else + { + if (type == LLRenderPass::PASS_GRASS) + { + draw_info->mAlphaMaskCutoff = 0.5f; + } + else + { + draw_info->mAlphaMaskCutoff = 0.33f; + } + } + if (type == LLRenderPass::PASS_ALPHA) { //for alpha sorting facep->setDrawInfo(draw_info); @@ -4284,12 +4417,18 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) static LLFace** fullbright_faces = (LLFace**) ll_aligned_malloc(MAX_FACE_COUNT*sizeof(LLFace*),64); static LLFace** bump_faces = (LLFace**) ll_aligned_malloc(MAX_FACE_COUNT*sizeof(LLFace*),64); static LLFace** simple_faces = (LLFace**) ll_aligned_malloc(MAX_FACE_COUNT*sizeof(LLFace*),64); + static LLFace** norm_faces = (LLFace**) ll_aligned_malloc(MAX_FACE_COUNT*sizeof(LLFace*), 64); + static LLFace** spec_faces = (LLFace**) ll_aligned_malloc(MAX_FACE_COUNT*sizeof(LLFace*), 64); + static LLFace** normspec_faces = (LLFace**) ll_aligned_malloc(MAX_FACE_COUNT*sizeof(LLFace*), 64); static LLFace** alpha_faces = (LLFace**) ll_aligned_malloc(MAX_FACE_COUNT*sizeof(LLFace*),64); U32 fullbright_count = 0; U32 bump_count = 0; U32 simple_count = 0; U32 alpha_count = 0; + U32 norm_count = 0; + U32 spec_count = 0; + U32 normspec_count = 0; U32 useage = group->mSpatialPartition->mBufferUsage; @@ -4465,7 +4604,26 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) LLMaterial* mat = te->getMaterialParams().get(); - if (mat) + if (mat && LLPipeline::sRenderDeferred) + { + U8 alpha_mode = mat->getDiffuseAlphaMode(); + + bool is_alpha = type == LLDrawPool::POOL_ALPHA && + (alpha_mode == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND || + te->getColor().mV[3] < 0.999f); + + if (is_alpha) + { //this face needs alpha blending, override alpha mode + alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_BLEND; + } + + if (!is_alpha || te->getColor().mV[3] > 0.f) // //only add the face if it will actually be visible + { + U32 mask = mat->getShaderMask(alpha_mode); + pool->addRiggedFace(facep, mask); + } + } + else if (mat) { bool fullbright = te->getFullbright(); bool is_alpha = type == LLDrawPool::POOL_ALPHA; @@ -4473,7 +4631,6 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) bool can_be_shiny = mode == LLMaterial::DIFFUSE_ALPHA_MODE_NONE || mode == LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE; - bool is_deferred_simple = LLPipeline::sRenderDeferred && !fullbright; if (mode == LLMaterial::DIFFUSE_ALPHA_MODE_MASK && te->getColor().mV[3] >= 0.999f) { pool->addRiggedFace(facep, fullbright ? LLDrawPoolAvatar::RIGGED_FULLBRIGHT : LLDrawPoolAvatar::RIGGED_SIMPLE); @@ -4484,21 +4641,18 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) { pool->addRiggedFace(facep, fullbright ? LLDrawPoolAvatar::RIGGED_FULLBRIGHT_ALPHA : LLDrawPoolAvatar::RIGGED_ALPHA); } - is_deferred_simple = false; } else if (gPipeline.canUseVertexShaders() && LLPipeline::sRenderBump && te->getShiny() && can_be_shiny) { - pool->addRiggedFace(facep, fullbright ? LLDrawPoolAvatar::RIGGED_FULLBRIGHT_SHINY : !LLPipeline::sRenderDeferred ? LLDrawPoolAvatar::RIGGED_SHINY : LLDrawPoolAvatar::RIGGED_SIMPLE); + pool->addRiggedFace(facep, fullbright ? LLDrawPoolAvatar::RIGGED_FULLBRIGHT_SHINY : LLDrawPoolAvatar::RIGGED_SHINY); } else { pool->addRiggedFace(facep, fullbright ? LLDrawPoolAvatar::RIGGED_FULLBRIGHT : LLDrawPoolAvatar::RIGGED_SIMPLE); } - if(is_deferred_simple) - pool->addRiggedFace(facep, LLDrawPoolAvatar::RIGGED_DEFERRED_SIMPLE); } else { @@ -4674,7 +4828,42 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) if (gPipeline.canUseWindLightShadersOnObjects() && LLPipeline::sRenderBump) { - if (te->getBumpmap()) + if (LLPipeline::sRenderDeferred && te->getMaterialParams().notNull() && !te->getMaterialID().isNull()) + { + LLMaterial* mat = te->getMaterialParams().get(); + if (mat->getNormalID().notNull()) + { + if (mat->getSpecularID().notNull()) + { //has normal and specular maps (needs texcoord1, texcoord2, and tangent) + if (normspec_count < MAX_FACE_COUNT) + { + normspec_faces[normspec_count++] = facep; + } + } + else + { //has normal map (needs texcoord1 and tangent) + if (norm_count < MAX_FACE_COUNT) + { + norm_faces[norm_count++] = facep; + } + } + } + else if (mat->getSpecularID().notNull()) + { //has specular map but no normal map, needs texcoord2 + if (spec_count < MAX_FACE_COUNT) + { + spec_faces[spec_count++] = facep; + } + } + else + { //has neither specular map nor normal map, only needs texcoord0 + if (simple_count < MAX_FACE_COUNT) + { + simple_faces[simple_count++] = facep; + } + } + } + else if (te->getBumpmap()) { //needs normal + tangent if (bump_count < MAX_FACE_COUNT) { @@ -4757,32 +4946,38 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) U32 bump_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; U32 fullbright_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; + U32 norm_mask = simple_mask | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TANGENT; + U32 normspec_mask = norm_mask | LLVertexBuffer::MAP_TEXCOORD2; + U32 spec_mask = simple_mask | LLVertexBuffer::MAP_TEXCOORD2; + if (emissive) { //emissive faces are present, include emissive byte to preserve batching simple_mask = simple_mask | LLVertexBuffer::MAP_EMISSIVE; alpha_mask = alpha_mask | LLVertexBuffer::MAP_EMISSIVE; bump_mask = bump_mask | LLVertexBuffer::MAP_EMISSIVE; fullbright_mask = fullbright_mask | LLVertexBuffer::MAP_EMISSIVE; + norm_mask = norm_mask | LLVertexBuffer::MAP_EMISSIVE; + normspec_mask = normspec_mask | LLVertexBuffer::MAP_EMISSIVE; + spec_mask = spec_mask | LLVertexBuffer::MAP_EMISSIVE; } - bool batch_textures = LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 1; + BOOL batch_textures = LLViewerShaderMgr::instance()->getVertexShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 1; if (batch_textures) { - bump_mask |= LLVertexBuffer::MAP_TANGENT; - genDrawInfo(group, simple_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, simple_faces, simple_count, FALSE, TRUE); - genDrawInfo(group, fullbright_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, fullbright_faces, fullbright_count, FALSE, TRUE); - genDrawInfo(group, bump_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, bump_faces, bump_count, FALSE, TRUE); - genDrawInfo(group, alpha_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, alpha_faces, alpha_count, TRUE, TRUE); + bump_mask = bump_mask | LLVertexBuffer::MAP_TANGENT; + simple_mask = simple_mask | LLVertexBuffer::MAP_TEXTURE_INDEX; + alpha_mask = alpha_mask | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2; + fullbright_mask = fullbright_mask | LLVertexBuffer::MAP_TEXTURE_INDEX; } - else - { - genDrawInfo(group, simple_mask, simple_faces, simple_count); - genDrawInfo(group, fullbright_mask, fullbright_faces, fullbright_count); - genDrawInfo(group, bump_mask, bump_faces, bump_count, FALSE, TRUE); - genDrawInfo(group, alpha_mask, alpha_faces, alpha_count, TRUE); - } - + + genDrawInfo(group, simple_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, simple_faces, simple_count, FALSE, batch_textures, FALSE); + genDrawInfo(group, fullbright_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, fullbright_faces, fullbright_count, FALSE, batch_textures); + genDrawInfo(group, alpha_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, alpha_faces, alpha_count, TRUE, batch_textures); + genDrawInfo(group, bump_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, bump_faces, bump_count, FALSE, FALSE); + genDrawInfo(group, norm_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, norm_faces, norm_count, FALSE, FALSE); + genDrawInfo(group, spec_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, spec_faces, spec_count, FALSE, FALSE); + genDrawInfo(group, normspec_mask | LLVertexBuffer::MAP_TEXTURE_INDEX, normspec_faces, normspec_count, FALSE, FALSE); if (!LLPipeline::sDelayVBUpdate) { @@ -4945,11 +5140,18 @@ struct CompareBatchBreakerModified { return lte->getFullbright() < rte->getFullbright(); } + else if (LLPipeline::sRenderDeferred && lte->getMaterialParams() != rte->getMaterialParams()) + { + return lte->getMaterialParams() < rte->getMaterialParams(); + } + else if (LLPipeline::sRenderDeferred && (lte->getMaterialParams() == rte->getMaterialParams()) && (lte->getShiny() != rte->getShiny())) + { + return lte->getShiny() < rte->getShiny(); + } else { return lhs->getTexture() < rhs->getTexture(); } - } }; @@ -5033,11 +5235,14 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFac //NEVER use more than 16 texture index channels (workaround for prevalent driver bug) texture_index_channels = llmin(texture_index_channels, 16); + bool flexi = false; + while (face_iter != end_faces) { //pull off next face LLFace* facep = *face_iter; LLViewerTexture* tex = facep->getTexture(); + LLMaterialPtr mat = facep->getTextureEntry()->getMaterialParams(); if (distance_sort) { @@ -5059,6 +5264,7 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFac U32 index_count = facep->getIndicesCount(); U32 geom_count = facep->getGeomCount(); + flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); //sum up vertices needed for this render batch LLFace** i = face_iter; @@ -5089,6 +5295,7 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFac if (!can_batch_texture(facep)) { //face is bump mapped or has an animated texture matrix -- can't //batch more than 1 texture at a time + facep->setTextureIndex(0); break; } if (facep->getTexture() != tex) @@ -5135,6 +5342,9 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFac } ++i; + + flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); + index_count += facep->getIndicesCount(); geom_count += facep->getGeomCount(); @@ -5142,11 +5352,18 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFac } tex = texture_list[0]; } + else + { + facep->setTextureIndex(0); + } } else { while (i != end_faces && - (LLPipeline::sTextureBindTest || (distance_sort || (*i)->getTexture() == tex))) + (LLPipeline::sTextureBindTest || + (distance_sort || + ((*i)->getTexture() == tex && + ((*i)->getTextureEntry()->getMaterialParams() == mat))))) { facep = *i; @@ -5163,8 +5380,16 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFac ++i; index_count += facep->getIndicesCount(); geom_count += facep->getGeomCount(); + + flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); + } } } + + + if (flexi && buffer_usage && buffer_usage != GL_STREAM_DRAW_ARB) + { + buffer_usage = GL_STREAM_DRAW_ARB; } //create vertex buffer @@ -5262,8 +5487,97 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFac mode == LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE; } - bool use_legacy_bump = te->getBumpmap(); - if (mat) + bool use_legacy_bump = te->getBumpmap() && (te->getBumpmap() < 18) && (!mat || mat->getNormalID().isNull()); + bool opaque = te->getColor().mV[3] >= 0.999f; + + if (mat && LLPipeline::sRenderDeferred && !hud_group) + { + bool material_pass = false; + + // do NOT use 'fullbright' for this logic or you risk sending + // things without normals down the materials pipeline and will + // render poorly if not crash NORSPEC-240,314 + // + if (te->getFullbright()) + { + if (mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) + { + if (opaque) + { + registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); + } + else + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA); + } + } + else if (is_alpha) + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA); + } + else + { + if (mat->getEnvironmentIntensity() > 0 || + te->getShiny() > 0) + { + material_pass = true; + } + else + { + registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT); + } + } + } + else if (no_materials) + { + registerFace(group, facep, LLRenderPass::PASS_SIMPLE); + } + else if (te->getColor().mV[3] < 0.999f) + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA); + } + else if (use_legacy_bump) + { + // we have a material AND legacy bump settings, but no normal map + registerFace(group, facep, LLRenderPass::PASS_BUMP); + } + else + { + material_pass = true; + } + + if (material_pass) + { + U32 pass[] = + { + LLRenderPass::PASS_MATERIAL, + LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_MATERIAL_ALPHA, + LLRenderPass::PASS_MATERIAL_ALPHA_MASK, + LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, + LLRenderPass::PASS_SPECMAP, + LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_SPECMAP_BLEND, + LLRenderPass::PASS_SPECMAP_MASK, + LLRenderPass::PASS_SPECMAP_EMISSIVE, + LLRenderPass::PASS_NORMMAP, + LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_NORMMAP_BLEND, + LLRenderPass::PASS_NORMMAP_MASK, + LLRenderPass::PASS_NORMMAP_EMISSIVE, + LLRenderPass::PASS_NORMSPEC, + LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_NORMSPEC_BLEND, + LLRenderPass::PASS_NORMSPEC_MASK, + LLRenderPass::PASS_NORMSPEC_EMISSIVE, + }; + + U32 mask = mat->getShaderMask(); + + llassert(mask < sizeof(pass)/sizeof(U32)); + + mask = llmin(mask, (U32)(sizeof(pass)/sizeof(U32)-1)); + + registerFace(group, facep, pass[mask]); + } + } + else if (mat) { U8 mode = mat->getDiffuseAlphaMode(); if (te->getColor().mV[3] < 0.999f) @@ -5284,7 +5598,7 @@ void LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFac && te->getShiny() && can_be_shiny) { - registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT_SHINY : (!LLPipeline::sRenderDeferred || hud_group) ? LLRenderPass::PASS_SHINY : LLRenderPass::PASS_SIMPLE); + registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT_SHINY : LLRenderPass::PASS_SHINY); } else { diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h index b9cb79508..3b6955951 100644 --- a/indra/newview/llvovolume.h +++ b/indra/newview/llvovolume.h @@ -164,6 +164,7 @@ public: const LLMatrix4& getWorldMatrix(LLXformMatrix* xform) const; void markForUpdate(BOOL priority) { LLViewerObject::markForUpdate(priority); mVolumeChanged = TRUE; } + void faceMappingChanged() { mFaceMappingChanged=TRUE; }; /*virtual*/ void onShift(const LLVector4a &shift_vector); // Called when the drawable shifts diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 2d1f229b8..52edd16aa 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -184,6 +184,7 @@ LLFastTimer::DeclareTimer FTM_RENDER_WL_SKY("Windlight Sky"); LLFastTimer::DeclareTimer FTM_RENDER_ALPHA("Alpha Objects"); LLFastTimer::DeclareTimer FTM_RENDER_CHARACTERS("Avatars"); LLFastTimer::DeclareTimer FTM_RENDER_BUMP("Bump"); +LLFastTimer::DeclareTimer FTM_RENDER_MATERIALS("Materials"); LLFastTimer::DeclareTimer FTM_RENDER_FULLBRIGHT("Fullbright"); LLFastTimer::DeclareTimer FTM_RENDER_GLOW("Glow"); LLFastTimer::DeclareTimer FTM_GEO_UPDATE("Geo Update"); @@ -204,12 +205,17 @@ LLFastTimer::DeclareTimer FTM_RENDER_DEFERRED("Deferred Shading"); static LLFastTimer::DeclareTimer FTM_STATESORT_DRAWABLE("Sort Drawables"); static LLFastTimer::DeclareTimer FTM_STATESORT_POSTSORT("Post Sort"); +static LLStaticHashedString sTint("tint"); +static LLStaticHashedString sAmbiance("ambiance"); +static LLStaticHashedString sAlphaScale("alpha_scale"); static LLStaticHashedString sNormMat("norm_mat"); static LLStaticHashedString sOffset("offset"); +static LLStaticHashedString sScreenRes("screenRes"); static LLStaticHashedString sDelta("delta"); static LLStaticHashedString sDistFactor("dist_factor"); static LLStaticHashedString sKern("kern"); static LLStaticHashedString sKernScale("kern_scale"); + //---------------------------------------- std::string gPoolNames[] = { @@ -219,8 +225,11 @@ std::string gPoolNames[] = "POOL_GROUND", "POOL_FULLBRIGHT", "POOL_BUMP", + "POOL_MATERIALS", "POOL_TERRAIN", "POOL_TREE", // Singu Note: Before sky for zcull. + "POOL_ALPHA_MASK", + "POOL_FULLBRIGHT_ALPHA_MASK", "POOL_SKY", "POOL_WL_SKY", "POOL_GRASS", @@ -326,6 +335,7 @@ BOOL LLPipeline::sWaterReflections = FALSE; BOOL LLPipeline::sRenderGlow = FALSE; BOOL LLPipeline::sReflectionRender = FALSE; BOOL LLPipeline::sImpostorRender = FALSE; +BOOL LLPipeline::sImpostorRenderAlphaDepthPass = FALSE; BOOL LLPipeline::sUnderWaterRender = FALSE; BOOL LLPipeline::sTextureBindTest = FALSE; BOOL LLPipeline::sRenderFrameTest = FALSE; @@ -335,6 +345,7 @@ BOOL LLPipeline::sRenderDeferred = FALSE; BOOL LLPipeline::sMemAllocationThrottled = FALSE; S32 LLPipeline::sVisibleLightCount = 0; F32 LLPipeline::sMinRenderSize = 0.f; +BOOL LLPipeline::sRenderingHUDs; static LLCullResult* sCull = NULL; @@ -354,9 +365,9 @@ void validate_framebuffer_object(); bool addDeferredAttachments(LLRenderTarget& target) { - static const LLCachedControl SHPrecisionDeferredNormals("SHPrecisionDeferredNormals",false); - return target.addColorAttachment(GL_RGBA) && //specular - target.addColorAttachment(SHPrecisionDeferredNormals ? GL_RGB10_A2 : GL_RGBA); //normal+z + //static const LLCachedControl SHPrecisionDeferredNormals("SHPrecisionDeferredNormals",false); //DEAD + return target.addColorAttachment(GL_SRGB8_ALPHA8) && //specular + target.addColorAttachment(GL_RGB10_A2); //normal+z } LLPipeline::LLPipeline() : @@ -392,10 +403,14 @@ LLPipeline::LLPipeline() : mWaterPool(NULL), mGroundPool(NULL), mSimplePool(NULL), + mGrassPool(NULL), + mAlphaMaskPool(NULL), + mFullbrightAlphaMaskPool(NULL), mFullbrightPool(NULL), mInvisiblePool(NULL), mGlowPool(NULL), mBumpPool(NULL), + mMaterialsPool(NULL), mWLSkyPool(NULL), mLightMask(0), mLightMovingMask(0), @@ -427,10 +442,13 @@ void LLPipeline::init() //create render pass pools getPool(LLDrawPool::POOL_ALPHA); getPool(LLDrawPool::POOL_SIMPLE); + getPool(LLDrawPool::POOL_ALPHA_MASK); + getPool(LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK); getPool(LLDrawPool::POOL_GRASS); getPool(LLDrawPool::POOL_FULLBRIGHT); getPool(LLDrawPool::POOL_INVISIBLE); getPool(LLDrawPool::POOL_BUMP); + getPool(LLDrawPool::POOL_MATERIALS); getPool(LLDrawPool::POOL_GLOW); LLViewerStats::getInstance()->mTrianglesDrawnStat.reset(); @@ -639,14 +657,22 @@ void LLPipeline::resizeScreenTexture() GLuint resX = gViewerWindow->getWorldViewWidthRaw(); GLuint resY = gViewerWindow->getWorldViewHeightRaw(); + if ((resX != mScreen.getWidth()) || (resY != mScreen.getHeight())) + { + releaseScreenBuffers(); if (!allocateScreenBuffer(resX,resY)) - { //FAILSAFE: screen buffer allocation failed, disable deferred rendering if it's enabled + { +#if PROBABLE_FALSE_DISABLES_OF_ALM_HERE + //FAILSAFE: screen buffer allocation failed, disable deferred rendering if it's enabled //NOTE: if the session closes successfully after this call, deferred rendering will be // disabled on future sessions if (LLPipeline::sRenderDeferred) { gSavedSettings.setBOOL("RenderDeferred", FALSE); LLPipeline::refreshCachedSettings(); + + } +#endif } } } @@ -777,15 +803,26 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) const U32 occlusion_divisor = 3; //allocate deferred rendering color buffers - if (!mDeferredScreen.allocate(resX, resY, GL_RGBA, TRUE, TRUE, LLTexUnit::TT_RECT_TEXTURE, FALSE)) return false; + if (!mDeferredScreen.allocate(resX, resY, GL_SRGB8_ALPHA8, TRUE, TRUE, LLTexUnit::TT_RECT_TEXTURE, FALSE)) return false; if (!mDeferredDepth.allocate(resX, resY, 0, TRUE, FALSE, LLTexUnit::TT_RECT_TEXTURE, FALSE)) return false; if (!mOcclusionDepth.allocate(resX/occlusion_divisor, resY/occlusion_divisor, 0, TRUE, FALSE, LLTexUnit::TT_RECT_TEXTURE, FALSE)) return false; if (!addDeferredAttachments(mDeferredScreen)) return false; + + GLuint screenFormat = GL_RGBA16; + if (gGLManager.mIsATI) + { + screenFormat = GL_RGBA12; + } - if (!mScreen.allocate(resX, resY, GL_RGBA, FALSE, FALSE, LLTexUnit::TT_RECT_TEXTURE, FALSE)) return false; + if (gGLManager.mGLVersion < 4.f && gGLManager.mIsNVIDIA) + { + screenFormat = GL_RGBA16F_ARB; + } + + if (!mScreen.allocate(resX, resY, screenFormat, FALSE, FALSE, LLTexUnit::TT_RECT_TEXTURE, FALSE)) return false; if (samples > 0) { - if (!mFXAABuffer.allocate(nhpo2(resX), nhpo2(resY), GL_RGBA, FALSE, FALSE, LLTexUnit::TT_TEXTURE, FALSE)) return false; + if (!mFXAABuffer.allocate(resX, resY, GL_RGBA, FALSE, FALSE, LLTexUnit::TT_TEXTURE, FALSE)) return false; } else { @@ -808,8 +845,8 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) U32 sun_shadow_map_width = ((U32(resX*scale)+1)&~1); // must be even to avoid a stripe in the horizontal shadow blur for (U32 i = 0; i < 4; i++) { - if (!mShadow[i].allocate(sun_shadow_map_width,U32(resY*scale), 0, TRUE, FALSE, LLTexUnit::TT_RECT_TEXTURE)) return false; - if (!mShadowOcclusion[i].allocate(mShadow[i].getWidth()/occlusion_divisor, mShadow[i].getHeight()/occlusion_divisor, 0, TRUE, FALSE, LLTexUnit::TT_RECT_TEXTURE)) return false; + if (!mShadow[i].allocate(sun_shadow_map_width,U32(resY*scale), 0, TRUE, FALSE, LLTexUnit::TT_TEXTURE)) return false; + if (!mShadowOcclusion[i].allocate(mShadow[i].getWidth()/occlusion_divisor, mShadow[i].getHeight()/occlusion_divisor, 0, TRUE, FALSE, LLTexUnit::TT_TEXTURE)) return false; } } else @@ -821,7 +858,7 @@ bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) } } - U32 width = nhpo2(U32(resX*scale))/2; + U32 width = (U32) (resX*scale); U32 height = width; if (shadow_detail > 1) @@ -902,6 +939,7 @@ void LLPipeline::updateRenderDeferred() sRenderDeferred = (gSavedSettings.getBOOL("RenderDeferred") && LLRenderTarget::sUseFBO && LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") && + gSavedSettings.getBOOL("RenderObjectBump") && gSavedSettings.getBOOL("VertexShaderEnable") && gSavedSettings.getBOOL("RenderAvatarVP") && gSavedSettings.getBOOL("WindLightUseAtmosShaders") && @@ -1003,13 +1041,30 @@ void LLPipeline::createGLBuffers() updateRenderDeferred(); + bool materials_in_water = false; + +#if MATERIALS_IN_REFLECTIONS + materials_in_water = gSavedSettings.getS32("RenderWaterMaterials"); +#endif + if (LLPipeline::sWaterReflections) { //water reflection texture U32 res = (U32) llmax(gSavedSettings.getS32("RenderWaterRefResolution"), 512); - mWaterRef.allocate(res,res,GL_RGBA,TRUE,FALSE); - //always use FBO for mWaterDis so it can be used for avatar texture bakes - mWaterDis.allocate(res,res,GL_RGBA,TRUE,FALSE,LLTexUnit::TT_TEXTURE, true); + // Set up SRGB targets if we're doing deferred-path reflection rendering + // + if (LLPipeline::sRenderDeferred && materials_in_water) + { + mWaterRef.allocate(res,res,GL_SRGB8_ALPHA8,TRUE,FALSE); + //always use FBO for mWaterDis so it can be used for avatar texture bakes + mWaterDis.allocate(res,res,GL_SRGB8_ALPHA8,TRUE,FALSE,LLTexUnit::TT_TEXTURE, true); + } + else + { + mWaterRef.allocate(res,res,GL_RGBA,TRUE,FALSE); + //always use FBO for mWaterDis so it can be used for avatar texture bakes + mWaterDis.allocate(res,res,GL_RGBA,TRUE,FALSE,LLTexUnit::TT_TEXTURE, true); + } } @@ -1118,7 +1173,6 @@ void LLPipeline::createLUTBuffers() // Always sample at a 1.0/2.2 curve. // This "Gamma corrects" our specular term, boosting our lower exponent reflections. - spec = powf(spec, 1.f/2.2f); ls[y*lightResX+x] = spec; // Easy fix for our dynamic range problem: divide by 6 here, multiply by 6 in our shaders. @@ -1354,6 +1408,14 @@ LLDrawPool *LLPipeline::findPool(const U32 type, LLViewerTexture *tex0) poolp = mGrassPool; break; + case LLDrawPool::POOL_ALPHA_MASK: + poolp = mAlphaMaskPool; + break; + + case LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK: + poolp = mFullbrightAlphaMaskPool; + break; + case LLDrawPool::POOL_FULLBRIGHT: poolp = mFullbrightPool; break; @@ -1377,7 +1439,9 @@ LLDrawPool *LLPipeline::findPool(const U32 type, LLViewerTexture *tex0) case LLDrawPool::POOL_BUMP: poolp = mBumpPool; break; - + case LLDrawPool::POOL_MATERIALS: + poolp = mMaterialsPool; + break; case LLDrawPool::POOL_ALPHA: poolp = mAlphaPool; break; @@ -1471,10 +1535,14 @@ U32 LLPipeline::getPoolTypeFromTE(const LLTextureEntry* te, LLViewerTexture* ima { return LLDrawPool::POOL_ALPHA; } - else if ((te->getBumpmap() || te->getShiny())) + else if ((te->getBumpmap() || te->getShiny()) && (!mat || mat->getNormalID().isNull())) { return LLDrawPool::POOL_BUMP; } + else if (mat && !alpha) + { + return LLDrawPool::POOL_MATERIALS; + } else { return LLDrawPool::POOL_SIMPLE; @@ -2176,7 +2244,7 @@ void LLPipeline::updateCull(LLCamera& camera, LLCullResult& result, S32 water_cl if (to_texture) { - if (LLPipeline::sRenderDeferred && !LLPipeline::sUnderWaterRender) + if (LLPipeline::sRenderDeferred) { mOcclusionDepth.bindTarget(); } @@ -2321,7 +2389,7 @@ void LLPipeline::updateCull(LLCamera& camera, LLCullResult& result, S32 water_cl if (to_texture) { - if (LLPipeline::sRenderDeferred && !LLPipeline::sUnderWaterRender) + if (LLPipeline::sRenderDeferred) { mOcclusionDepth.flush(); } @@ -4601,8 +4669,8 @@ void LLPipeline::renderDebug() LLSpatialPartition* part = region->getSpatialPartition(i); if (part) { - if ( hud_only && (part->mDrawableType == RENDER_TYPE_HUD || part->mDrawableType == RENDER_TYPE_HUD_PARTICLES) || - !hud_only && hasRenderType(part->mDrawableType) ) + if ( (hud_only && (part->mDrawableType == RENDER_TYPE_HUD || part->mDrawableType == RENDER_TYPE_HUD_PARTICLES)) || + (!hud_only && hasRenderType(part->mDrawableType)) ) { part->renderDebug(); } @@ -4917,6 +4985,32 @@ void LLPipeline::addToQuickLookup( LLDrawPool* new_poolp ) } break; + case LLDrawPool::POOL_ALPHA_MASK: + if (mAlphaMaskPool) + { + llassert(0); + llwarns << "Ignoring duplicate alpha mask pool." << llendl; + break; + } + else + { + mAlphaMaskPool = (LLRenderPass*) new_poolp; + } + break; + + case LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK: + if (mFullbrightAlphaMaskPool) + { + llassert(0); + llwarns << "Ignoring duplicate alpha mask pool." << llendl; + break; + } + else + { + mFullbrightAlphaMaskPool = (LLRenderPass*) new_poolp; + } + break; + case LLDrawPool::POOL_GRASS: if (mGrassPool) { @@ -4984,7 +5078,17 @@ void LLPipeline::addToQuickLookup( LLDrawPool* new_poolp ) mBumpPool = new_poolp; } break; - + case LLDrawPool::POOL_MATERIALS: + if (mMaterialsPool) + { + llassert(0); + llwarns << "Ignorning duplicate materials pool." << llendl; + } + else + { + mMaterialsPool = new_poolp; + } + break; case LLDrawPool::POOL_ALPHA: if( mAlphaPool ) { @@ -4993,7 +5097,7 @@ void LLPipeline::addToQuickLookup( LLDrawPool* new_poolp ) } else { - mAlphaPool = new_poolp; + mAlphaPool = (LLDrawPoolAlpha*) new_poolp; } break; @@ -5073,6 +5177,16 @@ void LLPipeline::removeFromQuickLookup( LLDrawPool* poolp ) mSimplePool = NULL; break; + case LLDrawPool::POOL_ALPHA_MASK: + llassert(mAlphaMaskPool == poolp); + mAlphaMaskPool = NULL; + break; + + case LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK: + llassert(mFullbrightAlphaMaskPool == poolp); + mFullbrightAlphaMaskPool = NULL; + break; + case LLDrawPool::POOL_GRASS: llassert(mGrassPool == poolp); mGrassPool = NULL; @@ -5124,7 +5238,12 @@ void LLPipeline::removeFromQuickLookup( LLDrawPool* poolp ) llassert( poolp == mBumpPool ); mBumpPool = NULL; break; - + + case LLDrawPool::POOL_MATERIALS: + llassert(poolp == mMaterialsPool); + mMaterialsPool = NULL; + break; + case LLDrawPool::POOL_ALPHA: llassert( poolp == mAlphaPool ); mAlphaPool = NULL; @@ -5536,7 +5655,7 @@ void LLPipeline::setupHWLights(LLDrawPool* pool) if (sRenderDeferred) { F32 size = light_radius*1.5f; - light_state->setLinearAttenuation(size*size); + light_state->setLinearAttenuation(size); light_state->setQuadraticAttenuation(light->getLightFalloff()*0.5f+1.f); } else @@ -6280,6 +6399,48 @@ BOOL LLPipeline::getRenderHighlights(void*) return sRenderHighlight; } +LLVOPartGroup* LLPipeline::lineSegmentIntersectParticle(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection, + S32* face_hit) +{ + LLVector4a local_end = end; + + LLVector4a position; + + LLDrawable* drawable = NULL; + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + + LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_PARTICLE); + if (part && hasRenderType(part->mDrawableType)) + { + LLDrawable* hit = part->lineSegmentIntersect(start, local_end, TRUE, face_hit, &position, NULL, NULL, NULL); + if (hit) + { + drawable = hit; + local_end = position; + } + } + } + + LLVOPartGroup* ret = NULL; + if (drawable) + { + //make sure we're returning an LLVOPartGroup + llassert(drawable->getVObj()->getPCode() == LLViewerObject::LL_VO_PART_GROUP); + ret = (LLVOPartGroup*) drawable->getVObj().get(); + } + + if (intersection) + { + *intersection = position; + } + + return ret; +} + LLViewerObject* LLPipeline::lineSegmentIntersectInWorld(const LLVector4a& start, const LLVector4a& end, BOOL pick_transparent, S32* face_hit, @@ -6585,6 +6746,17 @@ void LLPipeline::renderObjects(U32 type, U32 mask, BOOL texture, BOOL batch_text gGLLastMatrix = NULL; } +void LLPipeline::renderMaskedObjects(U32 type, U32 mask, BOOL texture, BOOL batch_texture) +{ + assertInitialized(); + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; + mAlphaMaskPool->pushMaskBatches(type, mask, texture, batch_texture); + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; +} + + void apply_cube_face_rotation(U32 face) { switch (face) @@ -7089,7 +7261,7 @@ void LLPipeline::renderBloom(BOOL for_snapshot, F32 zoom_factor, int subfield, b { //combine result based on alpha if (multisample) { - mDeferredScreen.bindTarget(); + mDeferredLight.bindTarget(); glViewport(0, 0, mDeferredScreen.getWidth(), mDeferredScreen.getHeight()); } else @@ -7110,6 +7282,13 @@ void LLPipeline::renderBloom(BOOL for_snapshot, F32 zoom_factor, int subfield, b mScreen.bindTexture(0, channel); gGL.getTexUnit(channel)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR); } + + if (!LLViewerCamera::getInstance()->cameraUnderWater()) + { + shader->uniform1f(LLShaderMgr::GLOBAL_GAMMA, 2.2); + } else { + shader->uniform1f(LLShaderMgr::GLOBAL_GAMMA, 1.0); + } shader->uniform1f(LLShaderMgr::DOF_MAX_COF, CameraMaxCoF); shader->uniform1f(LLShaderMgr::DOF_RES_SCALE, CameraDoFResScale); @@ -7132,7 +7311,7 @@ void LLPipeline::renderBloom(BOOL for_snapshot, F32 zoom_factor, int subfield, b if (multisample) { - mDeferredScreen.flush(); + mDeferredLight.flush(); } } } @@ -7140,7 +7319,7 @@ void LLPipeline::renderBloom(BOOL for_snapshot, F32 zoom_factor, int subfield, b { if (multisample) { - mDeferredScreen.bindTarget(); + mDeferredLight.bindTarget(); } LLGLSLShader* shader = &gDeferredPostNoDoFProgram; @@ -7151,6 +7330,13 @@ void LLPipeline::renderBloom(BOOL for_snapshot, F32 zoom_factor, int subfield, b { mScreen.bindTexture(0, channel); } + + if (!LLViewerCamera::getInstance()->cameraUnderWater()) + { + shader->uniform1f(LLShaderMgr::GLOBAL_GAMMA, 2.2); + } else { + shader->uniform1f(LLShaderMgr::GLOBAL_GAMMA, 1.0); + } gGL.begin(LLRender::TRIANGLE_STRIP); gGL.texCoord2f(tc1.mV[0], tc1.mV[1]); @@ -7168,7 +7354,7 @@ void LLPipeline::renderBloom(BOOL for_snapshot, F32 zoom_factor, int subfield, b if (multisample) { - mDeferredScreen.flush(); + mDeferredLight.flush(); } } @@ -7189,7 +7375,7 @@ void LLPipeline::renderBloom(BOOL for_snapshot, F32 zoom_factor, int subfield, b S32 channel = shader->enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, mDeferredLight.getUsage()); if (channel > -1) { - mDeferredScreen.bindTexture(0, channel); + mDeferredLight.bindTexture(0, channel); } gGL.begin(LLRender::TRIANGLE_STRIP); @@ -7479,7 +7665,7 @@ void LLPipeline::bindDeferredShader(LLGLSLShader& shader, U32 light_index, U32 n for (U32 i = 0; i < 4; i++) { - channel = shader.enableTexture(LLShaderMgr::DEFERRED_SHADOW0+i, LLTexUnit::TT_RECT_TEXTURE); + channel = shader.enableTexture(LLShaderMgr::DEFERRED_SHADOW0+i, LLTexUnit::TT_TEXTURE); stop_glerror(); if (channel > -1) { @@ -7489,8 +7675,8 @@ void LLPipeline::bindDeferredShader(LLGLSLShader& shader, U32 light_index, U32 n gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); stop_glerror(); - glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); - glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL); stop_glerror(); } } @@ -7563,13 +7749,13 @@ void LLPipeline::bindDeferredShader(LLGLSLShader& shader, U32 light_index, U32 n LLVector3 ssao_effect = RenderSSAOEffect; shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_EFFECT, ssao_effect[0]); - F32 shadow_offset_error = 1.f + RenderShadowOffsetError * fabsf(LLViewerCamera::getInstance()->getOrigin().mV[2]); - F32 shadow_bias_error = 1.f + RenderShadowBiasError * fabsf(LLViewerCamera::getInstance()->getOrigin().mV[2]); + //F32 shadow_offset_error = 1.f + RenderShadowOffsetError * fabsf(LLViewerCamera::getInstance()->getOrigin().mV[2]); + F32 shadow_bias_error = RenderShadowBiasError * fabsf(LLViewerCamera::getInstance()->getOrigin().mV[2])/3000.f; shader.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, mDeferredScreen.getWidth(), mDeferredScreen.getHeight()); shader.uniform1f(LLShaderMgr::DEFERRED_NEAR_CLIP, LLViewerCamera::getInstance()->getNear()*2.f); - shader.uniform1f (LLShaderMgr::DEFERRED_SHADOW_OFFSET, RenderShadowOffset*shadow_offset_error); - shader.uniform1f(LLShaderMgr::DEFERRED_SHADOW_BIAS, RenderShadowBias*shadow_bias_error); + shader.uniform1f (LLShaderMgr::DEFERRED_SHADOW_OFFSET, RenderShadowOffset); //*shadow_offset_error); + shader.uniform1f(LLShaderMgr::DEFERRED_SHADOW_BIAS, RenderShadowBias+shadow_bias_error); shader.uniform1f(LLShaderMgr::DEFERRED_SPOT_SHADOW_OFFSET, RenderSpotShadowOffset); shader.uniform1f(LLShaderMgr::DEFERRED_SPOT_SHADOW_BIAS, RenderSpotShadowBias); @@ -7813,7 +7999,7 @@ void LLPipeline::renderDeferredLighting() if (RenderDeferredAtmospheric) { //apply sunlight contribution LLFastTimer ftm(FTM_ATMOSPHERICS); - bindDeferredShader(gDeferredSoftenProgram); + bindDeferredShader(LLPipeline::sUnderWaterRender ? gDeferredSoftenWaterProgram : gDeferredSoftenProgram); { LLGLDepthTest depth(GL_FALSE); LLGLDisable blend(GL_BLEND); @@ -7835,7 +8021,7 @@ void LLPipeline::renderDeferredLighting() gGL.popMatrix(); } - unbindDeferredShader(gDeferredSoftenProgram); + unbindDeferredShader(LLPipeline::sUnderWaterRender ? gDeferredSoftenWaterProgram : gDeferredSoftenProgram); } { //render non-deferred geometry (fullbright, alpha, etc) @@ -7947,7 +8133,7 @@ void LLPipeline::renderDeferredLighting() LLFastTimer ftm(FTM_LOCAL_LIGHTS); gDeferredLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, c); - gDeferredLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s*s); + gDeferredLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s); gDeferredLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); gDeferredLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, volume->getLightFalloff()*0.5f); gGL.syncMatrices(); @@ -7968,7 +8154,7 @@ void LLPipeline::renderDeferredLighting() glh::vec3f tc(c); mat.mult_matrix_vec(tc); - fullscreen_lights.push_back(LLVector4(tc.v[0], tc.v[1], tc.v[2], s*s)); + fullscreen_lights.push_back(LLVector4(tc.v[0], tc.v[1], tc.v[2], s)); light_colors.push_back(LLVector4(col.mV[0], col.mV[1], col.mV[2], volume->getLightFalloff()*0.5f)); } } @@ -8003,7 +8189,7 @@ void LLPipeline::renderDeferredLighting() LLColor3 col = volume->getLightColor(); gDeferredSpotLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, c); - gDeferredSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s*s); + gDeferredSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s); gDeferredSpotLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); gDeferredSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, volume->getLightFalloff()*0.5f); gGL.syncMatrices(); @@ -8021,10 +8207,6 @@ void LLPipeline::renderDeferredLighting() vert[2].set(3,1,0); { - bindDeferredShader(gDeferredMultiLightProgram); - - mDeferredVB->setBuffer(LLVertexBuffer::MAP_VERTEX); - LLGLDepthTest depth(GL_FALSE); //full screen blit @@ -8036,7 +8218,7 @@ void LLPipeline::renderDeferredLighting() U32 count = 0; - const U32 max_count = 8; + const U32 max_count = LL_DEFERRED_MULTI_LIGHT_COUNT; LLVector4 light[max_count]; LLVector4 col[max_count]; @@ -8049,23 +8231,587 @@ void LLPipeline::renderDeferredLighting() fullscreen_lights.pop_front(); col[count] = light_colors.front(); light_colors.pop_front(); - - far_z = llmin(light[count].mV[2]-sqrtf(light[count].mV[3]), far_z); - + + /*col[count].mV[0] = powf(col[count].mV[0], 2.2f); + col[count].mV[1] = powf(col[count].mV[1], 2.2f); + col[count].mV[2] = powf(col[count].mV[2], 2.2f);*/ + + far_z = llmin(light[count].mV[2]-light[count].mV[3], far_z); + //col[count] = pow4fsrgb(col[count], 2.2f); count++; if (count == max_count || fullscreen_lights.empty()) { - gDeferredMultiLightProgram.uniform1i(LLShaderMgr::MULTI_LIGHT_COUNT, count); - gDeferredMultiLightProgram.uniform4fv(LLShaderMgr::MULTI_LIGHT, count, (GLfloat*) light); - gDeferredMultiLightProgram.uniform4fv(LLShaderMgr::MULTI_LIGHT_COL, count, (GLfloat*) col); - gDeferredMultiLightProgram.uniform1f(LLShaderMgr::MULTI_LIGHT_FAR_Z, far_z); + U32 idx = count-1; + bindDeferredShader(gDeferredMultiLightProgram[idx]); + gDeferredMultiLightProgram[idx].uniform1i(LLShaderMgr::MULTI_LIGHT_COUNT, count); + gDeferredMultiLightProgram[idx].uniform4fv(LLShaderMgr::MULTI_LIGHT, count, (GLfloat*) light); + gDeferredMultiLightProgram[idx].uniform4fv(LLShaderMgr::MULTI_LIGHT_COL, count, (GLfloat*) col); + gDeferredMultiLightProgram[idx].uniform1f(LLShaderMgr::MULTI_LIGHT_FAR_Z, far_z); far_z = 0.f; count = 0; + mDeferredVB->setBuffer(LLVertexBuffer::MAP_VERTEX); + mDeferredVB->drawArrays(LLRender::TRIANGLES, 0, 3); + unbindDeferredShader(gDeferredMultiLightProgram[idx]); + } + } + + bindDeferredShader(gDeferredMultiSpotLightProgram); + + gDeferredMultiSpotLightProgram.enableTexture(LLShaderMgr::DEFERRED_PROJECTION); + + mDeferredVB->setBuffer(LLVertexBuffer::MAP_VERTEX); + + for (LLDrawable::drawable_list_t::iterator iter = fullscreen_spot_lights.begin(); iter != fullscreen_spot_lights.end(); ++iter) + { + LLFastTimer ftm(FTM_PROJECTORS); + LLDrawable* drawablep = *iter; + + LLVOVolume* volume = drawablep->getVOVolume(); + + LLVector3 center = drawablep->getPositionAgent(); + F32* c = center.mV; + F32 s = volume->getLightRadius()*1.5f; + + sVisibleLightCount++; + + glh::vec3f tc(c); + mat.mult_matrix_vec(tc); + + setupSpotLight(gDeferredMultiSpotLightProgram, drawablep); + + LLColor3 col = volume->getLightColor(); + + /*col.mV[0] = powf(col.mV[0], 2.2f); + col.mV[1] = powf(col.mV[1], 2.2f); + col.mV[2] = powf(col.mV[2], 2.2f);*/ + + gDeferredMultiSpotLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, tc.v); + gDeferredMultiSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s); + gDeferredMultiSpotLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); + gDeferredMultiSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, volume->getLightFalloff()*0.5f); + mDeferredVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + + gDeferredMultiSpotLightProgram.disableTexture(LLShaderMgr::DEFERRED_PROJECTION); + unbindDeferredShader(gDeferredMultiSpotLightProgram); + + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + } + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } + + gGL.setColorMask(true, true); + } + + mScreen.flush(); + + //gamma correct lighting + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.loadIdentity(); + + { + LLGLDepthTest depth(GL_FALSE, GL_FALSE); + + LLVector2 tc1(0,0); + LLVector2 tc2((F32) mScreen.getWidth()*2, + (F32) mScreen.getHeight()*2); + + mScreen.bindTarget(); + // Apply gamma correction to the frame here. + gDeferredPostGammaCorrectProgram.bind(); + //mDeferredVB->setBuffer(LLVertexBuffer::MAP_VERTEX); + S32 channel = 0; + channel = gDeferredPostGammaCorrectProgram.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, mScreen.getUsage()); + if (channel > -1) + { + mScreen.bindTexture(0,channel); + gGL.getTexUnit(channel)->setTextureFilteringOption(LLTexUnit::TFO_POINT); + } + + gDeferredPostGammaCorrectProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, mScreen.getWidth(), mScreen.getHeight()); + + //F32 gamma = gSavedSettings.getF32("RenderDeferredDisplayGamma"); + + //gDeferredPostGammaCorrectProgram.uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f/2.2f)); + + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.texCoord2f(tc1.mV[0], tc1.mV[1]); + gGL.vertex2f(-1,-1); + + gGL.texCoord2f(tc1.mV[0], tc2.mV[1]); + gGL.vertex2f(-1,3); + + gGL.texCoord2f(tc2.mV[0], tc1.mV[1]); + gGL.vertex2f(3,-1); + + gGL.end(); + + gGL.getTexUnit(channel)->unbind(mScreen.getUsage()); + gDeferredPostGammaCorrectProgram.unbind(); + mScreen.flush(); + } + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + + mScreen.bindTarget(); + + { //render non-deferred geometry (alpha, fullbright, glow) + LLGLDisable blend(GL_BLEND); + LLGLDisable stencil(GL_STENCIL_TEST); + + pushRenderTypeMask(); + andRenderTypeMask(LLPipeline::RENDER_TYPE_ALPHA, + LLPipeline::RENDER_TYPE_FULLBRIGHT, + //LLPipeline::RENDER_TYPE_VOLUME, + LLPipeline::RENDER_TYPE_GLOW, + LLPipeline::RENDER_TYPE_BUMP, + /*LLPipeline::RENDER_TYPE_PASS_SIMPLE, //These aren't used. + LLPipeline::RENDER_TYPE_PASS_ALPHA, + LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK, + LLPipeline::RENDER_TYPE_PASS_BUMP, + LLPipeline::RENDER_TYPE_PASS_POST_BUMP, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY, + LLPipeline::RENDER_TYPE_PASS_GLOW, + LLPipeline::RENDER_TYPE_PASS_GRASS, + LLPipeline::RENDER_TYPE_PASS_SHINY, + LLPipeline::RENDER_TYPE_PASS_INVISIBLE, + LLPipeline::RENDER_TYPE_PASS_INVISI_SHINY,*/ + LLPipeline::RENDER_TYPE_AVATAR, + LLPipeline::RENDER_TYPE_ALPHA_MASK, + LLPipeline::RENDER_TYPE_FULLBRIGHT_ALPHA_MASK, + END_RENDER_TYPES); + + renderGeomPostDeferred(*LLViewerCamera::getInstance()); + popRenderTypeMask(); + } + + { + //render highlights, etc. + renderHighlights(); + mHighlightFaces.clear(); + + renderDebug(); + + LLVertexBuffer::unbind(); + + if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + // Render debugging beacons. + gObjectList.renderObjectBeacons(); + gObjectList.resetObjectBeacons(); + } + } + + mScreen.flush(); + +} + +void LLPipeline::renderDeferredLightingToRT(LLRenderTarget* target) +{ + if (!sCull) + { + return; + } + + static const LLCachedControl RenderFSAASamples("RenderFSAASamples",0); + static const LLCachedControl RenderDeferredSSAO("RenderDeferredSSAO",false); + static const LLCachedControl RenderShadowDetail("RenderShadowDetail",0); + static const LLCachedControl RenderShadowGaussian("RenderShadowGaussian",LLVector3(3.f,2.f,0.f)); + static const LLCachedControl RenderShadowBlurSize("RenderShadowBlurSize",1.4f); + static const LLCachedControl RenderShadowBlurDistFactor("RenderShadowBlurDistFactor",.1f); + static const LLCachedControl RenderDeferredAtmospheric("RenderDeferredAtmospheric",false); + static const LLCachedControl RenderLocalLights("RenderLocalLights",false); + + { + LLFastTimer ftm(FTM_RENDER_DEFERRED); + + LLViewerCamera* camera = LLViewerCamera::getInstance(); + { + LLGLDepthTest depth(GL_TRUE); + mDeferredDepth.copyContents(mDeferredScreen, 0, 0, mDeferredScreen.getWidth(), mDeferredScreen.getHeight(), + 0, 0, mDeferredDepth.getWidth(), mDeferredDepth.getHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST); + } + + LLGLEnable multisample(RenderFSAASamples > 0 ? GL_MULTISAMPLE_ARB : 0); + + if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD)) + { + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD); + } + + //ati doesn't seem to love actually using the stencil buffer on FBO's + LLGLDisable stencil(GL_STENCIL_TEST); + //glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF); + //glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + + gGL.setColorMask(true, true); + + //draw a cube around every light + LLVertexBuffer::unbind(); + + LLGLEnable cull(GL_CULL_FACE); + LLGLEnable blend(GL_BLEND); + + glh::matrix4f mat = glh_copy_matrix(gGLModelView); + + LLStrider vert; + mDeferredVB->getVertexStrider(vert); + + vert[0].set(-1,1,0); + vert[1].set(-1,-3,0); + vert[2].set(3,1,0); + + { + setupHWLights(NULL); //to set mSunDir; + LLVector4 dir(mSunDir, 0.f); + glh::vec4f tc(dir.mV); + mat.mult_matrix_vec(tc); + mTransformedSunDir.set(tc.v); + } + + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + + if (RenderDeferredSSAO || RenderShadowDetail > 0) + { + mDeferredLight.bindTarget(); + { //paint shadow/SSAO light map (direct lighting lightmap) + LLFastTimer ftm(FTM_SUN_SHADOW); + bindDeferredShader(gDeferredSunProgram); + mDeferredVB->setBuffer(LLVertexBuffer::MAP_VERTEX); + glClearColor(1,1,1,1); + mDeferredLight.clear(GL_COLOR_BUFFER_BIT); + glClearColor(0,0,0,0); + + glh::matrix4f inv_trans = glh_get_current_modelview().inverse().transpose(); + + const U32 slice = 32; + F32 offset[slice*3]; + for (U32 i = 0; i < 4; i++) + { + for (U32 j = 0; j < 8; j++) + { + glh::vec3f v; + v.set_value(sinf(6.284f/8*j), cosf(6.284f/8*j), -(F32) i); + v.normalize(); + inv_trans.mult_matrix_vec(v); + v.normalize(); + offset[(i*8+j)*3+0] = v.v[0]; + offset[(i*8+j)*3+1] = v.v[2]; + offset[(i*8+j)*3+2] = v.v[1]; + } + } + + gDeferredSunProgram.uniform3fv(LLShaderMgr::DEFERRED_SHADOW_OFFSET, slice, offset); + gDeferredSunProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, mDeferredLight.getWidth(), mDeferredLight.getHeight()); + + { + LLGLDisable blend(GL_BLEND); + LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_ALWAYS); + stop_glerror(); + mDeferredVB->drawArrays(LLRender::TRIANGLES, 0, 3); + stop_glerror(); + } + + unbindDeferredShader(gDeferredSunProgram); + } + mDeferredLight.flush(); + } + + stop_glerror(); + gGL.popMatrix(); + stop_glerror(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + stop_glerror(); + gGL.popMatrix(); + stop_glerror(); + + target->bindTarget(); + + //clear color buffer here - zeroing alpha (glow) is important or it will accumulate against sky + glClearColor(0,0,0,0); + target->clear(GL_COLOR_BUFFER_BIT); + + if (RenderDeferredAtmospheric) + { //apply sunlight contribution + LLFastTimer ftm(FTM_ATMOSPHERICS); + bindDeferredShader(gDeferredSoftenProgram); + { + LLGLDepthTest depth(GL_FALSE); + LLGLDisable blend(GL_BLEND); + LLGLDisable test(GL_ALPHA_TEST); + + //full screen blit + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + + mDeferredVB->setBuffer(LLVertexBuffer::MAP_VERTEX); + + mDeferredVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + } + + unbindDeferredShader(gDeferredSoftenProgram); + } + + { //render non-deferred geometry (fullbright, alpha, etc) + LLGLDisable blend(GL_BLEND); + LLGLDisable stencil(GL_STENCIL_TEST); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + + gPipeline.pushRenderTypeMask(); + + gPipeline.andRenderTypeMask(LLPipeline::RENDER_TYPE_SKY, + LLPipeline::RENDER_TYPE_CLASSIC_CLOUDS, + LLPipeline::RENDER_TYPE_WL_SKY, + LLPipeline::END_RENDER_TYPES); + + + renderGeomPostDeferred(*LLViewerCamera::getInstance(), false); + gPipeline.popRenderTypeMask(); + } + + BOOL render_local = RenderLocalLights; + + if (render_local) + { + gGL.setSceneBlendType(LLRender::BT_ADD); + std::list fullscreen_lights; + LLDrawable::drawable_list_t spot_lights; + LLDrawable::drawable_list_t fullscreen_spot_lights; + + for (U32 i = 0; i < 2; i++) + { + mTargetShadowSpotLight[i] = NULL; + } + + std::list light_colors; + + LLVertexBuffer::unbind(); + + { + bindDeferredShader(gDeferredLightProgram); + + if (mCubeVB.isNull()) + { + mCubeVB = ll_create_cube_vb(LLVertexBuffer::MAP_VERTEX, GL_STATIC_DRAW_ARB); + } + + mCubeVB->setBuffer(LLVertexBuffer::MAP_VERTEX); + + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + for (LLDrawable::drawable_set_t::iterator iter = mLights.begin(); iter != mLights.end(); ++iter) + { + LLDrawable* drawablep = *iter; + + LLVOVolume* volume = drawablep->getVOVolume(); + if (!volume) + { + continue; + } + + if (volume->isAttachment()) + { + if (!sRenderAttachedLights) + { + continue; + } + } + + + LLVector4a center; + center.load3(drawablep->getPositionAgent().mV); + const F32* c = center.getF32ptr(); + F32 s = volume->getLightRadius()*1.5f; + + LLColor3 col = volume->getLightColor(); + + if (col.magVecSquared() < 0.001f) + { + continue; + } + + if (s <= 0.001f) + { + continue; + } + + LLVector4a sa; + sa.splat(s); + if (camera->AABBInFrustumNoFarClip(center, sa) == 0) + { + continue; + } + + sVisibleLightCount++; + + if (camera->getOrigin().mV[0] > c[0] + s + 0.2f || + camera->getOrigin().mV[0] < c[0] - s - 0.2f || + camera->getOrigin().mV[1] > c[1] + s + 0.2f || + camera->getOrigin().mV[1] < c[1] - s - 0.2f || + camera->getOrigin().mV[2] > c[2] + s + 0.2f || + camera->getOrigin().mV[2] < c[2] - s - 0.2f) + { //draw box if camera is outside box + if (render_local) + { + if (volume->isLightSpotlight()) + { + drawablep->getVOVolume()->updateSpotLightPriority(); + spot_lights.push_back(drawablep); + continue; + } + + /*col.mV[0] = powf(col.mV[0], 2.2f); + col.mV[1] = powf(col.mV[1], 2.2f); + col.mV[2] = powf(col.mV[2], 2.2f);*/ + + LLFastTimer ftm(FTM_LOCAL_LIGHTS); + gDeferredLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, c); + gDeferredLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s); + gDeferredLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); + gDeferredLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, volume->getLightFalloff()*0.5f); + gGL.syncMatrices(); + + mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, center)); + stop_glerror(); + } + } + else + { + if (volume->isLightSpotlight()) + { + drawablep->getVOVolume()->updateSpotLightPriority(); + fullscreen_spot_lights.push_back(drawablep); + continue; + } + + glh::vec3f tc(c); + mat.mult_matrix_vec(tc); + + fullscreen_lights.push_back(LLVector4(tc.v[0], tc.v[1], tc.v[2], s)); + light_colors.push_back(LLVector4(col.mV[0], col.mV[1], col.mV[2], volume->getLightFalloff()*0.5f)); + } + } + unbindDeferredShader(gDeferredLightProgram); + } + + if (!spot_lights.empty()) + { + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + bindDeferredShader(gDeferredSpotLightProgram); + + mCubeVB->setBuffer(LLVertexBuffer::MAP_VERTEX); + + gDeferredSpotLightProgram.enableTexture(LLShaderMgr::DEFERRED_PROJECTION); + + for (LLDrawable::drawable_list_t::iterator iter = spot_lights.begin(); iter != spot_lights.end(); ++iter) + { + LLFastTimer ftm(FTM_PROJECTORS); + LLDrawable* drawablep = *iter; + + LLVOVolume* volume = drawablep->getVOVolume(); + + LLVector4a center; + center.load3(drawablep->getPositionAgent().mV); + const F32* c = center.getF32ptr(); + F32 s = volume->getLightRadius()*1.5f; + + sVisibleLightCount++; + + setupSpotLight(gDeferredSpotLightProgram, drawablep); + + LLColor3 col = volume->getLightColor(); + /*col.mV[0] = powf(col.mV[0], 2.2f); + col.mV[1] = powf(col.mV[1], 2.2f); + col.mV[2] = powf(col.mV[2], 2.2f);*/ + + gDeferredSpotLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, c); + gDeferredSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s); + gDeferredSpotLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); + gDeferredSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, volume->getLightFalloff()*0.5f); + gGL.syncMatrices(); + + mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, center)); + } + gDeferredSpotLightProgram.disableTexture(LLShaderMgr::DEFERRED_PROJECTION); + unbindDeferredShader(gDeferredSpotLightProgram); + } + + //reset mDeferredVB to fullscreen triangle + mDeferredVB->getVertexStrider(vert); + vert[0].set(-1,1,0); + vert[1].set(-1,-3,0); + vert[2].set(3,1,0); + + { + LLGLDepthTest depth(GL_FALSE); + + //full screen blit + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + + U32 count = 0; + + const U32 max_count = LL_DEFERRED_MULTI_LIGHT_COUNT; + LLVector4 light[max_count]; + LLVector4 col[max_count]; + + F32 far_z = 0.f; + + while (!fullscreen_lights.empty()) + { + LLFastTimer ftm(FTM_FULLSCREEN_LIGHTS); + light[count] = fullscreen_lights.front(); + fullscreen_lights.pop_front(); + col[count] = light_colors.front(); + light_colors.pop_front(); + + /*col[count].mV[0] = powf(col[count].mV[0], 2.2f); + col[count].mV[1] = powf(col[count].mV[1], 2.2f); + col[count].mV[2] = powf(col[count].mV[2], 2.2f);*/ + + far_z = llmin(light[count].mV[2]-light[count].mV[3], far_z); + //col[count] = pow4fsrgb(col[count], 2.2f); + count++; + if (count == max_count || fullscreen_lights.empty()) + { + U32 idx = count-1; + bindDeferredShader(gDeferredMultiLightProgram[idx]); + gDeferredMultiLightProgram[idx].uniform1i(LLShaderMgr::MULTI_LIGHT_COUNT, count); + gDeferredMultiLightProgram[idx].uniform4fv(LLShaderMgr::MULTI_LIGHT, count, (GLfloat*) light); + gDeferredMultiLightProgram[idx].uniform4fv(LLShaderMgr::MULTI_LIGHT_COL, count, (GLfloat*) col); + gDeferredMultiLightProgram[idx].uniform1f(LLShaderMgr::MULTI_LIGHT_FAR_Z, far_z); + far_z = 0.f; + count = 0; + mDeferredVB->setBuffer(LLVertexBuffer::MAP_VERTEX); mDeferredVB->drawArrays(LLRender::TRIANGLES, 0, 3); } } - unbindDeferredShader(gDeferredMultiLightProgram); + unbindDeferredShader(gDeferredMultiLightProgram[0]); bindDeferredShader(gDeferredMultiSpotLightProgram); @@ -8092,9 +8838,13 @@ void LLPipeline::renderDeferredLighting() setupSpotLight(gDeferredMultiSpotLightProgram, drawablep); LLColor3 col = volume->getLightColor(); - + + /*col.mV[0] = powf(col.mV[0], 2.2f); + col.mV[1] = powf(col.mV[1], 2.2f); + col.mV[2] = powf(col.mV[2], 2.2f);*/ + gDeferredMultiSpotLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, tc.v); - gDeferredMultiSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s*s); + gDeferredMultiSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s); gDeferredMultiSpotLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); gDeferredMultiSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, volume->getLightFalloff()*0.5f); mDeferredVB->drawArrays(LLRender::TRIANGLES, 0, 3); @@ -8107,12 +8857,70 @@ void LLPipeline::renderDeferredLighting() gGL.matrixMode(LLRender::MM_MODELVIEW); gGL.popMatrix(); } - gGL.setSceneBlendType(LLRender::BT_ALPHA); } gGL.setColorMask(true, true); } + /*mScreen.flush(); + + //gamma correct lighting + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.loadIdentity(); + + { + LLGLDepthTest depth(GL_FALSE, GL_FALSE); + + LLVector2 tc1(0,0); + LLVector2 tc2((F32) mScreen.getWidth()*2, + (F32) mScreen.getHeight()*2); + + mScreen.bindTarget(); + // Apply gamma correction to the frame here. + gDeferredPostGammaCorrectProgram.bind(); + //mDeferredVB->setBuffer(LLVertexBuffer::MAP_VERTEX); + S32 channel = 0; + channel = gDeferredPostGammaCorrectProgram.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, mScreen.getUsage()); + if (channel > -1) + { + mScreen.bindTexture(0,channel); + gGL.getTexUnit(channel)->setTextureFilteringOption(LLTexUnit::TFO_POINT); + } + + gDeferredPostGammaCorrectProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, mScreen.getWidth(), mScreen.getHeight()); + + F32 gamma = gSavedSettings.getF32("RenderDeferredDisplayGamma"); + + gDeferredPostGammaCorrectProgram.uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f/2.2f)); + + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.texCoord2f(tc1.mV[0], tc1.mV[1]); + gGL.vertex2f(-1,-1); + + gGL.texCoord2f(tc1.mV[0], tc2.mV[1]); + gGL.vertex2f(-1,3); + + gGL.texCoord2f(tc2.mV[0], tc1.mV[1]); + gGL.vertex2f(3,-1); + + gGL.end(); + + gGL.getTexUnit(channel)->unbind(mScreen.getUsage()); + gDeferredPostGammaCorrectProgram.unbind(); + mScreen.flush(); + } + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + + mScreen.bindTarget();*/ + { //render non-deferred geometry (alpha, fullbright, glow) LLGLDisable blend(GL_BLEND); LLGLDisable stencil(GL_STENCIL_TEST); @@ -8120,10 +8928,10 @@ void LLPipeline::renderDeferredLighting() pushRenderTypeMask(); andRenderTypeMask(LLPipeline::RENDER_TYPE_ALPHA, LLPipeline::RENDER_TYPE_FULLBRIGHT, - //LLPipeline::RENDER_TYPE_VOLUME, + LLPipeline::RENDER_TYPE_VOLUME, LLPipeline::RENDER_TYPE_GLOW, LLPipeline::RENDER_TYPE_BUMP, - /*LLPipeline::RENDER_TYPE_PASS_SIMPLE, //These aren't used. + LLPipeline::RENDER_TYPE_PASS_SIMPLE, LLPipeline::RENDER_TYPE_PASS_ALPHA, LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK, LLPipeline::RENDER_TYPE_PASS_BUMP, @@ -8135,33 +8943,17 @@ void LLPipeline::renderDeferredLighting() LLPipeline::RENDER_TYPE_PASS_GRASS, LLPipeline::RENDER_TYPE_PASS_SHINY, LLPipeline::RENDER_TYPE_PASS_INVISIBLE, - LLPipeline::RENDER_TYPE_PASS_INVISI_SHINY,*/ + LLPipeline::RENDER_TYPE_PASS_INVISI_SHINY, LLPipeline::RENDER_TYPE_AVATAR, + LLPipeline::RENDER_TYPE_ALPHA_MASK, + LLPipeline::RENDER_TYPE_FULLBRIGHT_ALPHA_MASK, END_RENDER_TYPES); renderGeomPostDeferred(*LLViewerCamera::getInstance()); popRenderTypeMask(); } - { - //render highlights, etc. - renderHighlights(); - mHighlightFaces.clear(); - - renderDebug(); - - LLVertexBuffer::unbind(); - - if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) - { - // Render debugging beacons. - gObjectList.renderObjectBeacons(); - gObjectList.resetObjectBeacons(); - } - } - - mScreen.flush(); - + } void LLPipeline::setupSpotLight(LLGLSLShader& shader, LLDrawable* drawablep) @@ -8316,9 +9108,9 @@ void LLPipeline::unbindDeferredShader(LLGLSLShader &shader) for (U32 i = 0; i < 4; i++) { - if (shader.disableTexture(LLShaderMgr::DEFERRED_SHADOW0+i, LLTexUnit::TT_RECT_TEXTURE) > -1) + if (shader.disableTexture(LLShaderMgr::DEFERRED_SHADOW0+i) > -1) { - glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE); } } @@ -8410,6 +9202,12 @@ void LLPipeline::generateWaterReflection(LLCamera& camera_in) water_clip = 1; } + bool materials_in_water = false; + +#if MATERIALS_IN_REFLECTIONS + materials_in_water = gSavedSettings.getS32("RenderWaterMaterials"); +#endif + if (!LLViewerCamera::getInstance()->cameraUnderWater()) { //generate planar reflection map @@ -8467,11 +9265,28 @@ void LLPipeline::generateWaterReflection(LLCamera& camera_in) updateCull(camera, result); stateSort(camera, result); + if (LLPipeline::sRenderDeferred && materials_in_water) + { + mWaterRef.flush(); + + gPipeline.grabReferences(result); + gPipeline.mDeferredScreen.bindTarget(); + gGL.setColorMask(true, true); + glClearColor(0,0,0,0); + gPipeline.mDeferredScreen.clear(); + + renderGeomDeferred(camera); + } + else + { renderGeom(camera, TRUE); + } gPipeline.popRenderTypeMask(); } + gGL.setColorMask(true, false); + static const LLCachedControl detail("RenderReflectionDetail",0); if (detail > 0) { //mask out selected geometry based on reflection detail @@ -8505,11 +9320,31 @@ void LLPipeline::generateWaterReflection(LLCamera& camera_in) LLGLDisable cull(GL_CULL_FACE); updateCull(camera, ref_result, -water_clip, &plane); stateSort(camera, ref_result); + if (LLDrawPoolWater::sNeedsDistortionUpdate) { - gPipeline.grabReferences(ref_result); - renderGeom(camera); + if (detail > 0) + { + gPipeline.grabReferences(ref_result); + LLGLUserClipPlane clip_plane(plane, mat, projection); + + if (LLPipeline::sRenderDeferred && materials_in_water) + { + renderGeomDeferred(camera); + } + else + { + renderGeom(camera); + } + } } + + if (LLPipeline::sRenderDeferred && materials_in_water) + { + gPipeline.mDeferredScreen.flush(); + renderDeferredLightingToRT(&mWaterRef); + } + LLPipeline::sSkipUpdate = FALSE; gPipeline.popRenderTypeMask(); } @@ -8568,13 +9403,36 @@ void LLPipeline::generateWaterReflection(LLCamera& camera_in) gGL.setColorMask(true, true); mWaterDis.clear(); + + gGL.setColorMask(true, false); + + if (LLPipeline::sRenderDeferred && materials_in_water) + { + mWaterDis.flush(); + gPipeline.mDeferredScreen.bindTarget(); + gGL.setColorMask(true, true); + glClearColor(0,0,0,0); + gPipeline.mDeferredScreen.clear(); + gPipeline.grabReferences(result); + renderGeomDeferred(camera); + } + else + { renderGeom(camera); + } + + if (LLPipeline::sRenderDeferred && materials_in_water) + { + gPipeline.mDeferredScreen.flush(); + renderDeferredLightingToRT(&mWaterDis); + } } - LLPipeline::sUnderWaterRender = FALSE; mWaterDis.flush(); + LLPipeline::sUnderWaterRender = FALSE; + } last_update = LLDrawPoolWater::sNeedsReflectionUpdate && LLDrawPoolWater::sNeedsDistortionUpdate; @@ -8690,7 +9548,15 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera LLRenderPass::PASS_FULLBRIGHT, LLRenderPass::PASS_SHINY, LLRenderPass::PASS_BUMP, - LLRenderPass::PASS_FULLBRIGHT_SHINY + LLRenderPass::PASS_FULLBRIGHT_SHINY , + LLRenderPass::PASS_MATERIAL, + LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, + LLRenderPass::PASS_SPECMAP, + LLRenderPass::PASS_SPECMAP_EMISSIVE, + LLRenderPass::PASS_NORMMAP, + LLRenderPass::PASS_NORMMAP_EMISSIVE, + LLRenderPass::PASS_NORMSPEC, + LLRenderPass::PASS_NORMSPEC_EMISSIVE, }; LLGLEnable cull(GL_CULL_FACE); @@ -8766,7 +9632,6 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera { LLFastTimer ftm(FTM_SHADOW_ALPHA); gDeferredShadowAlphaMaskProgram.bind(); - gDeferredShadowAlphaMaskProgram.setMinimumAlpha(0.598f); gDeferredShadowAlphaMaskProgram.uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); U32 mask = LLVertexBuffer::MAP_VERTEX | @@ -8774,10 +9639,19 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TEXTURE_INDEX; - renderObjects(LLRenderPass::PASS_ALPHA_MASK, mask, TRUE, TRUE); - renderObjects(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, mask, TRUE, TRUE); + renderMaskedObjects(LLRenderPass::PASS_ALPHA_MASK, mask, TRUE, TRUE); + renderMaskedObjects(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, mask, TRUE, TRUE); + gDeferredShadowAlphaMaskProgram.setMinimumAlpha(0.598f); renderObjects(LLRenderPass::PASS_ALPHA, mask, TRUE, TRUE); + + mask = mask & ~LLVertexBuffer::MAP_TEXTURE_INDEX; + gDeferredTreeShadowProgram.bind(); + renderMaskedObjects(LLRenderPass::PASS_NORMSPEC_MASK, mask); + renderMaskedObjects(LLRenderPass::PASS_MATERIAL_ALPHA_MASK, mask); + renderMaskedObjects(LLRenderPass::PASS_SPECMAP_MASK, mask); + renderMaskedObjects(LLRenderPass::PASS_NORMMAP_MASK, mask); + gDeferredTreeShadowProgram.setMinimumAlpha(0.598f); renderObjects(LLRenderPass::PASS_GRASS, LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0, TRUE); } @@ -9044,6 +9918,22 @@ void LLPipeline::generateSunShadow(LLCamera& camera) LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT, LLPipeline::RENDER_TYPE_PASS_SHINY, LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY, + LLPipeline::RENDER_TYPE_PASS_MATERIAL, + LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA, + LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_MASK, + LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_EMISSIVE, + LLPipeline::RENDER_TYPE_PASS_SPECMAP, + LLPipeline::RENDER_TYPE_PASS_SPECMAP_BLEND, + LLPipeline::RENDER_TYPE_PASS_SPECMAP_MASK, + LLPipeline::RENDER_TYPE_PASS_SPECMAP_EMISSIVE, + LLPipeline::RENDER_TYPE_PASS_NORMMAP, + LLPipeline::RENDER_TYPE_PASS_NORMMAP_BLEND, + LLPipeline::RENDER_TYPE_PASS_NORMMAP_MASK, + LLPipeline::RENDER_TYPE_PASS_NORMMAP_EMISSIVE, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC_BLEND, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC_MASK, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC_EMISSIVE, END_RENDER_TYPES); gGL.setColorMask(false, false); @@ -9787,33 +10677,39 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar) assertInitialized(); - bool muted = avatar->isVisuallyMuted(); + bool visually_muted = avatar->isVisuallyMuted(); pushRenderTypeMask(); - if (muted) + if (visually_muted) { andRenderTypeMask(LLPipeline::RENDER_TYPE_AVATAR, END_RENDER_TYPES); } else { - andRenderTypeMask(LLPipeline::RENDER_TYPE_VOLUME, - LLPipeline::RENDER_TYPE_AVATAR, + andRenderTypeMask(LLPipeline::RENDER_TYPE_ALPHA, + LLPipeline::RENDER_TYPE_FULLBRIGHT, + LLPipeline::RENDER_TYPE_VOLUME, + LLPipeline::RENDER_TYPE_GLOW, LLPipeline::RENDER_TYPE_BUMP, - LLPipeline::RENDER_TYPE_GRASS, - LLPipeline::RENDER_TYPE_SIMPLE, - LLPipeline::RENDER_TYPE_FULLBRIGHT, - LLPipeline::RENDER_TYPE_ALPHA, - LLPipeline::RENDER_TYPE_INVISIBLE, LLPipeline::RENDER_TYPE_PASS_SIMPLE, LLPipeline::RENDER_TYPE_PASS_ALPHA, LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK, + LLPipeline::RENDER_TYPE_PASS_BUMP, + LLPipeline::RENDER_TYPE_PASS_POST_BUMP, LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT, LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK, LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY, + LLPipeline::RENDER_TYPE_PASS_GLOW, + LLPipeline::RENDER_TYPE_PASS_GRASS, LLPipeline::RENDER_TYPE_PASS_SHINY, LLPipeline::RENDER_TYPE_PASS_INVISIBLE, LLPipeline::RENDER_TYPE_PASS_INVISI_SHINY, + LLPipeline::RENDER_TYPE_AVATAR, + LLPipeline::RENDER_TYPE_ALPHA_MASK, + LLPipeline::RENDER_TYPE_FULLBRIGHT_ALPHA_MASK, + LLPipeline::RENDER_TYPE_INVISIBLE, + LLPipeline::RENDER_TYPE_SIMPLE, END_RENDER_TYPES); } @@ -9919,19 +10815,23 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar) if (!avatar->mImpostor.isComplete()) { LLFastTimer t(FTM_IMPOSTOR_ALLOCATE); - avatar->mImpostor.allocate(resX,resY,GL_RGBA,TRUE,FALSE); + if (LLPipeline::sRenderDeferred) { + avatar->mImpostor.allocate(resX,resY,GL_SRGB8_ALPHA8,TRUE,FALSE); addDeferredAttachments(avatar->mImpostor); } + else + { + avatar->mImpostor.allocate(resX,resY,GL_RGBA,TRUE,FALSE); + } gGL.getTexUnit(0)->bind(&avatar->mImpostor); gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); } - else if(resX != avatar->mImpostor.getWidth() || - resY != avatar->mImpostor.getHeight()) + else if(resX != avatar->mImpostor.getWidth() || resY != avatar->mImpostor.getHeight()) { LLFastTimer t(FTM_IMPOSTOR_RESIZE); avatar->mImpostor.resize(resX,resY); @@ -9940,11 +10840,34 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar) avatar->mImpostor.bindTarget(); } + F32 old_alpha = LLDrawPoolAvatar::sMinimumAlpha; + + if (visually_muted) + { //disable alpha masking for muted avatars (get whole skin silhouette) + LLDrawPoolAvatar::sMinimumAlpha = 0.f; + } + if (LLPipeline::sRenderDeferred) { avatar->mImpostor.clear(); renderGeomDeferred(camera); + + renderGeomPostDeferred(camera); + + // Shameless hack time: render it all again, + // this time writing the depth + // values we need to generate the alpha mask below + // while preserving the alpha-sorted color rendering + // from the previous pass + // + sImpostorRenderAlphaDepthPass = true; + // depth-only here... + // + gGL.setColorMask(false,false); renderGeomPostDeferred(camera); + + sImpostorRenderAlphaDepthPass = false; + } else { @@ -9952,8 +10875,25 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar) glScissor(0, 0, resX, resY); avatar->mImpostor.clear(); renderGeom(camera); + + // Shameless hack time: render it all again, + // this time writing the depth + // values we need to generate the alpha mask below + // while preserving the alpha-sorted color rendering + // from the previous pass + // + sImpostorRenderAlphaDepthPass = true; + + // depth-only here... + // + gGL.setColorMask(false,false); + renderGeom(camera); + + sImpostorRenderAlphaDepthPass = false; } - + + LLDrawPoolAvatar::sMinimumAlpha = old_alpha; + { //create alpha mask based on depth buffer (grey out if muted) LLFastTimer t(FTM_IMPOSTOR_BACKGROUND); if (LLPipeline::sRenderDeferred) @@ -9964,7 +10904,7 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar) LLGLDisable blend(GL_BLEND); - if (muted) + if (visually_muted) { gGL.setColorMask(true, true); } @@ -9989,10 +10929,10 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar) if (LLGLSLShader::sNoFixedFunction) { - gUIProgram.bind(); + gDebugProgram.bind(); } - gGL.color4ub(64,64,64,255); + gGL.diffuseColor4ub(64,64,64,255); gGL.begin(LLRender::QUADS); gGL.vertex3f(-1, -1, clip_plane); gGL.vertex3f(1, -1, clip_plane); @@ -10003,7 +10943,7 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar) if (LLGLSLShader::sNoFixedFunction) { - gUIProgram.unbind(); + gDebugProgram.unbind(); } gGL.popMatrix(); diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index b9204d878..75f03f52c 100644 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -41,7 +41,8 @@ #include "llspatialpartition.h" #include "m4math.h" #include "llpointer.h" -#include "lldrawpool.h" +#include "lldrawpoolalpha.h" +#include "lldrawpoolmaterials.h" #include "llgl.h" #include "lldrawable.h" #include "llrendertarget.h" @@ -64,7 +65,11 @@ class LLRenderFunc; class LLCubeMap; class LLCullResult; class LLVOAvatar; +class LLVOPartGroup; class LLGLSLShader; +class LLDrawPoolAlpha; + +class LLMeshResponder; typedef enum e_avatar_skinning_method { @@ -99,6 +104,7 @@ extern LLFastTimer::DeclareTimer FTM_RENDER_WL_SKY; extern LLFastTimer::DeclareTimer FTM_RENDER_ALPHA; extern LLFastTimer::DeclareTimer FTM_RENDER_CHARACTERS; extern LLFastTimer::DeclareTimer FTM_RENDER_BUMP; +extern LLFastTimer::DeclareTimer FTM_RENDER_MATERIALS; extern LLFastTimer::DeclareTimer FTM_RENDER_FULLBRIGHT; extern LLFastTimer::DeclareTimer FTM_RENDER_GLOW; extern LLFastTimer::DeclareTimer FTM_STATESORT; @@ -203,6 +209,12 @@ public: LLVector4a* normal = NULL, // return the surface normal at the intersection point LLVector4a* tangent = NULL // return the surface tangent at the intersection point ); + + //get the closest particle to start between start and end, returns the LLVOPartGroup and particle index + LLVOPartGroup* lineSegmentIntersectParticle(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection, + S32* face_hit); + + LLViewerObject* lineSegmentIntersectInHUD(const LLVector4a& start, const LLVector4a& end, BOOL pick_transparent, S32* face_hit, // return the face hit @@ -266,6 +278,7 @@ public: void forAllVisibleDrawables(void (*func)(LLDrawable*)); void renderObjects(U32 type, U32 mask, BOOL texture = TRUE, BOOL batch_texture = FALSE); + void renderMaskedObjects(U32 type, U32 mask, BOOL texture = TRUE, BOOL batch_texture = FALSE); void renderGroups(LLRenderPass* pass, U32 type, U32 mask, BOOL texture); void grabReferences(LLCullResult& result); @@ -287,6 +300,7 @@ public: void unbindDeferredShader(LLGLSLShader& shader); void renderDeferredLighting(); + void renderDeferredLightingToRT(LLRenderTarget* target); void generateWaterReflection(LLCamera& camera); void generateSunShadow(LLCamera& camera); @@ -429,11 +443,14 @@ public: RENDER_TYPE_WL_SKY = LLDrawPool::POOL_WL_SKY, RENDER_TYPE_GROUND = LLDrawPool::POOL_GROUND, RENDER_TYPE_TERRAIN = LLDrawPool::POOL_TERRAIN, - RENDER_TYPE_SIMPLE = LLDrawPool::POOL_SIMPLE, - RENDER_TYPE_GRASS = LLDrawPool::POOL_GRASS, - RENDER_TYPE_FULLBRIGHT = LLDrawPool::POOL_FULLBRIGHT, - RENDER_TYPE_BUMP = LLDrawPool::POOL_BUMP, - RENDER_TYPE_AVATAR = LLDrawPool::POOL_AVATAR, + RENDER_TYPE_SIMPLE = LLDrawPool::POOL_SIMPLE, + RENDER_TYPE_GRASS = LLDrawPool::POOL_GRASS, + RENDER_TYPE_ALPHA_MASK = LLDrawPool::POOL_ALPHA_MASK, + RENDER_TYPE_FULLBRIGHT_ALPHA_MASK = LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK, + RENDER_TYPE_FULLBRIGHT = LLDrawPool::POOL_FULLBRIGHT, + RENDER_TYPE_BUMP = LLDrawPool::POOL_BUMP, + RENDER_TYPE_MATERIALS = LLDrawPool::POOL_MATERIALS, + RENDER_TYPE_AVATAR = LLDrawPool::POOL_AVATAR, RENDER_TYPE_TREE = LLDrawPool::POOL_TREE, RENDER_TYPE_INVISIBLE = LLDrawPool::POOL_INVISIBLE, RENDER_TYPE_VOIDWATER = LLDrawPool::POOL_VOIDWATER, @@ -453,6 +470,22 @@ public: RENDER_TYPE_PASS_ALPHA = LLRenderPass::PASS_ALPHA, RENDER_TYPE_PASS_ALPHA_MASK = LLRenderPass::PASS_ALPHA_MASK, RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK = LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, + RENDER_TYPE_PASS_MATERIAL = LLRenderPass::PASS_MATERIAL, + RENDER_TYPE_PASS_MATERIAL_ALPHA = LLRenderPass::PASS_MATERIAL_ALPHA, + RENDER_TYPE_PASS_MATERIAL_ALPHA_MASK = LLRenderPass::PASS_MATERIAL_ALPHA_MASK, + RENDER_TYPE_PASS_MATERIAL_ALPHA_EMISSIVE= LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, + RENDER_TYPE_PASS_SPECMAP = LLRenderPass::PASS_SPECMAP, + RENDER_TYPE_PASS_SPECMAP_BLEND = LLRenderPass::PASS_SPECMAP_BLEND, + RENDER_TYPE_PASS_SPECMAP_MASK = LLRenderPass::PASS_SPECMAP_MASK, + RENDER_TYPE_PASS_SPECMAP_EMISSIVE = LLRenderPass::PASS_SPECMAP_EMISSIVE, + RENDER_TYPE_PASS_NORMMAP = LLRenderPass::PASS_NORMMAP, + RENDER_TYPE_PASS_NORMMAP_BLEND = LLRenderPass::PASS_NORMMAP_BLEND, + RENDER_TYPE_PASS_NORMMAP_MASK = LLRenderPass::PASS_NORMMAP_MASK, + RENDER_TYPE_PASS_NORMMAP_EMISSIVE = LLRenderPass::PASS_NORMMAP_EMISSIVE, + RENDER_TYPE_PASS_NORMSPEC = LLRenderPass::PASS_NORMSPEC, + RENDER_TYPE_PASS_NORMSPEC_BLEND = LLRenderPass::PASS_NORMSPEC_BLEND, + RENDER_TYPE_PASS_NORMSPEC_MASK = LLRenderPass::PASS_NORMSPEC_MASK, + RENDER_TYPE_PASS_NORMSPEC_EMISSIVE = LLRenderPass::PASS_NORMSPEC_EMISSIVE, // Following are object types (only used in drawable mRenderType) RENDER_TYPE_HUD = LLRenderPass::NUM_RENDER_TYPES, RENDER_TYPE_VOLUME, @@ -561,6 +594,7 @@ public: static BOOL sPickAvatar; static BOOL sReflectionRender; static BOOL sImpostorRender; + static BOOL sImpostorRenderAlphaDepthPass; static BOOL sUnderWaterRender; static BOOL sRenderGlow; static BOOL sTextureBindTest; @@ -571,6 +605,7 @@ public: static BOOL sMemAllocationThrottled; static S32 sVisibleLightCount; static F32 sMinRenderSize; + static BOOL sRenderingHUDs; //screen texture @@ -750,17 +785,20 @@ protected: // For quick-lookups into mPools (mapped by texture pointer) std::map mTerrainPools; std::map mTreePools; - LLDrawPool* mAlphaPool; + LLDrawPoolAlpha* mAlphaPool; LLDrawPool* mSkyPool; LLDrawPool* mTerrainPool; LLDrawPool* mWaterPool; LLDrawPool* mGroundPool; LLRenderPass* mSimplePool; LLRenderPass* mGrassPool; + LLRenderPass* mAlphaMaskPool; + LLRenderPass* mFullbrightAlphaMaskPool; LLRenderPass* mFullbrightPool; LLDrawPool* mInvisiblePool; LLDrawPool* mGlowPool; LLDrawPool* mBumpPool; + LLDrawPool* mMaterialsPool; LLDrawPool* mWLSkyPool; // Note: no need to keep an quick-lookup to avatar pools, since there's only one per avatar diff --git a/indra/newview/skins/default/textures/flatnormal.tga b/indra/newview/skins/default/textures/flatnormal.tga new file mode 100644 index 0000000000000000000000000000000000000000..6d5abd17821b5589422617c7e5665a93def5a48a GIT binary patch literal 92 rcmZQzU}As)76uju35Ngm^&}yf?vS8R*D%juPk%q%2sck3S3L#*jK3t_ literal 0 HcmV?d00001 From e5d71560e86a0e3cd352c8a85c94feed237e4cba Mon Sep 17 00:00:00 2001 From: Shyotl Date: Sat, 2 Nov 2013 02:38:23 -0500 Subject: [PATCH 12/17] Fix fullbright stuff in deferred. Have to re-assess old glow occlusion bugfixes. --- .../app_settings/shaders/class1/deferred/fullbrightF.glsl | 8 ++++---- .../shaders/class1/deferred/fullbrightShinyF.glsl | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl index 41cee1db0..f22b16965 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl @@ -166,12 +166,12 @@ void main() vec3 pos = vary_position; vec4 fogged = applyWaterFogDeferred(pos, vec4(color.rgb, final_alpha)); color.rgb = fogged.rgb; - //color.a = fogged.a; + color.a = fogged.a; #else - //color.a = final_alpha; + color.a = final_alpha; #endif - color.a = .0; - frag_color = color; + frag_color.rgb = color.rgb; + frag_color.a = color.a; } diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl index edc6ff049..b0db9876d 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl @@ -63,7 +63,7 @@ void main() color.rgb = fullbrightShinyAtmosTransport(color.rgb); color.rgb = fullbrightScaleSoftClip(color.rgb); - color.a = 0.0; + color.a = 1.0; color.rgb = pow(color.rgb, vec3(1.0/2.2)); From 378dd14adec4485b9778250a72c3ff9d6e45b077 Mon Sep 17 00:00:00 2001 From: Shyotl Date: Sat, 2 Nov 2013 03:29:32 -0500 Subject: [PATCH 13/17] Return to our nice ssao. --- .../shaders/class1/deferred/blurLightF.glsl | 7 +++---- .../shaders/class2/deferred/softenLightF.glsl | 15 ++++----------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/indra/newview/app_settings/shaders/class1/deferred/blurLightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/blurLightF.glsl index 3e836e794..c329bcde6 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/blurLightF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/blurLightF.glsl @@ -97,12 +97,11 @@ void main() vec4 col = defined_weight.xyxx * ccol; // relax tolerance according to distance to avoid speckling artifacts, as angles and distances are a lot more abrupt within a small screen area at larger distances - float pointplanedist_tolerance_pow2 = pos.z*pos.z*0.00005; + float pointplanedist_tolerance_pow2 = pos.z*-0.001; // perturb sampling origin slightly in screen-space to hide edge-ghosting artifacts where smoothing radius is quite large - float tc_mod = 0.5*(tc.x + tc.y); // mod(tc.x+tc.y,2) - tc_mod -= floor(tc_mod); - tc_mod *= 2.0; + vec2 tc_v = fract(0.5 * tc.xy); // we now have floor(mod(tc,2.0))*0.5 + float tc_mod = 2.0 * abs(tc_v.x - tc_v.y); // diff of x,y makes checkerboard tc += ( (tc_mod - 0.5) * getKern(1).z * dlt * 0.5 ); for (int i = 1; i < 4; i++) diff --git a/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl b/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl index 64ac5ebb2..efcb2f189 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl @@ -63,7 +63,7 @@ uniform float global_gamma; uniform float scene_light_strength; uniform mat3 env_mat; uniform vec4 shadow_clip; -uniform mat3 ssao_effect_mat; +uniform float ssao_effect; uniform vec3 sun_dir; VARYING vec2 vary_fragcoord; @@ -256,16 +256,6 @@ void calcAtmospherics(vec3 inPositionEye, float ambFactor) { //increase ambient when there are more clouds vec4 tmpAmbient = ambient + (vec4(1.) - ambient) * cloud_shadow * 0.5; - - /* decrease value and saturation (that in HSV, not HSL) for occluded areas - * // for HSV color/geometry used here, see http://gimp-savvy.com/BOOK/index.html?node52.html - * // The following line of code performs the equivalent of: - * float ambAlpha = tmpAmbient.a; - * float ambValue = dot(vec3(tmpAmbient), vec3(0.577)); // projection onto <1/rt(3), 1/rt(3), 1/rt(3)>, the neutral white-black axis - * vec3 ambHueSat = vec3(tmpAmbient) - vec3(ambValue); - * tmpAmbient = vec4(RenderSSAOEffect.valueFactor * vec3(ambValue) + RenderSSAOEffect.saturationFactor *(1.0 - ambFactor) * ambHueSat, ambAlpha); - */ - tmpAmbient = vec4(mix(ssao_effect_mat * tmpAmbient.rgb, tmpAmbient.rgb, ambFactor), tmpAmbient.a); //haze color setAdditiveColor( @@ -273,6 +263,9 @@ void calcAtmospherics(vec3 inPositionEye, float ambFactor) { + (haze_horizon * haze_weight) * (sunlight*(1.-cloud_shadow) * temp2.x + tmpAmbient))); + // decrease ambient value for occluded areas + tmpAmbient *= mix(ssao_effect, 1.0, ambFactor); + //brightness of surface both sunlight and ambient /*setSunlitColor(pow(vec3(sunlight * .5), vec3(global_gamma)) * global_gamma); setAmblitColor(pow(vec3(tmpAmbient * .25), vec3(global_gamma)) * global_gamma); From 77eb9283800c0418f086482c51ff17c130f90a42 Mon Sep 17 00:00:00 2001 From: Shyotl Date: Sat, 2 Nov 2013 03:33:21 -0500 Subject: [PATCH 14/17] Add LLGLManager::mIsMobileGF --- indra/llrender/llgl.cpp | 12 +++++++++++- indra/llrender/llgl.h | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 9c5ffeb7d..ae78758b4 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -450,7 +450,9 @@ LLGLManager::LLGLManager() : mIsGFFX(FALSE), mATIOffsetVerticalLines(FALSE), mATIOldDriver(FALSE), - +#if LL_DARWIN + mIsMobileGF(FALSE), +#endif mHasRequirements(TRUE), mHasSeparateSpecularColor(FALSE), @@ -648,6 +650,14 @@ bool LLGLManager::initGL() { mIsGF3 = TRUE; } +#if LL_DARWIN + else if ((mGLRenderer.find("9400M") != std::string::npos) + || (mGLRenderer.find("9600M") != std::string::npos) + || (mGLRenderer.find("9800M") != std::string::npos)) + { + mIsMobileGF = TRUE; + } +#endif } else if (mGLVendor.find("INTEL") != std::string::npos diff --git a/indra/llrender/llgl.h b/indra/llrender/llgl.h index de4a81bb5..65161a6fa 100644 --- a/indra/llrender/llgl.h +++ b/indra/llrender/llgl.h @@ -123,6 +123,11 @@ public: BOOL mATIOffsetVerticalLines; BOOL mATIOldDriver; +#if LL_DARWIN + // Needed to distinguish problem cards on older Macs that break with Materials + BOOL mIsMobileGF; +#endif + // Whether this version of GL is good enough for SL to use BOOL mHasRequirements; From 822b728adb0e76daa50478da71e0458d3c63ea6b Mon Sep 17 00:00:00 2001 From: Latif Khalifa Date: Sun, 3 Nov 2013 00:32:01 +0100 Subject: [PATCH 15/17] Only build Quicktime on 32 bit Windows Helps address issue 1180 --- indra/cmake/QuickTimePlugin.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/cmake/QuickTimePlugin.cmake b/indra/cmake/QuickTimePlugin.cmake index 8afd8f304..cb54e287a 100644 --- a/indra/cmake/QuickTimePlugin.cmake +++ b/indra/cmake/QuickTimePlugin.cmake @@ -8,7 +8,7 @@ endif(INSTALL_PROPRIETARY) if (DARWIN) include(CMakeFindFrameworks) find_library(QUICKTIME_LIBRARY QuickTime) -elseif (WINDOWS) +elseif (WINDOWS AND WORD_SIZE EQUAL 32) set(QUICKTIME_SDK_DIR "$ENV{PROGRAMFILES}/QuickTime SDK" CACHE PATH "Location of the QuickTime SDK.") From 827bbc9b89f3ba33c31f7a401a772d9f56c38a52 Mon Sep 17 00:00:00 2001 From: Shyotl Date: Sat, 2 Nov 2013 19:15:28 -0500 Subject: [PATCH 16/17] Fix up a few glowies. --- .../app_settings/shaders/class1/deferred/fullbrightF.glsl | 7 ++++++- .../shaders/class1/deferred/fullbrightShinyF.glsl | 2 +- indra/newview/lldrawpoolsimple.cpp | 4 +++- indra/newview/pipeline.cpp | 6 +++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl index f22b16965..469b26497 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightF.glsl @@ -166,10 +166,15 @@ void main() vec3 pos = vary_position; vec4 fogged = applyWaterFogDeferred(pos, vec4(color.rgb, final_alpha)); color.rgb = fogged.rgb; +#ifndef HAS_ALPHA_MASK color.a = fogged.a; -#else +#endif +#elif !HAS_ALPHA_MASK color.a = final_alpha; #endif +#if HAS_ALPHA_MASK + color.a = 0.0; +#endif frag_color.rgb = color.rgb; frag_color.a = color.a; diff --git a/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl index b0db9876d..edc6ff049 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/fullbrightShinyF.glsl @@ -63,7 +63,7 @@ void main() color.rgb = fullbrightShinyAtmosTransport(color.rgb); color.rgb = fullbrightScaleSoftClip(color.rgb); - color.a = 1.0; + color.a = 0.0; color.rgb = pow(color.rgb, vec3(1.0/2.2)); diff --git a/indra/newview/lldrawpoolsimple.cpp b/indra/newview/lldrawpoolsimple.cpp index 638a9dfa0..3f894912a 100644 --- a/indra/newview/lldrawpoolsimple.cpp +++ b/indra/newview/lldrawpoolsimple.cpp @@ -563,7 +563,7 @@ void LLDrawPoolFullbright::renderPostDeferred(S32 pass) { LLFastTimer t(FTM_RENDER_FULLBRIGHT); gGL.setColorMask(true, true); - gGL.setSceneBlendType(LLRender::BT_ALPHA); + LLGLDisable blend(GL_BLEND); U32 fullbright_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TEXTURE_INDEX; pushBatches(LLRenderPass::PASS_FULLBRIGHT, fullbright_mask, TRUE, TRUE); @@ -689,9 +689,11 @@ void LLDrawPoolFullbrightAlphaMask::beginPostDeferredPass(S32 pass) void LLDrawPoolFullbrightAlphaMask::renderPostDeferred(S32 pass) { LLFastTimer t(FTM_RENDER_FULLBRIGHT); + gGL.setColorMask(true, true); LLGLDisable blend(GL_BLEND); U32 fullbright_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TEXTURE_INDEX; pushMaskBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, fullbright_mask, TRUE, TRUE); + gGL.setColorMask(true, false); } void LLDrawPoolFullbrightAlphaMask::endPostDeferredPass(S32 pass) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 52edd16aa..230bc59a0 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -8928,10 +8928,10 @@ void LLPipeline::renderDeferredLightingToRT(LLRenderTarget* target) pushRenderTypeMask(); andRenderTypeMask(LLPipeline::RENDER_TYPE_ALPHA, LLPipeline::RENDER_TYPE_FULLBRIGHT, - LLPipeline::RENDER_TYPE_VOLUME, + //LLPipeline::RENDER_TYPE_VOLUME, LLPipeline::RENDER_TYPE_GLOW, LLPipeline::RENDER_TYPE_BUMP, - LLPipeline::RENDER_TYPE_PASS_SIMPLE, + /*LLPipeline::RENDER_TYPE_PASS_SIMPLE, //These aren't used. LLPipeline::RENDER_TYPE_PASS_ALPHA, LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK, LLPipeline::RENDER_TYPE_PASS_BUMP, @@ -8943,7 +8943,7 @@ void LLPipeline::renderDeferredLightingToRT(LLRenderTarget* target) LLPipeline::RENDER_TYPE_PASS_GRASS, LLPipeline::RENDER_TYPE_PASS_SHINY, LLPipeline::RENDER_TYPE_PASS_INVISIBLE, - LLPipeline::RENDER_TYPE_PASS_INVISI_SHINY, + LLPipeline::RENDER_TYPE_PASS_INVISI_SHINY,*/ LLPipeline::RENDER_TYPE_AVATAR, LLPipeline::RENDER_TYPE_ALPHA_MASK, LLPipeline::RENDER_TYPE_FULLBRIGHT_ALPHA_MASK, From 6bbb24b974323a29fb1685d1fcd0d2f43fdbf290 Mon Sep 17 00:00:00 2001 From: Shyotl Date: Sat, 2 Nov 2013 22:43:11 -0500 Subject: [PATCH 17/17] Revert occlusion changes to try and pinpoint flicker issue. --- indra/newview/pipeline.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 230bc59a0..23192c52a 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -2244,11 +2244,11 @@ void LLPipeline::updateCull(LLCamera& camera, LLCullResult& result, S32 water_cl if (to_texture) { - if (LLPipeline::sRenderDeferred) + /*if (LLPipeline::sRenderDeferred) { mOcclusionDepth.bindTarget(); } - else + else*/ { mScreen.bindTarget(); } @@ -2389,11 +2389,11 @@ void LLPipeline::updateCull(LLCamera& camera, LLCullResult& result, S32 water_cl if (to_texture) { - if (LLPipeline::sRenderDeferred) + /*if (LLPipeline::sRenderDeferred) { mOcclusionDepth.flush(); } - else + else*/ { mScreen.flush(); } @@ -4382,7 +4382,7 @@ void LLPipeline::renderGeomPostDeferred(LLCamera& camera, bool do_occlusion) gGLLastMatrix = NULL; gGL.loadMatrix(gGLModelView); LLGLSLShader::bindNoShader(); - doOcclusion(camera, mScreen, mOcclusionDepth, &mDeferredDepth); + doOcclusion(camera/*, mScreen, mOcclusionDepth, &mDeferredDepth*/); gGL.setColorMask(true, false); } @@ -9566,11 +9566,11 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera gDeferredShadowCubeProgram.bind(); } - LLRenderTarget& occlusion_target = mShadowOcclusion[LLViewerCamera::sCurCameraID-1]; + //LLRenderTarget& occlusion_target = mShadowOcclusion[LLViewerCamera::sCurCameraID-1]; - occlusion_target.bindTarget(); + //occlusion_target.bindTarget(); updateCull(shadow_cam, result); - occlusion_target.flush(); + //occlusion_target.flush(); stateSort(shadow_cam, result); @@ -9642,7 +9642,7 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera renderMaskedObjects(LLRenderPass::PASS_ALPHA_MASK, mask, TRUE, TRUE); renderMaskedObjects(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, mask, TRUE, TRUE); gDeferredShadowAlphaMaskProgram.setMinimumAlpha(0.598f); - renderObjects(LLRenderPass::PASS_ALPHA, mask, TRUE, TRUE); + //renderObjects(LLRenderPass::PASS_ALPHA, mask, TRUE, TRUE); mask = mask & ~LLVertexBuffer::MAP_TEXTURE_INDEX; @@ -9662,9 +9662,9 @@ void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera gGLLastMatrix = NULL; gGL.loadMatrix(gGLModelView); - LLRenderTarget& occlusion_source = mShadow[LLViewerCamera::sCurCameraID-1]; + //LLRenderTarget& occlusion_source = mShadow[LLViewerCamera::sCurCameraID-1]; - doOcclusion(shadow_cam, occlusion_source, occlusion_target); + doOcclusion(shadow_cam/*, occlusion_source, occlusion_target*/); if (use_shader) {