From 56f697e5f0ce166981bf2019588a22126c894330 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sat, 24 Feb 2024 23:26:34 -0800 Subject: [PATCH 01/35] merge old work --- .../dialogs/graphics/GraphicsSettings.qml | 3 +- interface/src/SecondaryCamera.cpp | 2 +- interface/src/graphics/GraphicsEngine.cpp | 4 +- interface/src/raypick/ParabolaPointer.cpp | 7 +- interface/src/raypick/ParabolaPointer.h | 2 + .../scripting/RenderScriptingInterface.cpp | 83 ++-- .../src/scripting/RenderScriptingInterface.h | 12 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 5 +- .../src/display-plugins/OpenGLDisplayPlugin.h | 1 - .../src/RenderableEntityItem.h | 1 + .../src/RenderableGizmoEntityItem.cpp | 6 +- .../src/RenderableGridEntityItem.cpp | 7 +- .../src/RenderableImageEntityItem.cpp | 5 +- .../src/RenderableLineEntityItem.cpp | 10 +- .../src/RenderableMaterialEntityItem.cpp | 5 +- .../src/RenderableModelEntityItem.cpp | 7 +- .../RenderableParticleEffectEntityItem.cpp | 7 +- .../src/RenderablePolyLineEntityItem.cpp | 5 + .../src/RenderablePolyVoxEntityItem.cpp | 6 +- .../src/RenderableShapeEntityItem.cpp | 8 +- .../src/RenderableTextEntityItem.cpp | 18 +- .../src/RenderableTextEntityItem.h | 2 + .../src/RenderableWebEntityItem.cpp | 9 +- .../entities-renderer/src/paintStroke.slf | 16 +- .../entities-renderer/src/paintStroke.slv | 20 +- libraries/entities-renderer/src/polyvox.slf | 3 + libraries/entities-renderer/src/polyvox.slv | 3 +- .../src/textured_particle.slv | 1 + .../gpu-gl-common/src/gpu/gl/GLBackend.cpp | 70 +-- .../gpu-gl-common/src/gpu/gl/GLBackend.h | 115 +++-- .../src/gpu/gl/GLBackendPipeline.cpp | 12 - .../src/gpu/gl/GLBackendTransform.cpp | 143 ++++-- .../gpu-gl-common/src/gpu/gl/GLPipeline.cpp | 5 - .../gpu-gl-common/src/gpu/gl/GLPipeline.h | 3 - libraries/gpu-gl/src/gpu/gl41/GL41Backend.h | 2 + .../src/gpu/gl41/GL41BackendTransform.cpp | 34 +- libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 2 + .../src/gpu/gl45/GL45BackendTexture.cpp | 4 +- .../src/gpu/gl45/GL45BackendTransform.cpp | 30 +- libraries/gpu-gles/src/gpu/gles/GLESBackend.h | 2 + .../src/gpu/gles/GLESBackendTransform.cpp | 34 +- libraries/gpu/src/gpu/Backend.cpp | 126 +++++ libraries/gpu/src/gpu/Backend.h | 141 ++++++ libraries/gpu/src/gpu/Batch.cpp | 79 ++- libraries/gpu/src/gpu/Batch.h | 66 ++- libraries/gpu/src/gpu/Context.cpp | 68 --- libraries/gpu/src/gpu/Context.h | 119 +---- libraries/gpu/src/gpu/DrawUnitQuad.slv | 27 + libraries/gpu/src/gpu/FrameIOKeys.h | 11 +- libraries/gpu/src/gpu/FrameReader.cpp | 3 +- libraries/gpu/src/gpu/FrameWriter.cpp | 5 +- libraries/gpu/src/gpu/Query.h | 2 + libraries/gpu/src/gpu/Transform.slh | 152 ++++-- .../gpu/src/gpu/TransformCamera_shared.slh | 32 +- .../gpu/src/gpu/TransformObject_shared.slh | 19 + libraries/gpu/src/gpu/drawColor.slp | 3 +- libraries/gpu/src/gpu/drawWhite.slp | 2 + libraries/graphics/src/graphics/Haze.slh | 5 +- libraries/graphics/src/graphics/Skybox.cpp | 8 +- libraries/graphics/src/graphics/Skybox.h | 4 +- libraries/graphics/src/graphics/skybox.slf | 29 +- libraries/graphics/src/graphics/skybox.slh | 65 +++ libraries/graphics/src/graphics/skybox.slv | 15 +- .../src/procedural/ProceduralSkybox.cpp | 10 +- .../src/procedural/ProceduralSkybox.h | 4 +- .../src/procedural/proceduralSkybox.slf | 21 +- .../render-utils/src/AntialiasingEffect.cpp | 465 ++++++++---------- .../render-utils/src/AntialiasingEffect.h | 295 ++++++----- .../render-utils/src/BackgroundStage.cpp | 15 +- libraries/render-utils/src/BackgroundStage.h | 5 +- libraries/render-utils/src/BloomEffect.cpp | 11 +- .../src/BloomThreshold.shared.slh | 2 + libraries/render-utils/src/BloomThreshold.slf | 3 +- .../src/CauterizedMeshPartPayload.cpp | 7 +- .../render-utils/src/DebugDeferredBuffer.cpp | 41 +- .../render-utils/src/DebugDeferredBuffer.h | 9 +- .../render-utils/src/DeferredBufferRead.slh | 24 +- .../render-utils/src/DeferredBufferWrite.slh | 69 ++- .../src/DeferredBufferWrite_shared.slh | 12 + .../src/DeferredFrameTransform.cpp | 80 ++- .../render-utils/src/DeferredFrameTransform.h | 45 +- .../render-utils/src/DeferredFramebuffer.cpp | 40 +- .../render-utils/src/DeferredFramebuffer.h | 8 +- .../src/DeferredLightingEffect.cpp | 6 +- .../render-utils/src/DeferredLightingEffect.h | 3 +- .../render-utils/src/DeferredTransform.slh | 130 +++-- .../src/DeferredTransform_shared.slh | 33 ++ libraries/render-utils/src/GeometryCache.cpp | 9 +- libraries/render-utils/src/Haze.slf | 9 +- .../render-utils/src/HighlightEffect.cpp | 48 +- libraries/render-utils/src/HighlightEffect.h | 23 +- .../render-utils/src/Highlight_aabox.slv | 8 +- libraries/render-utils/src/LightClusters.cpp | 12 +- libraries/render-utils/src/LightClusters.h | 3 +- .../render-utils/src/MeshPartPayload.cpp | 9 +- libraries/render-utils/src/MeshPartPayload.h | 3 + .../render-utils/src/RenderCommonTask.cpp | 33 +- libraries/render-utils/src/RenderCommonTask.h | 7 +- .../render-utils/src/RenderDeferredTask.cpp | 155 +++--- .../render-utils/src/RenderDeferredTask.h | 20 +- .../render-utils/src/RenderForwardTask.cpp | 40 +- .../render-utils/src/RenderForwardTask.h | 6 +- .../render-utils/src/RenderHUDLayerTask.cpp | 23 +- .../render-utils/src/RenderHUDLayerTask.h | 11 +- .../render-utils/src/RenderPipelines.cpp | 2 +- libraries/render-utils/src/RenderViewTask.cpp | 16 +- libraries/render-utils/src/RenderViewTask.h | 14 +- .../render-utils/src/StencilMaskPass.cpp | 6 + libraries/render-utils/src/StencilMaskPass.h | 1 + .../render-utils/src/VelocityBufferPass.cpp | 165 ------- .../render-utils/src/VelocityBufferPass.h | 89 ---- libraries/render-utils/src/VelocityWrite.slh | 34 ++ libraries/render-utils/src/ZoneRenderer.cpp | 6 +- libraries/render-utils/src/aa_blend.slf | 52 ++ .../src/deferred_light_limited.slv | 3 +- .../src/directional_skybox_light.slf | 9 +- .../render-utils/src/drawWorkloadProxy.slf | 2 + .../render-utils/src/drawWorkloadProxy.slv | 30 +- .../render-utils/src/drawWorkloadView.slf | 2 + .../render-utils/src/drawWorkloadView.slv | 19 +- libraries/render-utils/src/grid.slf | 6 +- libraries/render-utils/src/grid.slv | 36 ++ .../src/lightClusters_drawClusterContent.slf | 2 +- .../lightClusters_drawClusterFromDepth.slf | 2 +- .../src/local_lights_drawOutline.slf | 2 +- .../render-utils/src/local_lights_shading.slf | 5 +- libraries/render-utils/src/model.slf | 19 +- libraries/render-utils/src/model.slv | 3 +- libraries/render-utils/src/parabola.slf | 9 +- libraries/render-utils/src/parabola.slv | 9 +- .../src/render-utils/ShaderConstants.h | 11 +- .../{fxaa_blend.slp => aa_blend.slp} | 0 .../render-utils/src/render-utils/grid.slp | 1 - .../velocityBuffer_cameraMotion.slp | 1 - libraries/render-utils/src/sdf_text3D.slf | 40 +- libraries/render-utils/src/sdf_text3D.slh | 19 +- libraries/render-utils/src/sdf_text3D.slv | 9 +- libraries/render-utils/src/simple.slf | 55 ++- libraries/render-utils/src/simple.slv | 19 +- .../render-utils/src/simple_procedural.slf | 17 +- .../render-utils/src/simple_procedural.slv | 3 +- .../src/surfaceGeometry_makeCurvature.slf | 10 +- libraries/render-utils/src/taa.slf | 33 +- libraries/render-utils/src/taa.slh | 318 +++++++----- libraries/render-utils/src/taa_blend.slf | 1 - .../src/velocityBuffer_cameraMotion.slf | 43 -- libraries/render-utils/src/web_browser.slf | 3 +- libraries/render-utils/src/web_browser.slv | 6 +- .../render-utils/src/zone_drawAmbient.slf | 2 +- .../render-utils/src/zone_drawKeyLight.slf | 5 +- .../render-utils/src/zone_drawSkybox.slf | 2 +- .../render/src/render/DrawSceneOctree.cpp | 16 +- libraries/render/src/render/DrawSceneOctree.h | 10 +- libraries/render/src/render/DrawStatus.cpp | 14 +- libraries/render/src/render/DrawStatus.h | 6 +- libraries/render/src/render/DrawTask.cpp | 7 +- libraries/render/src/render/DrawTask.h | 3 + libraries/render/src/render/Engine.h | 5 + libraries/render/src/render/ShapePipeline.cpp | 1 + libraries/render/src/render/ShapePipeline.h | 1 + .../render/src/render/drawItemStatus.slv | 4 +- .../utilities/render/luci/Antialiasing.qml | 33 +- .../utilities/render/luci/Framebuffer.qml | 1 + tests-manual/gpu/src/TestWindow.cpp | 4 +- tools/gpu-frame-player/src/RenderThread.cpp | 6 +- 165 files changed, 2673 insertions(+), 2031 deletions(-) create mode 100644 libraries/gpu/src/gpu/Backend.cpp create mode 100644 libraries/gpu/src/gpu/Backend.h create mode 100644 libraries/gpu/src/gpu/DrawUnitQuad.slv create mode 100644 libraries/gpu/src/gpu/TransformObject_shared.slh create mode 100644 libraries/gpu/src/gpu/drawWhite.slp create mode 100644 libraries/graphics/src/graphics/skybox.slh create mode 100644 libraries/render-utils/src/DeferredBufferWrite_shared.slh create mode 100644 libraries/render-utils/src/DeferredTransform_shared.slh delete mode 100644 libraries/render-utils/src/VelocityBufferPass.cpp delete mode 100644 libraries/render-utils/src/VelocityBufferPass.h create mode 100644 libraries/render-utils/src/VelocityWrite.slh create mode 100644 libraries/render-utils/src/aa_blend.slf create mode 100644 libraries/render-utils/src/grid.slv rename libraries/render-utils/src/render-utils/{fxaa_blend.slp => aa_blend.slp} (100%) delete mode 100644 libraries/render-utils/src/render-utils/velocityBuffer_cameraMotion.slp delete mode 100644 libraries/render-utils/src/velocityBuffer_cameraMotion.slf diff --git a/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml b/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml index 06894d9576..db9830d2f3 100644 --- a/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml +++ b/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml @@ -4,6 +4,7 @@ // // Created by Zach Fox on 2019-07-10 // Copyright 2019 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -501,7 +502,7 @@ Flickable { ListModel { id: antialiasingModel - // Maintain same order as "AntialiasingConfig::Mode". + // Maintain same order as "AntialiasingSetupConfig::Mode". ListElement { text: "None" } diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 704d7963e7..185d2ca1b8 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -275,7 +275,7 @@ public: void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor) { const auto cachedArg = task.addJob("SecondaryCamera"); - task.addJob("RenderSecondView", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); + task.addJob("RenderSecondView", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1, RenderViewTask::TransformOffset::SECONDARY_VIEW); task.addJob("EndSecondaryCamera", cachedArg); } \ No newline at end of file diff --git a/interface/src/graphics/GraphicsEngine.cpp b/interface/src/graphics/GraphicsEngine.cpp index bf69efd23e..c519ecc753 100644 --- a/interface/src/graphics/GraphicsEngine.cpp +++ b/interface/src/graphics/GraphicsEngine.cpp @@ -262,14 +262,14 @@ void GraphicsEngine::render_performFrame() { batch.enableStereo(isStereo); batch.clearDepthStencilFramebuffer(1.0, 0); batch.setViewportTransform({ 0, 0, finalFramebuffer->getSize() }); - _splashScreen->render(batch, viewFrustum, renderArgs._renderMethod == RenderArgs::RenderMethod::FORWARD); + _splashScreen->render(batch, viewFrustum, renderArgs._renderMethod == RenderArgs::RenderMethod::FORWARD, render::RenderEngine::TS_BACKGROUND_VIEW); }); } else { { PROFILE_RANGE(render, "/renderOverlay"); PerformanceTimer perfTimer("renderOverlay"); // NOTE: There is no batch associated with this renderArgs - // the ApplicationOverlay class assumes it's viewport is set up to be the device size + // the ApplicationOverlay class assumes its viewport is set up to be the device size renderArgs._viewport = glm::ivec4(0, 0, qApp->getDeviceSize()); qApp->getApplicationOverlay().renderOverlay(&renderArgs); } diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 5f045c5504..321b4ea17c 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -444,7 +444,10 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) Transform transform; transform.setTranslation(_origin); - batch.setModelTransform(transform); + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == RenderArgs::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == RenderArgs::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } batch.setPipeline(getParabolaPipeline(args->_renderMethod == render::Args::RenderMethod::FORWARD)); @@ -479,4 +482,4 @@ namespace render { template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { return ShapeKey::Builder::ownPipeline(); } -} \ No newline at end of file +} diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 59168be5ed..950ddc72ba 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -1,6 +1,7 @@ // // Created by Sam Gondelman 7/17/2018 // Copyright 2018 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -62,6 +63,7 @@ public: render::ItemKey _key; glm::vec3 _origin { 0.0f }; + Transform _prevRenderTransform; bool _isVisibleInSecondaryCamera { DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA }; bool _drawInFront { DEFAULT_PARABOLA_DRAWINFRONT }; bool _visible { false }; diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index 12814aa6b6..50bdb5557d 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -19,14 +19,14 @@ STATIC_SCRIPT_TYPES_INITIALIZER((+[](ScriptManager* manager){ auto scriptEngine = manager->engine().get(); scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "RenderMethod"); - scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "Mode"); + scriptRegisterMetaType, scriptValueToEnumClass >(scriptEngine, "Mode"); })); STATIC_SCRIPT_INITIALIZER(+[](ScriptManager* manager){ auto scriptEngine = manager->engine().get(); scriptEngine->registerEnum("Render.RenderMethod",QMetaEnum::fromType()); - scriptEngine->registerEnum("AntialiasingMode",QMetaEnum::fromType()); + scriptEngine->registerEnum("AntialiasingMode",QMetaEnum::fromType()); }); RenderScriptingInterface* RenderScriptingInterface::getInstance() { @@ -49,7 +49,7 @@ void RenderScriptingInterface::loadSettings() { _shadowsEnabled = (_shadowsEnabledSetting.get()); _ambientOcclusionEnabled = (_ambientOcclusionEnabledSetting.get()); //_antialiasingMode = (_antialiasingModeSetting.get()); - _antialiasingMode = static_cast(_antialiasingModeSetting.get()); + _antialiasingMode = static_cast(_antialiasingModeSetting.get()); _viewportResolutionScale = (_viewportResolutionScaleSetting.get()); _fullScreenScreen = (_fullScreenScreenSetting.get()); }); @@ -84,10 +84,16 @@ void RenderScriptingInterface::forceRenderMethod(RenderMethod renderMethod) { _renderMethod = (int)renderMethod; _renderMethodSetting.set((int)renderMethod); - auto config = dynamic_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.DeferredForwardSwitch")); + auto renderConfig = qApp->getRenderEngine()->getConfiguration(); + auto config = dynamic_cast(renderConfig->getConfig("RenderMainView.DeferredForwardSwitch")); if (config) { config->setBranch((int)renderMethod); } + + auto secondaryConfig = dynamic_cast(renderConfig->getConfig("RenderSecondView.DeferredForwardSwitch")); + if (secondaryConfig) { + secondaryConfig->setBranch((int)renderMethod); + } }); } @@ -111,17 +117,16 @@ void RenderScriptingInterface::forceShadowsEnabled(bool enabled) { _renderSettingLock.withWriteLock([&] { _shadowsEnabled = (enabled); _shadowsEnabledSetting.set(enabled); + Menu::getInstance()->setIsOptionChecked(MenuOption::Shadows, enabled); auto renderConfig = qApp->getRenderEngine()->getConfiguration(); assert(renderConfig); auto lightingModelConfig = renderConfig->getConfig("RenderMainView.LightingModel"); if (lightingModelConfig) { - Menu::getInstance()->setIsOptionChecked(MenuOption::Shadows, enabled); lightingModelConfig->setShadow(enabled); } auto secondaryLightingModelConfig = renderConfig->getConfig("RenderSecondView.LightingModel"); if (secondaryLightingModelConfig) { - Menu::getInstance()->setIsOptionChecked(MenuOption::Shadows, enabled); secondaryLightingModelConfig->setShadow(enabled); } }); @@ -142,63 +147,70 @@ void RenderScriptingInterface::forceAmbientOcclusionEnabled(bool enabled) { _renderSettingLock.withWriteLock([&] { _ambientOcclusionEnabled = (enabled); _ambientOcclusionEnabledSetting.set(enabled); + Menu::getInstance()->setIsOptionChecked(MenuOption::AmbientOcclusion, enabled); - auto lightingModelConfig = qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.LightingModel"); + auto renderConfig = qApp->getRenderEngine()->getConfiguration(); + auto lightingModelConfig = renderConfig->getConfig("RenderMainView.LightingModel"); if (lightingModelConfig) { - Menu::getInstance()->setIsOptionChecked(MenuOption::AmbientOcclusion, enabled); lightingModelConfig->setAmbientOcclusion(enabled); } + + auto secondaryLightingModelConfig = renderConfig->getConfig("RenderSecondView.LightingModel"); + if (secondaryLightingModelConfig) { + secondaryLightingModelConfig->setAmbientOcclusion(enabled); + } }); } -AntialiasingConfig::Mode RenderScriptingInterface::getAntialiasingMode() const { +AntialiasingSetupConfig::Mode RenderScriptingInterface::getAntialiasingMode() const { return _antialiasingMode; } -void RenderScriptingInterface::setAntialiasingMode(AntialiasingConfig::Mode mode) { +void RenderScriptingInterface::setAntialiasingMode(AntialiasingSetupConfig::Mode mode) { if (_antialiasingMode != mode) { forceAntialiasingMode(mode); emit settingsChanged(); } } -void setAntialiasingModeForView(AntialiasingConfig::Mode mode, JitterSampleConfig *jitterCamConfig, AntialiasingConfig *antialiasingConfig) { +void setAntialiasingModeForView(AntialiasingSetupConfig::Mode mode, AntialiasingSetupConfig *antialiasingSetupConfig, AntialiasingConfig *antialiasingConfig) { switch (mode) { - case AntialiasingConfig::Mode::NONE: - jitterCamConfig->none(); + case AntialiasingSetupConfig::Mode::NONE: + antialiasingSetupConfig->none(); antialiasingConfig->blend = 1; antialiasingConfig->setDebugFXAA(false); break; - case AntialiasingConfig::Mode::TAA: - jitterCamConfig->play(); + case AntialiasingSetupConfig::Mode::TAA: + antialiasingSetupConfig->play(); antialiasingConfig->blend = 0.25; antialiasingConfig->setDebugFXAA(false); break; - case AntialiasingConfig::Mode::FXAA: - jitterCamConfig->none(); + case AntialiasingSetupConfig::Mode::FXAA: + antialiasingSetupConfig->none(); antialiasingConfig->blend = 0.25; antialiasingConfig->setDebugFXAA(true); break; default: - jitterCamConfig->none(); + antialiasingSetupConfig->none(); antialiasingConfig->blend = 1; antialiasingConfig->setDebugFXAA(false); break; } } -void RenderScriptingInterface::forceAntialiasingMode(AntialiasingConfig::Mode mode) { +void RenderScriptingInterface::forceAntialiasingMode(AntialiasingSetupConfig::Mode mode) { _renderSettingLock.withWriteLock([&] { _antialiasingMode = mode; - auto mainViewJitterCamConfig = qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.JitterCam"); - auto mainViewAntialiasingConfig = qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.Antialiasing"); - auto secondViewJitterCamConfig = qApp->getRenderEngine()->getConfiguration()->getConfig("RenderSecondView.JitterCam"); - auto secondViewAntialiasingConfig = qApp->getRenderEngine()->getConfiguration()->getConfig("RenderSecondView.Antialiasing"); - if (mode != AntialiasingConfig::Mode::NONE - && mode != AntialiasingConfig::Mode::TAA - && mode != AntialiasingConfig::Mode::FXAA) { - _antialiasingMode = AntialiasingConfig::Mode::NONE; + auto renderConfig = qApp->getRenderEngine()->getConfiguration(); + auto mainViewJitterCamConfig = renderConfig->getConfig("RenderMainView.JitterCam"); + auto mainViewAntialiasingConfig = renderConfig->getConfig("RenderMainView.Antialiasing"); + auto secondViewJitterCamConfig = renderConfig->getConfig("RenderSecondView.JitterCam"); + auto secondViewAntialiasingConfig = renderConfig->getConfig("RenderSecondView.Antialiasing"); + if (mode != AntialiasingSetupConfig::Mode::NONE + && mode != AntialiasingSetupConfig::Mode::TAA + && mode != AntialiasingSetupConfig::Mode::FXAA) { + _antialiasingMode = AntialiasingSetupConfig::Mode::NONE; } if (mainViewJitterCamConfig && mainViewAntialiasingConfig) { setAntialiasingModeForView( mode, mainViewJitterCamConfig, mainViewAntialiasingConfig); @@ -271,7 +283,7 @@ void RenderScriptingInterface::forceViewportResolutionScale(float scale) { return; } _renderSettingLock.withWriteLock([&] { - _viewportResolutionScale = (scale); + _viewportResolutionScale = scale; _viewportResolutionScaleSetting.set(scale); auto renderConfig = qApp->getRenderEngine()->getConfiguration(); @@ -279,12 +291,23 @@ void RenderScriptingInterface::forceViewportResolutionScale(float scale) { auto deferredView = renderConfig->getConfig("RenderMainView.RenderDeferredTask"); // mainView can be null if we're rendering in forward mode if (deferredView) { - deferredView->setProperty("resolutionScale", _viewportResolutionScale); + deferredView->setProperty("resolutionScale", scale); } auto forwardView = renderConfig->getConfig("RenderMainView.RenderForwardTask"); // mainView can be null if we're rendering in forward mode if (forwardView) { - forwardView->setProperty("resolutionScale", _viewportResolutionScale); + forwardView->setProperty("resolutionScale", scale); + } + + auto deferredSecondView = renderConfig->getConfig("RenderSecondView.RenderDeferredTask"); + // mainView can be null if we're rendering in forward mode + if (deferredSecondView) { + deferredSecondView->setProperty("resolutionScale", scale); + } + auto forwardSecondView = renderConfig->getConfig("RenderMainView.RenderForwardTask"); + // mainView can be null if we're rendering in forward mode + if (forwardSecondView) { + forwardSecondView->setProperty("resolutionScale", scale); } }); } diff --git a/interface/src/scripting/RenderScriptingInterface.h b/interface/src/scripting/RenderScriptingInterface.h index 2025c71510..dd95cca568 100644 --- a/interface/src/scripting/RenderScriptingInterface.h +++ b/interface/src/scripting/RenderScriptingInterface.h @@ -39,7 +39,7 @@ class RenderScriptingInterface : public QObject { Q_PROPERTY(RenderMethod renderMethod READ getRenderMethod WRITE setRenderMethod NOTIFY settingsChanged) Q_PROPERTY(bool shadowsEnabled READ getShadowsEnabled WRITE setShadowsEnabled NOTIFY settingsChanged) Q_PROPERTY(bool ambientOcclusionEnabled READ getAmbientOcclusionEnabled WRITE setAmbientOcclusionEnabled NOTIFY settingsChanged) - Q_PROPERTY(AntialiasingConfig::Mode antialiasingMode READ getAntialiasingMode WRITE setAntialiasingMode NOTIFY settingsChanged) + Q_PROPERTY(AntialiasingSetupConfig::Mode antialiasingMode READ getAntialiasingMode WRITE setAntialiasingMode NOTIFY settingsChanged) Q_PROPERTY(float viewportResolutionScale READ getViewportResolutionScale WRITE setViewportResolutionScale NOTIFY settingsChanged) Q_PROPERTY(float verticalFieldOfView READ getVerticalFieldOfView WRITE setVerticalFieldOfView NOTIFY settingsChanged) @@ -153,14 +153,14 @@ public slots: * @function Render.getAntialiasingMode * @returns {AntialiasingMode} The active anti-aliasing mode. */ - AntialiasingConfig::Mode getAntialiasingMode() const; + AntialiasingSetupConfig::Mode getAntialiasingMode() const; /*@jsdoc * Sets the active anti-aliasing mode. * @function Render.setAntialiasingMode * @param {AntialiasingMode} The active anti-aliasing mode. */ - void setAntialiasingMode(AntialiasingConfig::Mode mode); + void setAntialiasingMode(AntialiasingSetupConfig::Mode mode); /*@jsdoc * Gets the view port resolution scale. @@ -236,7 +236,7 @@ private: int _renderMethod{ RENDER_FORWARD ? render::Args::RenderMethod::FORWARD : render::Args::RenderMethod::DEFERRED }; bool _shadowsEnabled{ true }; bool _ambientOcclusionEnabled{ false }; - AntialiasingConfig::Mode _antialiasingMode{ AntialiasingConfig::Mode::NONE }; + AntialiasingSetupConfig::Mode _antialiasingMode{ AntialiasingSetupConfig::Mode::NONE }; float _viewportResolutionScale{ 1.0f }; QString _fullScreenScreen; @@ -246,7 +246,7 @@ private: Setting::Handle _shadowsEnabledSetting { "shadowsEnabled", true }; Setting::Handle _ambientOcclusionEnabledSetting { "ambientOcclusionEnabled", false }; //Setting::Handle _antialiasingModeSetting { "antialiasingMode", AntialiasingConfig::Mode::TAA }; - Setting::Handle _antialiasingModeSetting { "antialiasingMode", AntialiasingConfig::Mode::NONE }; + Setting::Handle _antialiasingModeSetting { "antialiasingMode", AntialiasingSetupConfig::Mode::NONE }; Setting::Handle _viewportResolutionScaleSetting { "viewportResolutionScale", 1.0f }; Setting::Handle _fullScreenScreenSetting { "fullScreenScreen", "" }; @@ -254,7 +254,7 @@ private: void forceRenderMethod(RenderMethod renderMethod); void forceShadowsEnabled(bool enabled); void forceAmbientOcclusionEnabled(bool enabled); - void forceAntialiasingMode(AntialiasingConfig::Mode mode); + void forceAntialiasingMode(AntialiasingSetupConfig::Mode mode); void forceViewportResolutionScale(float scale); static std::once_flag registry_flag; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 03a463c82a..e4fa39e333 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -357,7 +357,7 @@ void OpenGLDisplayPlugin::customizeContext() { auto presentThread = DependencyManager::get(); Q_ASSERT(thread() == presentThread->thread()); - getGLBackend()->setCameraCorrection(mat4(), mat4(), true); + getGLBackend()->updatePresentFrame(mat4()); for (auto& cursorValue : _cursorsData) { auto& cursorData = cursorValue.second; @@ -701,8 +701,7 @@ void OpenGLDisplayPlugin::present(const std::shared_ptr& if (_currentFrame) { auto correction = getViewCorrection(); - getGLBackend()->setCameraCorrection(correction, _prevRenderView); - _prevRenderView = correction * _currentFrame->view; + getGLBackend()->updatePresentFrame(correction); { withPresentThreadLock([&] { _renderRate.increment(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 15cebf7e3a..239deb87a8 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -150,7 +150,6 @@ protected: gpu::FramePointer _currentFrame; gpu::Frame* _lastFrame{ nullptr }; - mat4 _prevRenderView; gpu::FramebufferPointer _compositeFramebuffer; gpu::PipelinePointer _hudPipeline; gpu::PipelinePointer _mirrorHUDPipeline; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 86ef9dfb54..7c40ff6594 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -157,6 +157,7 @@ protected: bool _cauterized { false }; bool _moving { false }; Transform _renderTransform; + Transform _prevRenderTransform; // each subclass is responsible for updating this after they render because they all handle transforms differently MaterialMap _materials; mutable std::mutex _materialsLock; diff --git a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp index 42df1e2888..2b7d45dc4c 100644 --- a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp @@ -1,6 +1,7 @@ // // Created by Sam Gondelman on 1/22/19 // Copyright 2019 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -265,7 +266,10 @@ void GizmoEntityRenderer::doRender(RenderArgs* args) { transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), true)); - batch.setModelTransform(transform); + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } Pipeline pipelineType = getPipelineType(materials); if (pipelineType == Pipeline::PROCEDURAL) { diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp index e374fe29c0..76ae8760be 100644 --- a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp @@ -105,7 +105,10 @@ void GridEntityRenderer::doRender(RenderArgs* args) { } transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); - batch->setModelTransform(transform); + batch->setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } auto minCorner = glm::vec2(-0.5f, -0.5f); auto maxCorner = glm::vec2(0.5f, 0.5f); @@ -120,4 +123,4 @@ void GridEntityRenderer::doRender(RenderArgs* args) { minorGridRowDivisions, minorGridColDivisions, MINOR_GRID_EDGE, majorGridRowDivisions, majorGridColDivisions, MAJOR_GRID_EDGE, color, forward, _geometryId); -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp index 13ddcffe6f..217c64f1fb 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp @@ -188,7 +188,10 @@ void ImageEntityRenderer::doRender(RenderArgs* args) { } transform.setScale(scale); } - batch->setModelTransform(transform); + batch->setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } Pipeline pipelineType = getPipelineType(materials); if (pipelineType == Pipeline::PROCEDURAL) { diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index a36cdde212..95fe17c986 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -4,6 +4,7 @@ // // Created by Seth Alves on 5/11/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -44,12 +45,17 @@ void LineEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderableLineEntityItem::render"); Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; + const auto& modelTransform = getModelTransform(); - Transform transform = Transform(); + Transform transform; transform.setTranslation(modelTransform.getTranslation()); transform.setRotation(BillboardModeHelpers::getBillboardRotation(modelTransform.getTranslation(), modelTransform.getRotation(), _billboardMode, args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); - batch.setModelTransform(transform); + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } + if (_linePoints.size() > 1) { DependencyManager::get()->bindSimpleProgram(batch, false, false, false, false, true, _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD); diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index b086f42d72..178ea21c09 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -327,7 +327,10 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) { transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); - batch.setModelTransform(transform); + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } if (!proceduralRender) { drawMaterial->setTextureTransforms(textureTransform, MaterialMappingMode::UV, true); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 8ed3f84609..f5e7550a27 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -4,6 +4,7 @@ // // Created by Brad Hefta-Gaub on 8/6/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -1474,7 +1475,11 @@ void ModelEntityRenderer::doRender(RenderArgs* args) { // If the model doesn't have visual geometry, render our bounding box as green wireframe static glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getModelTransform()); // we want to include the scale as well + Transform transform = getModelTransform(); + batch.setModelTransform(transform, _prevRenderTransform); // we want to include the scale as well + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } auto geometryCache = DependencyManager::get(); geometryCache->renderWireCubeInstance(args, batch, greenColor, geometryCache->getShapePipelinePointer(false, false, args->_renderMethod == Args::RenderMethod::FORWARD)); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index e2a57840d9..accaae0a1c 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -3,6 +3,7 @@ // interface/src // // Created by Jason Rickwald on 3/2/15. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -31,7 +32,7 @@ static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, co state->setDepthTest(true, false, gpu::LESS_EQUAL); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - PrepareStencil::testMask(*state); + PrepareStencil::testMaskResetNoAA(*state); auto program = gpu::Shader::createProgram(shader::entities_renderer::program::textured_particle); _texturedPipeline = texturedPipeline = gpu::Pipeline::create(program, state); @@ -455,7 +456,7 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { color.finish = EntityRenderer::calculatePulseColor(_particleProperties.getColorFinish(), _pulseProperties, _created); color.spread = EntityRenderer::calculatePulseColor(_particleProperties.getColorSpread(), _pulseProperties, _created); - batch.setModelTransform(transform); + batch.setModelTransform(transform); // particles are currently always transparent so we don't worry about TAA right now batch.setUniformBuffer(0, _uniformBuffer); batch.setInputFormat(_vertexFormat); @@ -589,4 +590,4 @@ void ParticleEffectEntityRenderer::computeTriangles(const hfm::Model& hfmModel) glm::vec3 scale = bounds.getScale(); _triangleInfo.transform = glm::scale(1.0f / scale) * glm::translate(-bounds.calcCenter()); -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index aca501985a..1e7e3f688b 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -4,6 +4,7 @@ // // Created by Eric Levin on 8/10/15 // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -330,6 +331,10 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) { batch.setModelTransform(transform); batch.setPipeline(_pipelines[{args->_renderMethod, isTransparent()}]); + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } batch.setResourceTexture(0, texture); batch.draw(gpu::TRIANGLE_STRIP, (gpu::uint32)(2 * _numVertices), 0); } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 8331e016fd..c7581f3ec1 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -4,6 +4,7 @@ // // Created by Seth Alves on 5/19/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. @@ -1863,7 +1864,10 @@ void PolyVoxEntityRenderer::doRender(RenderArgs* args) { glm::mat4 rotation = glm::mat4_cast(BillboardModeHelpers::getBillboardRotation(_position, _orientation, _billboardMode, args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); Transform transform(glm::translate(_position) * rotation * _lastVoxelToLocalMatrix); - batch.setModelTransform(transform); + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } batch.setInputFormat(_vertexFormat); batch.setInputBuffer(gpu::Stream::POSITION, _mesh->getVertexBuffer()._buffer, 0, diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 1355885625..7f73902eca 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2016/05/09 // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -111,8 +112,13 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { auto geometryCache = DependencyManager::get(); GeometryCache::Shape geometryShape = geometryCache->getShapeForEntityShape(_shape); Transform transform; + Transform prevTransform; withReadLock([&] { transform = _renderTransform; + prevTransform = _prevRenderTransform; + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = _renderTransform; + } }); bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES; @@ -120,7 +126,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), _shape < entity::Shape::Cube || _shape > entity::Shape::Icosahedron)); - batch.setModelTransform(transform); + batch.setModelTransform(transform, prevTransform); Pipeline pipelineType = getPipelineType(materials); if (pipelineType == Pipeline::PROCEDURAL) { diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 5b790d6e60..7769e1ef68 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -4,6 +4,8 @@ // // Created by Brad Hefta-Gaub on 8/6/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -158,14 +160,19 @@ void TextEntityRenderer::doRender(RenderArgs* args) { bool transparent; Transform transform; + Transform prevTransform; withReadLock([&] { transparent = isTransparent(); transform = _renderTransform; + prevTransform = _prevRenderTransform; + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } }); - transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); - batch.setModelTransform(transform); + batch.setModelTransform(transform, prevTransform); Pipeline pipelineType = getPipelineType(materials); if (pipelineType == Pipeline::PROCEDURAL) { @@ -357,7 +364,10 @@ void entities::TextPayload::render(RenderArgs* args) { float scale = textRenderable->_lineHeight / textRenderer->getFontSize(); transform.postTranslate(glm::vec3(-0.5, 0.5, 1.0f + EPSILON / dimensions.z)); transform.setScale(scale); - batch.setModelTransform(transform); + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } glm::vec2 bounds = glm::vec2(dimensions.x - (textRenderable->_leftMargin + textRenderable->_rightMargin), dimensions.y - (textRenderable->_topMargin + textRenderable->_bottomMargin)); textRenderer->draw(batch, textRenderable->_leftMargin / scale, -textRenderable->_topMargin / scale, bounds / scale, scale, diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index 8a18554dea..df256a5cd0 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -4,6 +4,7 @@ // // Created by Brad Hefta-Gaub on 8/6/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. @@ -105,6 +106,7 @@ public: protected: QUuid _entityID; std::weak_ptr _textRenderer; + Transform _prevRenderTransform; int _geometryID { 0 }; }; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index c98bfe7f63..cd2f8b25f8 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -322,13 +322,16 @@ void WebEntityRenderer::doRender(RenderArgs* args) { transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); - batch.setModelTransform(transform); + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } // Turn off jitter for these entities - batch.pushProjectionJitter(); + batch.pushProjectionJitterEnabled(false); DependencyManager::get()->bindWebBrowserProgram(batch, transparent, forward); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, color, _geometryId); - batch.popProjectionJitter(); + batch.popProjectionJitterEnabled(); batch.setResourceTexture(0, nullptr); } diff --git a/libraries/entities-renderer/src/paintStroke.slf b/libraries/entities-renderer/src/paintStroke.slf index eb46be1e20..4b73d50bd7 100644 --- a/libraries/entities-renderer/src/paintStroke.slf +++ b/libraries/entities-renderer/src/paintStroke.slf @@ -5,6 +5,7 @@ // // Created by Eric Levin on 8/10/2015 // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -23,12 +24,15 @@ LAYOUT(binding=0) uniform sampler2D _texture; +<@include render-utils/ShaderConstants.h@> + <@if not HIFI_USE_FORWARD@> - layout(location=0) in vec3 _normalWS; + layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) in vec4 _prevPositionCS; <@endif@> -layout(location=1) in vec2 _texCoord; -layout(location=2) in vec4 _color; -layout(location=3) in float _distanceFromCenter; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec2 _texCoord; +layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; +layout(location=2) in float _distanceFromCenter; void main(void) { vec4 texel = texture(_texture, _texCoord); @@ -37,9 +41,9 @@ void main(void) { <@if not HIFI_USE_FORWARD@> <@if HIFI_USE_TRANSLUCENT@> - packDeferredFragmentTranslucent(evalFrontOrBackFaceNormal(_normalWS), texel.a, texel.rgb, DEFAULT_ROUGHNESS); + packDeferredFragmentTranslucentUnlit(_prevPositionCS, evalFrontOrBackFaceNormal(_normalWS), texel.a, texel.rgb); <@else@> - packDeferredFragmentUnlit(evalFrontOrBackFaceNormal(_normalWS), texel.a, texel.rgb); + packDeferredFragmentUnlit(_prevPositionCS, evalFrontOrBackFaceNormal(_normalWS), texel.a, texel.rgb); <@endif@> <@else@> _fragColor0 = texel; diff --git a/libraries/entities-renderer/src/paintStroke.slv b/libraries/entities-renderer/src/paintStroke.slv index cf91438746..23315cbc2d 100644 --- a/libraries/entities-renderer/src/paintStroke.slv +++ b/libraries/entities-renderer/src/paintStroke.slv @@ -5,6 +5,7 @@ // // Created by Eric Levin on 7/20/15. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -12,18 +13,22 @@ <@include gpu/Inputs.slh@> <@include gpu/Color.slh@> + <@include gpu/Transform.slh@> <$declareStandardTransform()$> <@include paintStroke.slh@> <$declarePolyLineBuffers()$> +<@include render-utils/ShaderConstants.h@> + <@if not HIFI_USE_FORWARD@> - layout(location=0) out vec3 _normalWS; + layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) out vec4 _prevPositionCS; <@endif@> -layout(location=1) out vec2 _texCoord; -layout(location=2) out vec4 _color; -layout(location=3) out float _distanceFromCenter; +layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec2 _texCoord; +layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; +layout(location=2) out float _distanceFromCenter; void main(void) { PolylineVertex vertex = getPolylineVertex(gl_VertexID / 2); @@ -54,14 +59,17 @@ void main(void) { posEye.z += _distanceFromCenter * vertex.binormalAndHalfWidth.w * binormalEye.z; <$transformEyeToClipPos(cam, posEye, gl_Position)$> <@if not HIFI_USE_FORWARD@> + <$transformEyeToPrevClipPos(cam, posEye, _prevPositionCS)$> <$transformEyeToWorldDir(cam, normalEye, _normalWS)$> <@endif@> } else { vec3 normal = vertex.normal.xyz; position.xyz += _distanceFromCenter * vertex.binormalAndHalfWidth.w * binormal; +<@if HIFI_USE_FORWARD@> <$transformModelToClipPos(cam, obj, position, gl_Position)$> -<@if not HIFI_USE_FORWARD@> +<@else@> + <$transformModelToClipPosAndPrevClipPos(cam, obj, position, gl_Position, _prevPositionCS)$> <$transformModelToWorldDir(cam, obj, normal, _normalWS)$> <@endif@> } -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index 3456823081..dd735ecf16 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -36,6 +36,8 @@ <@if HIFI_USE_FORWARD@> layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; + <@else@> + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) in vec4 _prevPositionCS; <@endif@> layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec3 _positionMS; layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; @@ -88,6 +90,7 @@ void main(void) { <@if not HIFI_USE_FORWARD@> packDeferredFragment( + _prevPositionCS, normalize(_normalWS), 1.0, diffuse, diff --git a/libraries/entities-renderer/src/polyvox.slv b/libraries/entities-renderer/src/polyvox.slv index 34547cef1a..42ee508bfd 100644 --- a/libraries/entities-renderer/src/polyvox.slv +++ b/libraries/entities-renderer/src/polyvox.slv @@ -23,6 +23,7 @@ layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; <@endif@> layout(location=RENDER_UTILS_ATTR_POSITION_MS) out vec3 _positionMS; + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) out vec4 _prevPositionCS; layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; <@endif@> @@ -34,7 +35,7 @@ void main(void) { <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> <@else@> <@if not HIFI_USE_FORWARD@> - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToClipPosAndPrevClipPos(cam, obj, inPosition, gl_Position, _prevPositionCS)$> <@else@> <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> <@endif@> diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv index 9fd5e87d32..e66b7d0302 100644 --- a/libraries/entities-renderer/src/textured_particle.slv +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -5,6 +5,7 @@ // texture_particle.vert // // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index af39458f17..e80279fa87 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 10/27/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -50,10 +51,16 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_setModelTransform), (&::gpu::gl::GLBackend::do_setViewTransform), (&::gpu::gl::GLBackend::do_setProjectionTransform), - (&::gpu::gl::GLBackend::do_setProjectionJitter), + (&::gpu::gl::GLBackend::do_setProjectionJitterEnabled), + (&::gpu::gl::GLBackend::do_setProjectionJitterSequence), + (&::gpu::gl::GLBackend::do_setProjectionJitterScale), (&::gpu::gl::GLBackend::do_setViewportTransform), (&::gpu::gl::GLBackend::do_setDepthRangeTransform), + (&::gpu::gl::GLBackend::do_saveViewProjectionTransform), + (&::gpu::gl::GLBackend::do_setSavedViewProjectionTransform), + (&::gpu::gl::GLBackend::do_copySavedViewProjectionTransformToBuffer), + (&::gpu::gl::GLBackend::do_setPipeline), (&::gpu::gl::GLBackend::do_setStateBlendFactor), (&::gpu::gl::GLBackend::do_setStateScissorRect), @@ -269,12 +276,10 @@ bool GLBackend::availableMemoryKnown() { } GLBackend::GLBackend(bool syncCache) { - _pipeline._cameraCorrectionBuffer._buffer->flush(); initShaderBinaryCache(); } GLBackend::GLBackend() { - _pipeline._cameraCorrectionBuffer._buffer->flush(); initShaderBinaryCache(); } @@ -320,19 +325,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) { case Batch::COMMAND_drawIndexedInstanced: case Batch::COMMAND_multiDrawIndirect: case Batch::COMMAND_multiDrawIndexedIndirect: - { - Vec2u outputSize{ 1,1 }; - - auto framebuffer = acquire(_output._framebuffer); - if (framebuffer) { - outputSize.x = framebuffer->getWidth(); - outputSize.y = framebuffer->getHeight(); - } else if (glm::dot(_transform._projectionJitter, _transform._projectionJitter)>0.0f) { - qCWarning(gpugllogging) << "Jittering needs to have a frame buffer to be set"; - } - - _transform.preUpdate(_commandIndex, _stereo, outputSize); - } + preUpdateTransform(); break; case Batch::COMMAND_disableContextStereo: @@ -343,11 +336,20 @@ void GLBackend::renderPassTransfer(const Batch& batch) { _stereo._contextDisable = false; break; + case Batch::COMMAND_copySavedViewProjectionTransformToBuffer: + // We need to store this transform state in the transform buffer + preUpdateTransform(); + break; + case Batch::COMMAND_setFramebuffer: case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setViewTransform: case Batch::COMMAND_setProjectionTransform: - case Batch::COMMAND_setProjectionJitter: + case Batch::COMMAND_setProjectionJitterEnabled: + case Batch::COMMAND_setProjectionJitterSequence: + case Batch::COMMAND_setProjectionJitterScale: + case Batch::COMMAND_saveViewProjectionTransform: + case Batch::COMMAND_setSavedViewProjectionTransform: { CommandCall call = _commandCalls[(*command)]; (this->*(call))(batch, *offset); @@ -385,6 +387,9 @@ void GLBackend::renderPassDraw(const Batch& batch) { case Batch::COMMAND_setModelTransform: case Batch::COMMAND_setViewTransform: case Batch::COMMAND_setProjectionTransform: + case Batch::COMMAND_saveViewProjectionTransform: + case Batch::COMMAND_setSavedViewProjectionTransform: + case Batch::COMMAND_setProjectionJitterSequence: break; case Batch::COMMAND_draw: @@ -410,7 +415,6 @@ void GLBackend::renderPassDraw(const Batch& batch) { //case Batch::COMMAND_setModelTransform: //case Batch::COMMAND_setViewTransform: //case Batch::COMMAND_setProjectionTransform: - case Batch::COMMAND_setProjectionJitter: case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setDepthRangeTransform: { @@ -554,7 +558,7 @@ void GLBackend::render(const Batch& batch) { _stereo._enable = false; } // Reset jitter - _transform._projectionJitter = Vec2(0.0f, 0.0f); + _transform._projectionJitter._isEnabled = false; { GL_PROFILE_RANGE(render_gpu_gl_detail, "Transfer"); @@ -578,6 +582,14 @@ void GLBackend::render(const Batch& batch) { // Restore the saved stereo state for the next batch _stereo._enable = savedStereo; + + if (batch._mustUpdatePreviousModels) { + // Update object transform history for when the batch will be reexecuted + for (auto& objectTransform : batch._objects) { + objectTransform._previousModel = objectTransform._model; + } + batch._mustUpdatePreviousModels = false; + } } @@ -997,15 +1009,17 @@ void GLBackend::recycle() const { _textureManagement._transferEngine->manageMemory(); } -void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset) { - auto invCorrection = glm::inverse(correction); - auto invPrevView = glm::inverse(prevRenderView); - _transform._correction.prevView = (reset ? Mat4() : prevRenderView); - _transform._correction.prevViewInverse = (reset ? Mat4() : invPrevView); - _transform._correction.correction = correction; - _transform._correction.correctionInverse = invCorrection; - _pipeline._cameraCorrectionBuffer._buffer->setSubData(0, _transform._correction); - _pipeline._cameraCorrectionBuffer._buffer->flush(); +void GLBackend::updatePresentFrame(const Mat4& correction) { + _transform._presentFrame.correction = correction; + _transform._presentFrame.correctionInverse = glm::inverse(correction); + + _transform._projectionJitter._currentSampleIndex++; + + // Update previous views of saved transforms + for (auto& viewProjState : _transform._savedTransforms) { + viewProjState._state._previousCorrectedView = viewProjState._state._correctedView; + viewProjState._state._previousProjection = viewProjState._state._projection; + } } void GLBackend::syncProgram(const gpu::ShaderPointer& program) { diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 2947649ce7..8d07b069d6 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 10/27/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -26,7 +27,7 @@ #include #include -#include +#include #include "GLShared.h" @@ -121,7 +122,8 @@ public: // Shutdown rendering and persist any required resources void shutdown() override; - void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false) override; + void updatePresentFrame(const Mat4& correction = Mat4()) override; + void render(const Batch& batch) final override; // This call synchronize the Full Backend cache with the current GLState @@ -177,10 +179,16 @@ public: virtual void do_setModelTransform(const Batch& batch, size_t paramOffset) final; virtual void do_setViewTransform(const Batch& batch, size_t paramOffset) final; virtual void do_setProjectionTransform(const Batch& batch, size_t paramOffset) final; - virtual void do_setProjectionJitter(const Batch& batch, size_t paramOffset) final; + virtual void do_setProjectionJitterEnabled(const Batch& batch, size_t paramOffset) final; + virtual void do_setProjectionJitterSequence(const Batch& batch, size_t paramOffset) final; + virtual void do_setProjectionJitterScale(const Batch& batch, size_t paramOffset) final; virtual void do_setViewportTransform(const Batch& batch, size_t paramOffset) final; virtual void do_setDepthRangeTransform(const Batch& batch, size_t paramOffset) final; + virtual void do_saveViewProjectionTransform(const Batch& batch, size_t paramOffset) final; + virtual void do_setSavedViewProjectionTransform(const Batch& batch, size_t paramOffset) final; + virtual void do_copySavedViewProjectionTransformToBuffer(const Batch& batch, size_t paramOffset) = 0; + // Uniform Stage virtual void do_setUniformBuffer(const Batch& batch, size_t paramOffset) final; @@ -300,8 +308,9 @@ protected: virtual bool supportsBindless() const { return false; } static const size_t INVALID_OFFSET = (size_t)-1; - bool _inRenderTransferPass{ false }; - int _currentDraw{ -1 }; + static const uint INVALID_SAVED_CAMERA_SLOT = (uint)-1; + bool _inRenderTransferPass { false }; + int _currentDraw { -1 }; struct FrameTrash { GLsync fence = nullptr; @@ -392,11 +401,9 @@ protected: // between the time when a was recorded and the time(s) when it is // executed // Prev is the previous correction used at previous frame - struct CameraCorrection { + struct PresentFrame { mat4 correction; mat4 correctionInverse; - mat4 prevView; - mat4 prevViewInverse; }; struct TransformStageState { @@ -417,32 +424,61 @@ protected: using CameraBufferElement = TransformCamera; #endif using TransformCameras = std::vector; + + struct ViewProjectionState { + Transform _view; + Transform _correctedView; + Transform _previousCorrectedView; + Mat4 _projection; + Mat4 _previousProjection; + bool _viewIsCamera; + + void copyExceptPrevious(const ViewProjectionState& other) { + _view = other._view; + _correctedView = other._correctedView; + _projection = other._projection; + _viewIsCamera = other._viewIsCamera; + } + }; + + struct SaveTransform { + ViewProjectionState _state; + size_t _cameraOffset { INVALID_OFFSET }; + }; TransformCamera _camera; TransformCameras _cameras; + std::array _savedTransforms; mutable std::map _drawCallInfoOffsets; - GLuint _objectBuffer{ 0 }; - GLuint _cameraBuffer{ 0 }; - GLuint _drawCallInfoBuffer{ 0 }; - GLuint _objectBufferTexture{ 0 }; - size_t _cameraUboSize{ 0 }; - bool _viewIsCamera{ false }; - bool _skybox{ false }; - Transform _view; - CameraCorrection _correction; - bool _viewCorrectionEnabled{ true }; + GLuint _objectBuffer { 0 }; + GLuint _cameraBuffer { 0 }; + GLuint _drawCallInfoBuffer { 0 }; + GLuint _objectBufferTexture { 0 }; + size_t _cameraUboSize { 0 }; + ViewProjectionState _viewProjectionState; + uint _currentSavedTransformSlot { INVALID_SAVED_CAMERA_SLOT }; + bool _skybox { false }; + PresentFrame _presentFrame; + bool _viewCorrectionEnabled { true }; - Mat4 _projection; - Vec4i _viewport{ 0, 0, 1, 1 }; - Vec2 _depthRange{ 0.0f, 1.0f }; - Vec2 _projectionJitter{ 0.0f, 0.0f }; - bool _invalidView{ false }; - bool _invalidProj{ false }; - bool _invalidViewport{ false }; + struct Jitter { + std::vector _offsetSequence; + Vec2 _offset { 0.0f }; + float _scale { 0.f }; + unsigned int _currentSampleIndex { 0 }; + bool _isEnabled { false }; + }; - bool _enabledDrawcallInfoBuffer{ false }; + Jitter _projectionJitter; + Vec4i _viewport { 0, 0, 1, 1 }; + Vec2 _depthRange { 0.0f, 1.0f }; + bool _invalidView { false }; + bool _invalidProj { false }; + bool _invalidViewport { false }; + + bool _enabledDrawcallInfoBuffer { false }; using Pair = std::pair; using List = std::list; @@ -450,11 +486,13 @@ protected: mutable List::const_iterator _camerasItr; mutable size_t _currentCameraOffset{ INVALID_OFFSET }; - void preUpdate(size_t commandIndex, const StereoState& stereo, Vec2u framebufferSize); + void pushCameraBufferElement(const StereoState& stereo, const StereoState& prevStereo, TransformCameras& cameras) const; + void preUpdate(size_t commandIndex, const StereoState& stereo, const StereoState& prevStereo); void update(size_t commandIndex, const StereoState& stereo) const; void bindCurrentCamera(int stereoSide) const; } _transform; + void preUpdateTransform(); virtual void transferTransformState(const Batch& batch) const = 0; struct UniformStageState { @@ -524,25 +562,16 @@ protected: PipelineReference _pipeline{}; GLuint _program{ 0 }; - bool _cameraCorrection{ false }; - GLShader* _programShader{ nullptr }; - bool _invalidProgram{ false }; + GLShader* _programShader { nullptr }; + bool _invalidProgram { false }; - BufferView _cameraCorrectionBuffer{ gpu::BufferView(std::make_shared(sizeof(CameraCorrection), nullptr)) }; - BufferView _cameraCorrectionBufferIdentity{ gpu::BufferView( - std::make_shared(sizeof(CameraCorrection), nullptr)) }; + State::Data _stateCache { State::DEFAULT }; + State::Signature _stateSignatureCache { 0 }; - State::Data _stateCache{ State::DEFAULT }; - State::Signature _stateSignatureCache{ 0 }; + GLState* _state { nullptr }; + bool _invalidState { false }; - GLState* _state{ nullptr }; - bool _invalidState{ false }; - - PipelineStageState() { - _cameraCorrectionBuffer.edit() = CameraCorrection(); - _cameraCorrectionBufferIdentity.edit() = CameraCorrection(); - _cameraCorrectionBufferIdentity._buffer->flush(); - } + PipelineStageState() {} } _pipeline; // Backend dependent compilation of the shader diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index e94d2986ee..45db4ba5fb 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -37,7 +37,6 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { reset(_pipeline._pipeline); _pipeline._program = 0; - _pipeline._cameraCorrection = false; _pipeline._programShader = nullptr; _pipeline._invalidProgram = true; @@ -63,7 +62,6 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { _pipeline._program = glprogram; _pipeline._programShader = pipelineObject->_program; _pipeline._invalidProgram = true; - _pipeline._cameraCorrection = pipelineObject->_cameraCorrection; } // Now for the state @@ -79,16 +77,6 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { // THis should be done on Pipeline::update... if (_pipeline._invalidProgram) { glUseProgram(_pipeline._program); - if (_pipeline._cameraCorrection) { - // Invalidate uniform buffer cache slot - _uniform._buffers[gpu::slot::buffer::CameraCorrection].reset(); - auto& cameraCorrectionBuffer = _transform._viewCorrectionEnabled ? - _pipeline._cameraCorrectionBuffer._buffer : - _pipeline._cameraCorrectionBufferIdentity._buffer; - // Because we don't sync Buffers in the bindUniformBuffer, let s force this buffer synced - getBufferID(*cameraCorrectionBuffer); - bindUniformBuffer(gpu::slot::buffer::CameraCorrection, cameraCorrectionBuffer, 0, sizeof(CameraCorrection)); - } (void)CHECK_GL_ERROR(); _pipeline._invalidProgram = false; } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp index 67ab502b6b..0f07fde876 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 3/8/2015. // Copyright 2014 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -18,20 +19,48 @@ void GLBackend::do_setModelTransform(const Batch& batch, size_t paramOffset) { } void GLBackend::do_setViewTransform(const Batch& batch, size_t paramOffset) { - _transform._view = batch._transforms.get(batch._params[paramOffset]._uint); - _transform._viewIsCamera = batch._params[paramOffset + 1]._uint != 0; + _transform._viewProjectionState._view = batch._transforms.get(batch._params[paramOffset]._uint); + // View history is only supported with saved transforms and if setViewTransform is called (and not setSavedViewProjectionTransform) + // then, in consequence, the view will NOT be corrected in the present thread. In which case + // the previousCorrectedView should be the same as the view. + _transform._viewProjectionState._previousCorrectedView = _transform._viewProjectionState._view; + _transform._viewProjectionState._previousProjection = _transform._viewProjectionState._projection; + _transform._viewProjectionState._viewIsCamera = batch._params[paramOffset + 1]._uint != 0; _transform._invalidView = true; + // The current view / proj doesn't correspond to a saved camera slot + _transform._currentSavedTransformSlot = INVALID_SAVED_CAMERA_SLOT; } void GLBackend::do_setProjectionTransform(const Batch& batch, size_t paramOffset) { - memcpy(glm::value_ptr(_transform._projection), batch.readData(batch._params[paramOffset]._uint), sizeof(Mat4)); + memcpy(glm::value_ptr(_transform._viewProjectionState._projection), batch.readData(batch._params[paramOffset]._uint), sizeof(Mat4)); _transform._invalidProj = true; + // The current view / proj doesn't correspond to a saved camera slot + _transform._currentSavedTransformSlot = INVALID_SAVED_CAMERA_SLOT; } -void GLBackend::do_setProjectionJitter(const Batch& batch, size_t paramOffset) { - _transform._projectionJitter.x = batch._params[paramOffset]._float; - _transform._projectionJitter.y = batch._params[paramOffset+1]._float; +void GLBackend::do_setProjectionJitterEnabled(const Batch& batch, size_t paramOffset) { + _transform._projectionJitter._isEnabled = (batch._params[paramOffset]._int & 1) != 0; _transform._invalidProj = true; + // The current view / proj doesn't correspond to a saved camera slot + _transform._currentSavedTransformSlot = INVALID_SAVED_CAMERA_SLOT; +} + +void GLBackend::do_setProjectionJitterSequence(const Batch& batch, size_t paramOffset) { + auto count = batch._params[paramOffset + 0]._uint; + auto& projectionJitter = _transform._projectionJitter; + projectionJitter._offsetSequence.resize(count); + if (count) { + memcpy(projectionJitter._offsetSequence.data(), batch.readData(batch._params[paramOffset + 1]._uint), sizeof(Vec2) * count); + projectionJitter._offset = projectionJitter._offsetSequence[projectionJitter._currentSampleIndex % count]; + } else { + projectionJitter._offset = Vec2(0.0f); + } +} + +void GLBackend::do_setProjectionJitterScale(const Batch& batch, size_t paramOffset) { + // Should be 2 for one pixel amplitude as clip space is between -1 and 1, but lower values give less blur + // but more aliasing... + _transform._projectionJitter._scale = 2.0f * batch._params[paramOffset + 0]._float; } void GLBackend::do_setViewportTransform(const Batch& batch, size_t paramOffset) { @@ -90,55 +119,80 @@ void GLBackend::syncTransformStateCache() { Mat4 modelView; auto modelViewInv = glm::inverse(modelView); - _transform._view.evalFromRawMatrix(modelViewInv); + _transform._viewProjectionState._view.evalFromRawMatrix(modelViewInv); glDisableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); _transform._enabledDrawcallInfoBuffer = false; } -void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const StereoState& stereo, Vec2u framebufferSize) { +void GLBackend::TransformStageState::pushCameraBufferElement(const StereoState& stereo, const StereoState& prevStereo, TransformCameras& cameras) const { + const float jitterAmplitude = _projectionJitter._scale; + const Vec2 jitterScale = Vec2(jitterAmplitude * float(_projectionJitter._isEnabled & 1)) / Vec2(_viewport.z, _viewport.w); + const Vec2 jitter = jitterScale * _projectionJitter._offset; + + if (stereo.isStereo()) { +#ifdef GPU_STEREO_CAMERA_BUFFER + cameras.push_back(CameraBufferElement(_camera.getEyeCamera(0, stereo, prevStereo, _viewProjectionState._correctedView, + _viewProjectionState._previousCorrectedView, jitter), + _camera.getEyeCamera(1, stereo, prevStereo, _viewProjectionState._correctedView, + _viewProjectionState._previousCorrectedView, jitter))); +#else + cameras.push_back((_camera.getEyeCamera(0, stereo, prevStereo, _viewProjectionState._correctedView, + _viewProjectionState._previousCorrectedView, jitter))); + cameras.push_back((_camera.getEyeCamera(1, stereo, prevStereo, _viewProjectionState._correctedView, + _viewProjectionState._previousCorrectedView, jitter))); +#endif + } else { +#ifdef GPU_STEREO_CAMERA_BUFFER + cameras.push_back(CameraBufferElement( + _camera.getMonoCamera(_skybox, _viewProjectionState._correctedView, _viewProjectionState._previousCorrectedView, + _viewProjectionState._previousProjection, jitter))); +#else + cameras.push_back((_camera.getMonoCamera(_skybox, _viewProjectionState._correctedView, + _viewProjectionState._previousCorrectedView, _viewProjectionState._previousProjection, + jitter))); +#endif + } +} + +void GLBackend::preUpdateTransform() { + _transform.preUpdate(_commandIndex, _stereo, _prevStereo); +} + +void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const StereoState& stereo, const StereoState& prevStereo) { // Check all the dirty flags and update the state accordingly if (_invalidViewport) { _camera._viewport = glm::vec4(_viewport); } if (_invalidProj) { - _camera._projection = _projection; + _camera._projection = _viewProjectionState._projection; } if (_invalidView) { // Apply the correction - if (_viewIsCamera && (_viewCorrectionEnabled && _correction.correction != glm::mat4())) { - // FIXME should I switch to using the camera correction buffer in Transform.slf and leave this out? - Transform result; - _view.mult(result, _view, _correction.correctionInverse); - if (_skybox) { - result.setTranslation(vec3()); - } - _view = result; + if (_viewProjectionState._viewIsCamera && (_viewCorrectionEnabled && _presentFrame.correction != glm::mat4())) { + Transform::mult(_viewProjectionState._correctedView, _viewProjectionState._view, _presentFrame.correctionInverse); + } else { + _viewProjectionState._correctedView = _viewProjectionState._view; + } + + if (_skybox) { + _viewProjectionState._correctedView.setTranslation(vec3()); } // This is when the _view matrix gets assigned - _view.getInverseMatrix(_camera._view); + _viewProjectionState._correctedView.getInverseMatrix(_camera._view); } if (_invalidView || _invalidProj || _invalidViewport) { size_t offset = _cameraUboSize * _cameras.size(); - Vec2 finalJitter = _projectionJitter / Vec2(framebufferSize); _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); - if (stereo.isStereo()) { -#ifdef GPU_STEREO_CAMERA_BUFFER - _cameras.push_back(CameraBufferElement(_camera.getEyeCamera(0, stereo, _view, finalJitter), _camera.getEyeCamera(1, stereo, _view, finalJitter))); -#else - _cameras.push_back((_camera.getEyeCamera(0, stereo, _view, finalJitter))); - _cameras.push_back((_camera.getEyeCamera(1, stereo, _view, finalJitter))); -#endif - } else { -#ifdef GPU_STEREO_CAMERA_BUFFER - _cameras.push_back(CameraBufferElement(_camera.getMonoCamera(_view, finalJitter))); -#else - _cameras.push_back((_camera.getMonoCamera(_view, finalJitter))); -#endif + pushCameraBufferElement(stereo, prevStereo, _cameras); + if (_currentSavedTransformSlot != INVALID_SAVED_CAMERA_SLOT) { + // Save the offset of the saved camera slot in the camera buffer. Can be used to copy + // that data, or (in the future) to reuse the offset. + _savedTransforms[_currentSavedTransformSlot]._cameraOffset = offset; } } @@ -177,3 +231,28 @@ void GLBackend::resetTransformStage() { glDisableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); _transform._enabledDrawcallInfoBuffer = false; } + +void GLBackend::do_saveViewProjectionTransform(const Batch& batch, size_t paramOffset) { + auto slotId = batch._params[paramOffset + 0]._uint; + slotId = std::min(slotId, gpu::Batch::MAX_TRANSFORM_SAVE_SLOT_COUNT); + + auto& savedTransform = _transform._savedTransforms[slotId]; + savedTransform._cameraOffset = INVALID_OFFSET; + _transform._currentSavedTransformSlot = slotId; + // If we are saving this transform to a save slot, then it means we are tracking the history of the view + // so copy the previous corrected view to the transform state. + _transform._viewProjectionState._previousCorrectedView = savedTransform._state._previousCorrectedView; + _transform._viewProjectionState._previousProjection = savedTransform._state._previousProjection; + preUpdateTransform(); + savedTransform._state.copyExceptPrevious(_transform._viewProjectionState); +} + +void GLBackend::do_setSavedViewProjectionTransform(const Batch& batch, size_t paramOffset) { + auto slotId = batch._params[paramOffset + 0]._uint; + slotId = std::min(slotId, gpu::Batch::MAX_TRANSFORM_SAVE_SLOT_COUNT); + + _transform._viewProjectionState = _transform._savedTransforms[slotId]._state; + _transform._invalidView = true; + _transform._invalidProj = true; + _transform._currentSavedTransformSlot = slotId; +} diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp index 52e10eb417..d4786a5a8f 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp @@ -49,11 +49,6 @@ GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) { Backend::setGPUObject(pipeline, object); } - // Special case for view correction matrices, any pipeline that declares the correction buffer - // uniform will automatically have it provided without any client code necessary. - // Required for stable lighting in the HMD. - auto reflection = shader->getReflection(backend.getShaderDialect(), backend.getShaderVariant()); - object->_cameraCorrection = reflection.validUniformBuffer(gpu::slot::buffer::CameraCorrection); object->_program = programObject; object->_state = stateObject; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h index a102e33b14..1634483323 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h @@ -18,9 +18,6 @@ public: GLShader* _program { nullptr }; GLState* _state { nullptr }; - // Bit of a hack, any pipeline can need the camera correction buffer at execution time, so - // we store whether a given pipeline has declared the uniform buffer for it. - bool _cameraCorrection{ false }; }; } } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index 881487c9db..49ef58ab00 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -167,6 +167,8 @@ protected: bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override; void releaseResourceBuffer(uint32_t slot) override; + void do_copySavedViewProjectionTransformToBuffer(const Batch& batch, size_t paramOffset) override; + // Output stage void do_blit(const Batch& batch, size_t paramOffset) override; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp index b11707eba2..6d7e108194 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp @@ -10,6 +10,8 @@ // #include "GL41Backend.h" +#include "gpu/gl/GLBuffer.h" + using namespace gpu; using namespace gpu::gl41; @@ -97,4 +99,34 @@ void GL41Backend::updateTransform(const Batch& batch) { } (void)CHECK_GL_ERROR(); -} \ No newline at end of file +} + +void GL41Backend::do_copySavedViewProjectionTransformToBuffer(const Batch& batch, size_t paramOffset) { + auto slotId = batch._params[paramOffset + 0]._uint; + BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 1]._uint); + auto dstOffset = batch._params[paramOffset + 2]._uint; + size_t size = _transform._cameraUboSize; + + slotId = std::min(slotId, gpu::Batch::MAX_TRANSFORM_SAVE_SLOT_COUNT); + const auto& savedTransform = _transform._savedTransforms[slotId]; + + if ((dstOffset + size) > buffer->getBufferCPUMemSize()) { + qCWarning(gpugllogging) << "Copying saved TransformCamera data out of bounds of uniform buffer"; + size = (size_t)std::max((ptrdiff_t)buffer->getBufferCPUMemSize() - (ptrdiff_t)dstOffset, 0); + } + if (savedTransform._cameraOffset == INVALID_OFFSET) { + qCWarning(gpugllogging) << "Saved TransformCamera data has an invalid transform offset. Copy aborted."; + return; + } + + // Sync BufferObject + auto* object = syncGPUObject(*buffer); + if (object) { + glBindBuffer(GL_COPY_READ_BUFFER, _transform._cameraBuffer); + glBindBuffer(GL_COPY_WRITE_BUFFER, object->_buffer); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, savedTransform._cameraOffset, dstOffset, size); + glBindBuffer(GL_COPY_READ_BUFFER, 0); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + (void)CHECK_GL_ERROR(); + } +} diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 3e7392e366..c8b0a84605 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -269,6 +269,8 @@ protected: bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override; void releaseResourceBuffer(uint32_t slot) override; + void do_copySavedViewProjectionTransformToBuffer(const Batch& batch, size_t paramOffset) override; + // Output stage void do_blit(const Batch& batch, size_t paramOffset) override; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index bb31903d8e..f1fa282720 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -167,7 +167,7 @@ public: glSamplerParameteri(result, GL_TEXTURE_WRAP_T, GLTexture::WRAP_MODES[sampler.getWrapModeV()]); glSamplerParameteri(result, GL_TEXTURE_WRAP_R, GLTexture::WRAP_MODES[sampler.getWrapModeW()]); - glSamplerParameterf(result, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); + glSamplerParameterf(result, GL_TEXTURE_MAX_ANISOTROPY, sampler.getMaxAnisotropy()); glSamplerParameterfv(result, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); glSamplerParameterf(result, GL_TEXTURE_MIN_LOD, sampler.getMinMip()); @@ -314,7 +314,7 @@ void GL45Texture::syncSampler() const { glTextureParameteri(_id, GL_TEXTURE_WRAP_T, WRAP_MODES[sampler.getWrapModeV()]); glTextureParameteri(_id, GL_TEXTURE_WRAP_R, WRAP_MODES[sampler.getWrapModeW()]); - glTextureParameterf(_id, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); + glTextureParameterf(_id, GL_TEXTURE_MAX_ANISOTROPY, sampler.getMaxAnisotropy()); glTextureParameterfv(_id, GL_TEXTURE_BORDER_COLOR, (const float*)&sampler.getBorderColor()); glTextureParameterf(_id, GL_TEXTURE_MIN_LOD, sampler.getMinMip()); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp index f389c5f62c..644ec6ae8e 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp @@ -10,6 +10,8 @@ // #include "GL45Backend.h" +#include "gpu/gl/GLBuffer.h" + using namespace gpu; using namespace gpu::gl45; @@ -101,4 +103,30 @@ void GL45Backend::updateTransform(const Batch& batch) { } (void)CHECK_GL_ERROR(); -} \ No newline at end of file +} + +void GL45Backend::do_copySavedViewProjectionTransformToBuffer(const Batch& batch, size_t paramOffset) { + auto slotId = batch._params[paramOffset + 0]._uint; + BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 1]._uint); + auto dstOffset = batch._params[paramOffset + 2]._uint; + size_t size = _transform._cameraUboSize; + + slotId = std::min(slotId, gpu::Batch::MAX_TRANSFORM_SAVE_SLOT_COUNT); + const auto& savedTransform = _transform._savedTransforms[slotId]; + + if ((dstOffset + size) > buffer->getBufferCPUMemSize()) { + qCWarning(gpugllogging) << "Copying saved TransformCamera data out of bounds of uniform buffer"; + size = (size_t)std::max((ptrdiff_t)buffer->getBufferCPUMemSize() - (ptrdiff_t)dstOffset, 0); + } + if (savedTransform._cameraOffset == INVALID_OFFSET) { + qCWarning(gpugllogging) << "Saved TransformCamera data has an invalid transform offset. Copy aborted."; + return; + } + + // Sync BufferObject + auto* object = syncGPUObject(*buffer); + if (object) { + glCopyNamedBufferSubData(_transform._cameraBuffer, object->_buffer, savedTransform._cameraOffset, dstOffset, size); + (void)CHECK_GL_ERROR(); + } +} diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index 636518c85a..65eb85fd6b 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -164,6 +164,8 @@ protected: bool bindResourceBuffer(uint32_t slot, const BufferPointer& buffer) override; void releaseResourceBuffer(uint32_t slot) override; + void do_copySavedViewProjectionTransformToBuffer(const Batch& batch, size_t paramOffset) override; + // Output stage void do_blit(const Batch& batch, size_t paramOffset) override; diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp index 7e1ee0da3b..14cc9a5a4d 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp @@ -10,6 +10,8 @@ // #include "GLESBackend.h" +#include "gpu/gl/GLBuffer.h" + using namespace gpu; using namespace gpu::gles; @@ -99,4 +101,34 @@ void GLESBackend::updateTransform(const Batch& batch) { } (void)CHECK_GL_ERROR(); -} \ No newline at end of file +} + +void GLESBackend::do_copySavedViewProjectionTransformToBuffer(const Batch& batch, size_t paramOffset) { + auto slotId = batch._params[paramOffset + 0]._uint; + BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 1]._uint); + auto dstOffset = batch._params[paramOffset + 2]._uint; + size_t size = _transform._cameraUboSize; + + slotId = std::min(slotId, gpu::Batch::MAX_TRANSFORM_SAVE_SLOT_COUNT); + const auto& savedTransform = _transform._savedTransforms[slotId]; + + if ((dstOffset + size) > buffer->getBufferCPUMemSize()) { + qCWarning(gpugllogging) << "Copying saved TransformCamera data out of bounds of uniform buffer"; + size = (size_t)std::max((ptrdiff_t)buffer->getBufferCPUMemSize() - (ptrdiff_t)dstOffset, 0); + } + if (savedTransform._cameraOffset == INVALID_OFFSET) { + qCWarning(gpugllogging) << "Saved TransformCamera data has an invalid transform offset. Copy aborted."; + return; + } + + // Sync BufferObject + auto* object = syncGPUObject(*buffer); + if (object) { + glBindBuffer(GL_COPY_READ_BUFFER, _transform._cameraBuffer); + glBindBuffer(GL_COPY_WRITE_BUFFER, object->_buffer); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, savedTransform._cameraOffset, dstOffset, size); + glBindBuffer(GL_COPY_READ_BUFFER, 0); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + (void)CHECK_GL_ERROR(); + } +} diff --git a/libraries/gpu/src/gpu/Backend.cpp b/libraries/gpu/src/gpu/Backend.cpp new file mode 100644 index 0000000000..c344b4c535 --- /dev/null +++ b/libraries/gpu/src/gpu/Backend.cpp @@ -0,0 +1,126 @@ +// +// Backend.cpp +// interface/src/gpu +// +// Created by Olivier Prat on 05/25/2018. +// Copyright 2018 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Backend.h" + +using namespace gpu; + +// Counters for Buffer and Texture usage in GPU/Context + +ContextMetricSize Backend::freeGPUMemSize; + +ContextMetricCount Backend::bufferCount; +ContextMetricSize Backend::bufferGPUMemSize; + +ContextMetricCount Backend::textureResidentCount; +ContextMetricCount Backend::textureFramebufferCount; +ContextMetricCount Backend::textureResourceCount; +ContextMetricCount Backend::textureExternalCount; + +ContextMetricSize Backend::textureResidentGPUMemSize; +ContextMetricSize Backend::textureFramebufferGPUMemSize; +ContextMetricSize Backend::textureResourceGPUMemSize; +ContextMetricSize Backend::textureExternalGPUMemSize; + +ContextMetricCount Backend::texturePendingGPUTransferCount; +ContextMetricSize Backend::texturePendingGPUTransferMemSize; + +ContextMetricSize Backend::textureResourcePopulatedGPUMemSize; +ContextMetricSize Backend::textureResourceIdealGPUMemSize; + +void Backend::setStereoState(const StereoState& stereo) { + _prevStereo = _stereo; + _stereo = stereo; +} + +Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, + const StereoState& stereo, + const StereoState& prevStereo, + const Transform& view, + const Transform& previousView, + Vec2 normalizedJitter) const { + TransformCamera result = *this; + Transform eyeView = view; + Transform eyePreviousView = previousView; + if (!stereo._skybox) { + eyeView.postTranslate(-Vec3(stereo._eyeViews[eye][3])); + eyePreviousView.postTranslate(-Vec3(prevStereo._eyeViews[eye][3])); + } else { + // FIXME: If "skybox" the ipd is set to 0 for now, let s try to propose a better solution for this in the future + eyePreviousView.setTranslation(vec3()); + } + result._projection = stereo._eyeProjections[eye]; + Mat4 previousProjection = prevStereo._eyeProjections[eye]; + + // Apply jitter to projections + // We divided by the framebuffer size, which was double-sized, to normalize the jitter, but we want a normal amount of jitter + // for each eye, so we multiply by 2 to get back to normal + normalizedJitter.x *= 2.0f; + result._projection[2][0] += normalizedJitter.x; + result._projection[2][1] += normalizedJitter.y; + + previousProjection[2][0] += normalizedJitter.x; + previousProjection[2][1] += normalizedJitter.y; + + result.recomputeDerived(eyeView, eyePreviousView, previousProjection); + + result._stereoInfo = Vec4(1.0f, (float)eye, 1.0f / result._viewport.z, 1.0f / result._viewport.w); + + return result; +} + +Backend::TransformCamera Backend::TransformCamera::getMonoCamera(bool isSkybox, + const Transform& view, + Transform previousView, + Mat4 previousProjection, + Vec2 normalizedJitter) const { + TransformCamera result = *this; + + if (isSkybox) { + previousView.setTranslation(vec3()); + } + result._projection[2][0] += normalizedJitter.x; + result._projection[2][1] += normalizedJitter.y; + + previousProjection[2][0] += normalizedJitter.x; + previousProjection[2][1] += normalizedJitter.y; + + result.recomputeDerived(view, previousView, previousProjection); + + result._stereoInfo = Vec4(0.0f, 0.0f, 1.0f / result._viewport.z, 1.0f / result._viewport.w); + return result; +} + +const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived(const Transform& view, + const Transform& previousView, + const Mat4& previousProjection) const { + _projectionInverse = glm::inverse(_projection); + + // Get the viewEyeToWorld matrix form the transformView as passed to the gpu::Batch + // this is the "_viewInverse" fed to the shader + // Genetrate the "_view" matrix as well from the xform + view.getMatrix(_viewInverse); + _view = glm::inverse(_viewInverse); + previousView.getMatrix(_previousViewInverse); + _previousView = glm::inverse(_previousViewInverse); + + Mat4 viewUntranslated = _view; + viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f); + _projectionViewUntranslated = _projection * viewUntranslated; + + viewUntranslated = _previousView; + viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f); + _previousProjectionViewUntranslated = previousProjection * viewUntranslated; + + _stereoInfo = Vec4(0.0f); + + return *this; +} diff --git a/libraries/gpu/src/gpu/Backend.h b/libraries/gpu/src/gpu/Backend.h new file mode 100644 index 0000000000..901780829c --- /dev/null +++ b/libraries/gpu/src/gpu/Backend.h @@ -0,0 +1,141 @@ +// +// Backend.h +// interface/src/gpu +// +// Created by Olivier Prat on 05/18/2018. +// Copyright 2018 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_gpu_Backend_h +#define hifi_gpu_Backend_h + +#include + +#include "Forward.h" +#include "Batch.h" +#include "Buffer.h" +#include "Framebuffer.h" + +class QImage; + +namespace gpu { +class Context; + +struct ContextStats { +public: + int _ISNumFormatChanges = 0; + int _ISNumInputBufferChanges = 0; + int _ISNumIndexBufferChanges = 0; + + int _RSNumResourceBufferBounded = 0; + int _RSNumTextureBounded = 0; + int _RSAmountTextureMemoryBounded = 0; + + int _DSNumAPIDrawcalls = 0; + int _DSNumDrawcalls = 0; + int _DSNumTriangles = 0; + + int _PSNumSetPipelines = 0; + + ContextStats() {} + ContextStats(const ContextStats& stats) = default; + + void evalDelta(const ContextStats& begin, const ContextStats& end); +}; + +class Backend { +public: + virtual ~Backend() {} + + virtual void shutdown() {} + virtual const std::string& getVersion() const = 0; + + void setStereoState(const StereoState& stereo); + + virtual void render(const Batch& batch) = 0; + virtual void syncCache() = 0; + virtual void syncProgram(const gpu::ShaderPointer& program) = 0; + virtual void recycle() const = 0; + virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0; + virtual void updatePresentFrame(const Mat4& correction = Mat4()) = 0; + + virtual bool supportedTextureFormat(const gpu::Element& format) = 0; + + // Shared header between C++ and GLSL +#include "TransformCamera_shared.slh" + + class TransformCamera : public _TransformCamera { + public: + const Backend::TransformCamera& recomputeDerived(const Transform& view, const Transform& previousView, const Mat4& previousProjection) const; + // Jitter should be divided by framebuffer size + TransformCamera getMonoCamera(bool isSkybox, const Transform& view, Transform previousView, Mat4 previousProjection, Vec2 normalizedJitter) const; + // Jitter should be divided by framebuffer size + TransformCamera getEyeCamera(int eye, const StereoState& stereo, const StereoState& prevStereo, const Transform& view, const Transform& previousView, + Vec2 normalizedJitter) const; + }; + + template + static void setGPUObject(const U& object, T* gpuObject) { + object.gpuObject.setGPUObject(gpuObject); + } + template + static T* getGPUObject(const U& object) { + return reinterpret_cast(object.gpuObject.getGPUObject()); + } + + void resetStats() const { _stats = ContextStats(); } + void getStats(ContextStats& stats) const { stats = _stats; } + + virtual bool isTextureManagementSparseEnabled() const = 0; + + // These should only be accessed by Backend implementation to report the buffer and texture allocations, + // they are NOT public objects + static ContextMetricSize freeGPUMemSize; + + static ContextMetricCount bufferCount; + static ContextMetricSize bufferGPUMemSize; + + static ContextMetricCount textureResidentCount; + static ContextMetricCount textureFramebufferCount; + static ContextMetricCount textureResourceCount; + static ContextMetricCount textureExternalCount; + + static ContextMetricSize textureResidentGPUMemSize; + static ContextMetricSize textureFramebufferGPUMemSize; + static ContextMetricSize textureResourceGPUMemSize; + static ContextMetricSize textureExternalGPUMemSize; + + static ContextMetricCount texturePendingGPUTransferCount; + static ContextMetricSize texturePendingGPUTransferMemSize; + static ContextMetricSize textureResourcePopulatedGPUMemSize; + static ContextMetricSize textureResourceIdealGPUMemSize; + +protected: + virtual bool isStereo() const { + return _stereo.isStereo(); + } + + void getStereoProjections(mat4* eyeProjections) const { + for (int i = 0; i < 2; ++i) { + eyeProjections[i] = _stereo._eyeProjections[i]; + } + } + + void getStereoViews(mat4* eyeViews) const { + for (int i = 0; i < 2; ++i) { + eyeViews[i] = _stereo._eyeViews[i]; + } + } + + friend class Context; + mutable ContextStats _stats; + StereoState _stereo; + StereoState _prevStereo; +}; + +} + +#endif diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index e6217cc600..bc53bf9c8e 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -53,6 +53,7 @@ Batch::Batch(const std::string& name) { _data.reserve(_dataMax); _objects.reserve(_objectsMax); _drawCallInfos.reserve(_drawCallInfosMax); + _mustUpdatePreviousModels = true; } Batch::~Batch() { @@ -101,17 +102,18 @@ void Batch::clear() { _currentModel = Transform(); _drawcallUniform = 0; _drawcallUniformReset = 0; - _projectionJitter = glm::vec2(0.0f); _enableStereo = true; _enableSkybox = false; + _mustUpdatePreviousModels = true; } size_t Batch::cacheData(size_t size, const void* data) { size_t offset = _data.size(); size_t numBytes = size; _data.resize(offset + numBytes); - memcpy(_data.data() + offset, data, size); - + if (data) { + memcpy(_data.data() + offset, data, size); + } return offset; } @@ -236,6 +238,15 @@ void Batch::setModelTransform(const Transform& model) { ADD_COMMAND(setModelTransform); _currentModel = model; + _previousModel = model; + _invalidModel = true; +} + +void Batch::setModelTransform(const Transform& model, const Transform& previousModel) { + ADD_COMMAND(setModelTransform); + + _currentModel = model; + _previousModel = previousModel; _invalidModel = true; } @@ -252,20 +263,29 @@ void Batch::setProjectionTransform(const Mat4& proj) { _params.emplace_back(cacheData(sizeof(Mat4), &proj)); } -void Batch::setProjectionJitter(float jx, float jy) { - _projectionJitter.x = jx; - _projectionJitter.y = jy; - pushProjectionJitter(jx, jy); +void Batch::setProjectionJitterEnabled(bool isProjectionEnabled) { + _isJitterOnProjectionEnabled = isProjectionEnabled; + pushProjectionJitterEnabled(_isJitterOnProjectionEnabled); } -void Batch::pushProjectionJitter(float jx, float jy) { - ADD_COMMAND(setProjectionJitter); - _params.emplace_back(jx); - _params.emplace_back(jy); +void Batch::pushProjectionJitterEnabled(bool isProjectionEnabled) { + ADD_COMMAND(setProjectionJitterEnabled); + _params.emplace_back(isProjectionEnabled & 1); } -void Batch::popProjectionJitter() { - pushProjectionJitter(_projectionJitter.x, _projectionJitter.y); +void Batch::popProjectionJitterEnabled() { + pushProjectionJitterEnabled(_isJitterOnProjectionEnabled); +} + +void Batch::setProjectionJitterSequence(const Vec2* sequence, size_t count) { + ADD_COMMAND(setProjectionJitterSequence); + _params.emplace_back((uint)count); + _params.emplace_back(cacheData(sizeof(Vec2) * count, sequence)); +} + +void Batch::setProjectionJitterScale(float scale) { + ADD_COMMAND(setProjectionJitterScale); + _params.emplace_back(scale); } void Batch::setViewportTransform(const Vec4i& viewport) { @@ -281,6 +301,34 @@ void Batch::setDepthRangeTransform(float nearDepth, float farDepth) { _params.emplace_back(nearDepth); } +void Batch::saveViewProjectionTransform(uint32 saveSlot) { + ADD_COMMAND(saveViewProjectionTransform); + if (saveSlot >= MAX_TRANSFORM_SAVE_SLOT_COUNT) { + qCWarning(gpulogging) << "Transform save slot" << saveSlot << "exceeds max save slot count of" << MAX_TRANSFORM_SAVE_SLOT_COUNT; + } + _params.emplace_back(saveSlot); +} + +void Batch::setSavedViewProjectionTransform(uint32 saveSlot) { + ADD_COMMAND(setSavedViewProjectionTransform); + if (saveSlot >= MAX_TRANSFORM_SAVE_SLOT_COUNT) { + qCWarning(gpulogging) << "Transform save slot" << saveSlot << "exceeds max save slot count of" + << MAX_TRANSFORM_SAVE_SLOT_COUNT; + } + _params.emplace_back(saveSlot); +} + +void Batch::copySavedViewProjectionTransformToBuffer(uint32 saveSlot, const BufferPointer& buffer, Offset offset) { + ADD_COMMAND(copySavedViewProjectionTransformToBuffer); + if (saveSlot >= MAX_TRANSFORM_SAVE_SLOT_COUNT) { + qCWarning(gpulogging) << "Transform save slot" << saveSlot << "exceeds max save slot count of" + << MAX_TRANSFORM_SAVE_SLOT_COUNT; + } + _params.emplace_back(saveSlot); + _params.emplace_back(_buffers.cache(buffer)); + _params.emplace_back(offset); +} + void Batch::setPipeline(const PipelinePointer& pipeline) { ADD_COMMAND(setPipeline); @@ -548,12 +596,15 @@ void Batch::captureDrawCallInfoImpl() { if (_invalidModel) { TransformObject object; _currentModel.getMatrix(object._model); + _previousModel.getMatrix(object._previousModel); // FIXME - we don't want to be using glm::inverse() here but it fixes the flickering issue we are // seeing with planky blocks in toybox. Our implementation of getInverseMatrix() is buggy in cases // of non-uniform scale. We need to fix that. In the mean time, glm::inverse() works. //_model.getInverseMatrix(_object._modelInverse); + //_previousModel.getInverseMatrix(_object._previousModelInverse); object._modelInverse = glm::inverse(object._model); + object._previousModelInverse = glm::inverse(object._previousModel); _objects.emplace_back(object); @@ -760,4 +811,4 @@ void Batch::flush() { } buffer->flush(); } -} \ No newline at end of file +} diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 0a438ea148..df9161bdda 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -42,6 +42,9 @@ class Batch { public: typedef Stream::Slot Slot; + enum { + MAX_TRANSFORM_SAVE_SLOT_COUNT = 6 + }; class DrawCallInfo { public: @@ -151,20 +154,20 @@ public: // multi command desctription for multiDrawIndexedIndirect class DrawIndirectCommand { public: - uint _count{ 0 }; - uint _instanceCount{ 0 }; - uint _firstIndex{ 0 }; - uint _baseInstance{ 0 }; + uint _count { 0 }; + uint _instanceCount { 0 }; + uint _firstIndex { 0 }; + uint _baseInstance { 0 }; }; // multi command desctription for multiDrawIndexedIndirect class DrawIndexedIndirectCommand { public: - uint _count{ 0 }; - uint _instanceCount{ 0 }; - uint _firstIndex{ 0 }; - uint _baseVertex{ 0 }; - uint _baseInstance{ 0 }; + uint _count { 0 }; + uint _instanceCount { 0 }; + uint _firstIndex { 0 }; + uint _baseVertex { 0 }; + uint _baseInstance { 0 }; }; // Transform Stage @@ -174,17 +177,24 @@ public: // WARNING: ViewTransform transform from eye space to world space, its inverse is composed // with the ModelTransform to create the equivalent of the gl ModelViewMatrix void setModelTransform(const Transform& model); + void setModelTransform(const Transform& model, const Transform& previousModel); void resetViewTransform() { setViewTransform(Transform(), false); } void setViewTransform(const Transform& view, bool camera = true); void setProjectionTransform(const Mat4& proj); - void setProjectionJitter(float jx = 0.0f, float jy = 0.0f); + void setProjectionJitterEnabled(bool isProjectionEnabled); + void setProjectionJitterSequence(const Vec2* sequence, size_t count); + void setProjectionJitterScale(float scale); // Very simple 1 level stack management of jitter. - void pushProjectionJitter(float jx = 0.0f, float jy = 0.0f); - void popProjectionJitter(); + void pushProjectionJitterEnabled(bool isProjectionEnabled); + void popProjectionJitterEnabled(); // Viewport is xy = low left corner in framebuffer, zw = width height of the viewport, expressed in pixels void setViewportTransform(const Vec4i& viewport); void setDepthRangeTransform(float nearDepth, float farDepth); + void saveViewProjectionTransform(uint32 saveSlot); + void setSavedViewProjectionTransform(uint32 saveSlot); + void copySavedViewProjectionTransformToBuffer(uint32 saveSlot, const BufferPointer& buffer, Offset offset); + // Pipeline Stage void setPipeline(const PipelinePointer& pipeline); @@ -202,7 +212,7 @@ public: void setResourceTexture(uint32 slot, const TexturePointer& texture); void setResourceTexture(uint32 slot, const TextureView& view); // not a command, just a shortcut from a TextureView void setResourceTextureTable(const TextureTablePointer& table, uint32 slot = 0); - void setResourceFramebufferSwapChainTexture(uint32 slot, const FramebufferSwapChainPointer& framebuffer, unsigned int swpaChainIndex, unsigned int renderBufferSlot = 0U); // not a command, just a shortcut from a TextureView + void setResourceFramebufferSwapChainTexture(uint32 slot, const FramebufferSwapChainPointer& framebuffer, unsigned int swapChainIndex, unsigned int renderBufferSlot = 0U); // not a command, just a shortcut from a TextureView // Ouput Stage void setFramebuffer(const FramebufferPointer& framebuffer); @@ -309,10 +319,16 @@ public: COMMAND_setModelTransform, COMMAND_setViewTransform, COMMAND_setProjectionTransform, - COMMAND_setProjectionJitter, + COMMAND_setProjectionJitterEnabled, + COMMAND_setProjectionJitterSequence, + COMMAND_setProjectionJitterScale, COMMAND_setViewportTransform, COMMAND_setDepthRangeTransform, + COMMAND_saveViewProjectionTransform, + COMMAND_setSavedViewProjectionTransform, + COMMAND_copySavedViewProjectionTransformToBuffer, + COMMAND_setPipeline, COMMAND_setStateBlendFactor, COMMAND_setStateScissorRect, @@ -495,17 +511,14 @@ public: Bytes _data; static size_t _dataMax; - // SSBO class... layout MUST match the layout in Transform.slh - class TransformObject { - public: - Mat4 _model; - Mat4 _modelInverse; - }; +#include "TransformObject_shared.slh" using TransformObjects = std::vector; bool _invalidModel { true }; Transform _currentModel; - TransformObjects _objects; + Transform _previousModel; + mutable bool _mustUpdatePreviousModels; + mutable TransformObjects _objects; static size_t _objectsMax; BufferCaches _buffers; @@ -523,11 +536,12 @@ public: NamedBatchDataMap _namedData; - uint16_t _drawcallUniform{ 0 }; - uint16_t _drawcallUniformReset{ 0 }; + bool _isJitterOnProjectionEnabled { false }; - glm::vec2 _projectionJitter{ 0.0f, 0.0f }; - bool _enableStereo{ true }; + uint16_t _drawcallUniform { 0 }; + uint16_t _drawcallUniformReset { 0 }; + + bool _enableStereo { true }; bool _enableSkybox { false }; protected: @@ -556,7 +570,7 @@ protected: template size_t Batch::Cache::_max = BATCH_PREALLOCATE_MIN; -} +} // namespace gpu #if defined(NSIGHT_FOUND) diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index 8dee120555..7afd4374fe 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -217,74 +217,6 @@ double Context::getFrameTimerBatchAverage() const { return 0.0; } -const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived(const Transform& xformView) const { - _projectionInverse = glm::inverse(_projection); - - // Get the viewEyeToWorld matrix from the transformView as passed to the gpu::Batch - // this is the "_viewInverse" fed to the shader - // Genetrate the "_view" matrix as well from the xform - xformView.getMatrix(_viewInverse); - _view = glm::inverse(_viewInverse); - - Mat4 viewUntranslated = _view; - viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f); - _projectionViewUntranslated = _projection * viewUntranslated; - - _stereoInfo = Vec4(0.0f); - - return *this; -} - -Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const StereoState& _stereo, const Transform& xformView, Vec2 normalizedJitter) const { - TransformCamera result = *this; - Transform offsetTransform = xformView; - if (!_stereo._skybox) { - offsetTransform.postTranslate(-Vec3(_stereo._eyeViews[eye][3])); - } else { - // FIXME: If "skybox" the ipd is set to 0 for now, let s try to propose a better solution for this in the future - } - result._projection = _stereo._eyeProjections[eye]; - normalizedJitter.x *= 2.0f; - result._projection[2][0] += normalizedJitter.x; - result._projection[2][1] += normalizedJitter.y; - result.recomputeDerived(offsetTransform); - - result._stereoInfo = Vec4(1.0f, (float)eye, 0.0f, 0.0f); - - return result; -} - -Backend::TransformCamera Backend::TransformCamera::getMonoCamera(const Transform& xformView, Vec2 normalizedJitter) const { - TransformCamera result = *this; - result._projection[2][0] += normalizedJitter.x; - result._projection[2][1] += normalizedJitter.y; - result.recomputeDerived(xformView); - return result; -} - -// Counters for Buffer and Texture usage in GPU/Context - -ContextMetricSize Backend::freeGPUMemSize; - -ContextMetricCount Backend::bufferCount; -ContextMetricSize Backend::bufferGPUMemSize; - -ContextMetricCount Backend::textureResidentCount; -ContextMetricCount Backend::textureFramebufferCount; -ContextMetricCount Backend::textureResourceCount; -ContextMetricCount Backend::textureExternalCount; - -ContextMetricSize Backend::textureResidentGPUMemSize; -ContextMetricSize Backend::textureFramebufferGPUMemSize; -ContextMetricSize Backend::textureResourceGPUMemSize; -ContextMetricSize Backend::textureExternalGPUMemSize; - -ContextMetricCount Backend::texturePendingGPUTransferCount; -ContextMetricSize Backend::texturePendingGPUTransferMemSize; - -ContextMetricSize Backend::textureResourcePopulatedGPUMemSize; -ContextMetricSize Backend::textureResourceIdealGPUMemSize; - Size Context::getFreeGPUMemSize() { return Backend::freeGPUMemSize.getValue(); } diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 1946f447f8..4e5cab17a8 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -15,131 +15,14 @@ #include #include -#include - -#include "Forward.h" -#include "Batch.h" -#include "Buffer.h" #include "Texture.h" #include "Pipeline.h" -#include "Framebuffer.h" #include "Frame.h" #include "PointerStorage.h" - -class QImage; +#include "Backend.h" namespace gpu { -struct ContextStats { -public: - uint32_t _ISNumFormatChanges { 0 }; - uint32_t _ISNumInputBufferChanges { 0 }; - uint32_t _ISNumIndexBufferChanges { 0 }; - - uint32_t _RSNumResourceBufferBounded { 0 }; - uint32_t _RSNumTextureBounded { 0 }; - uint64_t _RSAmountTextureMemoryBounded { 0 }; - - uint32_t _DSNumAPIDrawcalls { 0 }; - uint32_t _DSNumDrawcalls { 0 }; - uint32_t _DSNumTriangles { 0 }; - - uint32_t _PSNumSetPipelines { 0 }; - - ContextStats() {} - ContextStats(const ContextStats& stats) = default; - - void evalDelta(const ContextStats& begin, const ContextStats& end); -}; - -class Backend { -public: - virtual ~Backend(){}; - - virtual void shutdown() {} - virtual const std::string& getVersion() const = 0; - - void setStereoState(const StereoState& stereo) { _stereo = stereo; } - - virtual void render(const Batch& batch) = 0; - virtual void syncCache() = 0; - virtual void syncProgram(const gpu::ShaderPointer& program) = 0; - virtual void recycle() const = 0; - virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0; - virtual void setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset = false) {} - - virtual bool supportedTextureFormat(const gpu::Element& format) = 0; - - // Shared header between C++ and GLSL -#include "TransformCamera_shared.slh" - - class TransformCamera : public _TransformCamera { - public: - const Backend::TransformCamera& recomputeDerived(const Transform& xformView) const; - // Jitter should be divided by framebuffer size - TransformCamera getMonoCamera(const Transform& xformView, Vec2 normalizedJitter) const; - // Jitter should be divided by framebuffer size - TransformCamera getEyeCamera(int eye, const StereoState& stereo, const Transform& xformView, Vec2 normalizedJitter) const; - }; - - template - static void setGPUObject(const U& object, T* gpuObject) { - object.gpuObject.setGPUObject(gpuObject); - } - template - static T* getGPUObject(const U& object) { - return reinterpret_cast(object.gpuObject.getGPUObject()); - } - - void resetStats() const { _stats = ContextStats(); } - void getStats(ContextStats& stats) const { stats = _stats; } - - virtual bool isTextureManagementSparseEnabled() const = 0; - - // These should only be accessed by Backend implementation to report the buffer and texture allocations, - // they are NOT public objects - static ContextMetricSize freeGPUMemSize; - - static ContextMetricCount bufferCount; - static ContextMetricSize bufferGPUMemSize; - - static ContextMetricCount textureResidentCount; - static ContextMetricCount textureFramebufferCount; - static ContextMetricCount textureResourceCount; - static ContextMetricCount textureExternalCount; - - static ContextMetricSize textureResidentGPUMemSize; - static ContextMetricSize textureFramebufferGPUMemSize; - static ContextMetricSize textureResourceGPUMemSize; - static ContextMetricSize textureExternalGPUMemSize; - - static ContextMetricCount texturePendingGPUTransferCount; - static ContextMetricSize texturePendingGPUTransferMemSize; - static ContextMetricSize textureResourcePopulatedGPUMemSize; - static ContextMetricSize textureResourceIdealGPUMemSize; - - virtual bool isStereo() const { - return _stereo.isStereo(); - } - - void getStereoProjections(mat4* eyeProjections) const { - for (int i = 0; i < 2; ++i) { - eyeProjections[i] = _stereo._eyeProjections[i]; - } - } -protected: - - void getStereoViews(mat4* eyeViews) const { - for (int i = 0; i < 2; ++i) { - eyeViews[i] = _stereo._eyeViews[i]; - } - } - - friend class Context; - mutable ContextStats _stats; - StereoState _stereo; -}; - class Context { public: using Size = Resource::Size; diff --git a/libraries/gpu/src/gpu/DrawUnitQuad.slv b/libraries/gpu/src/gpu/DrawUnitQuad.slv new file mode 100644 index 0000000000..909ab81655 --- /dev/null +++ b/libraries/gpu/src/gpu/DrawUnitQuad.slv @@ -0,0 +1,27 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// <$_SCRIBE_FILENAME$> +// Generated on <$_SCRIBE_DATE$> +// Draw the unit quad [-1,-1 -> 1,1]. +// Not transform used. +// Simply draw a Triangle_strip of 2 triangles, no input buffers or index buffer needed +// +// Created by Olivier Prat on 10/22/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +void main(void) { + const float depth = 1.0; + const vec4 UNIT_QUAD[4] = vec4[4]( + vec4(-1.0, -1.0, depth, 1.0), + vec4(1.0, -1.0, depth, 1.0), + vec4(-1.0, 1.0, depth, 1.0), + vec4(1.0, 1.0, depth, 1.0) + ); + vec4 pos = UNIT_QUAD[gl_VertexID]; + + gl_Position = pos; +} diff --git a/libraries/gpu/src/gpu/FrameIOKeys.h b/libraries/gpu/src/gpu/FrameIOKeys.h index 1a98d0decd..6cc4a6bd8c 100644 --- a/libraries/gpu/src/gpu/FrameIOKeys.h +++ b/libraries/gpu/src/gpu/FrameIOKeys.h @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2018/10/14 // Copyright 2013-2018 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -80,7 +81,7 @@ constexpr const char* pose = "pose"; constexpr const char* profileRanges = "profileRanges"; constexpr const char* program = "program"; constexpr const char* programs = "programs"; -constexpr const char* projectionJitter = "projectionJitter"; +constexpr const char* isJitterOnProjectionEnabled = "isJitterOnProjectionEnabled"; constexpr const char* queries = "queries"; constexpr const char* sampleCount = "sampleCount"; constexpr const char* sampleMask = "sampleMask"; @@ -150,10 +151,16 @@ constexpr const char* COMMAND_NAMES[] = { "setModelTransform", "setViewTransform", "setProjectionTransform", - "setProjectionJitter", + "setProjectionJitterEnabled", + "setProjectionJitterSequence", + "setProjectionJitterScale", "setViewportTransform", "setDepthRangeTransform", + "saveViewProjectionTransform", + "setSavedViewProjectionTransform", + "copySavedViewProjectionTransformToBuffer", + "setPipeline", "setStateBlendFactor", "setStateScissorRect", diff --git a/libraries/gpu/src/gpu/FrameReader.cpp b/libraries/gpu/src/gpu/FrameReader.cpp index 96f6b99f7a..79858e2888 100644 --- a/libraries/gpu/src/gpu/FrameReader.cpp +++ b/libraries/gpu/src/gpu/FrameReader.cpp @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2018/10/14 // Copyright 2013-2018 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -740,7 +741,7 @@ BatchPointer Deserializer::readBatch(const json& node) { auto& batch = *result; readOptional(batch._enableStereo, node, keys::stereo); readOptional(batch._enableSkybox, node, keys::skybox); - readOptionalTransformed(batch._projectionJitter, node, keys::projectionJitter, &readVec2); + readOptional(batch._isJitterOnProjectionEnabled, node, keys::isJitterOnProjectionEnabled); readOptional(batch._drawcallUniform, node, keys::drawcallUniform); readOptional(batch._drawcallUniformReset, node, keys::drawcallUniformReset); readPointerCache(batch._textures, node, keys::textures, textures); diff --git a/libraries/gpu/src/gpu/FrameWriter.cpp b/libraries/gpu/src/gpu/FrameWriter.cpp index 761f37a620..eb53041280 100644 --- a/libraries/gpu/src/gpu/FrameWriter.cpp +++ b/libraries/gpu/src/gpu/FrameWriter.cpp @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2018/10/14 // Copyright 2013-2018 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -220,8 +221,8 @@ json Serializer::writeBatch(const Batch& batch) { if (batch._enableStereo != DEFAULT_BATCH._enableStereo) { batchNode[keys::stereo] = batch._enableStereo; } - if (batch._projectionJitter != DEFAULT_BATCH._projectionJitter) { - batchNode[keys::projectionJitter] = writeVec2(batch._projectionJitter); + if (batch._isJitterOnProjectionEnabled != DEFAULT_BATCH._isJitterOnProjectionEnabled) { + batchNode[keys::isJitterOnProjectionEnabled] = batch._isJitterOnProjectionEnabled; } if (batch._drawcallUniform != DEFAULT_BATCH._drawcallUniform) { batchNode[keys::drawcallUniform] = batch._drawcallUniform; diff --git a/libraries/gpu/src/gpu/Query.h b/libraries/gpu/src/gpu/Query.h index 912901951c..acec5500b2 100644 --- a/libraries/gpu/src/gpu/Query.h +++ b/libraries/gpu/src/gpu/Query.h @@ -66,6 +66,8 @@ namespace gpu { double getGPUAverage() const; double getBatchAverage() const; + const std::string& name() const { return _name; } + protected: static const int QUERY_QUEUE_SIZE { 4 }; diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index 3015de7e0e..5653d67a26 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -3,6 +3,7 @@ // // Created by Sam Gateau on 2/10/15. // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -10,8 +11,12 @@ <@if not GPU_TRANSFORM_STATE_SLH@> <@def GPU_TRANSFORM_STATE_SLH@> -<@include gpu/ShaderConstants.h@> <@func declareStandardCameraTransform()@> + +#ifndef STANDARD_TRANSFORM_CAMERA +#define STANDARD_TRANSFORM_CAMERA + +<@include gpu/ShaderConstants.h@> <@include gpu/TransformCamera_shared.slh@> #define TransformCamera _TransformCamera @@ -90,32 +95,23 @@ vec3 getEyeWorldPos() { } bool cam_isStereo() { -#ifdef GPU_TRANSFORM_IS_STEREO return getTransformCamera()._stereoInfo.x > 0.0; -#else - return _cameraBlock._camera._stereoInfo.x > 0.0; -#endif } float cam_getStereoSide() { -#ifdef GPU_TRANSFORM_IS_STEREO -#ifdef GPU_TRANSFORM_STEREO_CAMERA return getTransformCamera()._stereoInfo.y; -#else - return _cameraBlock._camera._stereoInfo.y; -#endif -#else - return _cameraBlock._camera._stereoInfo.y; -#endif } +vec2 cam_getInvWidthHeight() { + return getTransformCamera()._stereoInfo.zw; +} + +#endif // STANDARD_TRANSFORM_CAMERA + <@endfunc@> <@func declareStandardObjectTransform()@> -struct TransformObject { - mat4 _model; - mat4 _modelInverse; -}; +<@include gpu/TransformObject_shared.slh@> layout(location=GPU_ATTR_DRAW_CALL_INFO) in ivec2 _drawCallInfo; @@ -155,11 +151,7 @@ TransformObject getTransformObject() { <$declareStandardObjectTransform()$> <@endfunc@> -<@func transformCameraViewport(cameraTransform, viewport)@> - <$viewport$> = <$cameraTransform$>._viewport; -<@endfunc@> - -<@func transformStereoClipsSpace(cameraTransform, clipPos)@> +<@func transformStereoClipSpace(clipPos)@> { #ifdef GPU_TRANSFORM_IS_STEREO @@ -190,6 +182,18 @@ TransformObject getTransformObject() { } <@endfunc@> +<@func transformModelToEyeAndPrevEyeWorldAlignedPos(cameraTransform, objectTransform, modelPos, eyeWAPos, prevEyeWAPos)@> + + { // transformModelToEyeAndPrevEyeWorldAlignedPos + highp mat4 _mv = <$objectTransform$>._model; + highp mat4 _pmv = <$objectTransform$>._previousModel; + _mv[3].xyz -= <$cameraTransform$>._viewInverse[3].xyz; + _pmv[3].xyz -= <$cameraTransform$>._previousViewInverse[3].xyz; + <$eyeWAPos$> = (_mv * <$modelPos$>); + <$prevEyeWAPos$> = (_pmv * <$modelPos$>); + } +<@endfunc@> + <@func transformModelToMonoClipPos(cameraTransform, objectTransform, modelPos, clipPos)@> { // transformModelToMonoClipPos vec4 eyeWAPos; @@ -201,7 +205,7 @@ TransformObject getTransformObject() { <@func transformModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@> { // transformModelToClipPos <$transformModelToMonoClipPos($cameraTransform$, $objectTransform$, $modelPos$, $clipPos$)$> - <$transformStereoClipsSpace($cameraTransform$, $clipPos$)$> + <$transformStereoClipSpace($clipPos$)$> } <@endfunc@> @@ -212,19 +216,59 @@ TransformObject getTransformObject() { <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; <$eyePos$> = vec4((<$cameraTransform$>._view * vec4(eyeWAPos.xyz, 0.0)).xyz, 1.0); - <$transformStereoClipsSpace($cameraTransform$, $clipPos$)$> + <$transformStereoClipSpace($clipPos$)$> } <@endfunc@> -<@func transformModelToWorldAndEyeAndClipPos(cameraTransform, objectTransform, modelPos, worldPos, eyePos, clipPos)@> - { // transformModelToEyeAndClipPos +<@func transformModelToEyeClipPosAndPrevClipPos(cameraTransform, objectTransform, modelPos, eyePos, clipPos, prevClipPos)@> + { // transformModelToEyeClipPosAndPrevClipPos + vec4 eyeWAPos; + vec4 prevEyeWAPos; + <$transformModelToEyeAndPrevEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos, prevEyeWAPos)$> + <$eyePos$> = vec4((<$cameraTransform$>._view * vec4(eyeWAPos.xyz, 0.0)).xyz, 1.0); + <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; + <$transformStereoClipSpace($clipPos$)$> + + <$prevClipPos$> = <$cameraTransform$>._previousProjectionViewUntranslated * prevEyeWAPos; + // Prev clip pos is in mono clip space + } +<@endfunc@> + +<@func transformModelToClipPosAndPrevClipPos(cameraTransform, objectTransform, modelPos, clipPos, prevClipPos)@> + { // transformModelToClipPosAndPrevClipPos + vec4 eyeWAPos; + vec4 prevEyeWAPos; + <$transformModelToEyeAndPrevEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos, prevEyeWAPos)$> + <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; + <$transformStereoClipSpace($clipPos$)$> + + <$prevClipPos$> = <$cameraTransform$>._previousProjectionViewUntranslated * prevEyeWAPos; + // Prev clip pos is in mono clip space + } +<@endfunc@> + +<@func transformModelToWorldEyeAndClipPos(cameraTransform, objectTransform, modelPos, worldPos, eyePos, clipPos)@> + { // transformModelToWorldEyeAndClipPos vec4 eyeWAPos; <$transformModelToEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos)$> <$worldPos$> = vec4(eyeWAPos.xyz + <$cameraTransform$>._viewInverse[3].xyz, 1.0); <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; <$eyePos$> = vec4((<$cameraTransform$>._view * vec4(eyeWAPos.xyz, 0.0)).xyz, 1.0); - <$transformStereoClipsSpace($cameraTransform$, $clipPos$)$> + <$transformStereoClipSpace($clipPos$)$> + } +<@endfunc@> + +<@func transformModelToWorldEyeClipPosAndPrevClipPos(cameraTransform, objectTransform, modelPos, worldPos, eyePos, clipPos, prevClipPos)@> + { // transformModelToWorldEyeClipPosAndPrevClipPos + vec4 eyeWAPos; + vec4 prevEyeWAPos; + <$transformModelToEyeAndPrevEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos, prevEyeWAPos)$> + <$worldPos$> = vec4(eyeWAPos.xyz + <$cameraTransform$>._viewInverse[3].xyz, 1.0); + <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; + <$eyePos$> = vec4((<$cameraTransform$>._view * vec4(eyeWAPos.xyz, 0.0)).xyz, 1.0); + <$prevClipPos$> = <$cameraTransform$>._previousProjectionViewUntranslated * prevEyeWAPos; + <$transformStereoClipSpace($clipPos$)$> } <@endfunc@> @@ -236,13 +280,22 @@ TransformObject getTransformObject() { } <@endfunc@> +<@func transformModelToEyePosAndPrevEyePos(cameraTransform, objectTransform, modelPos, eyePos, prevEyePos)@> + { // transformModelToEyePosAndPrevEyePos + vec4 eyeWAPos; + vec4 prevEyeWAPos; + <$transformModelToEyeAndPrevEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos, prevEyeWAPos)$> + <$eyePos$> = vec4((<$cameraTransform$>._view * vec4(eyeWAPos.xyz, 0.0)).xyz, 1.0); + <$prevEyePos$> = vec4((<$cameraTransform$>._previousView * vec4(prevEyeWAPos.xyz, 0.0)).xyz, 1.0); + } +<@endfunc@> <@func transformWorldToClipPos(cameraTransform, worldPos, clipPos)@> { // transformWorldToClipPos vec4 eyeWAPos = <$worldPos$> - vec4(<$cameraTransform$>._viewInverse[3].xyz, 0.0); <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; - <$transformStereoClipsSpace($cameraTransform$, $clipPos$)$> + <$transformStereoClipSpace($clipPos$)$> } <@endfunc@> @@ -275,6 +328,20 @@ TransformObject getTransformObject() { } <@endfunc@> +<@func transformModelToPrevEyeDir(cameraTransform, objectTransform, modelDir, prevEyeDir)@> + { // transformModelToPrevEyeDir + vec3 mr0 = vec3(<$objectTransform$>._previousModelInverse[0].x, <$objectTransform$>._previousModelInverse[1].x, <$objectTransform$>._previousModelInverse[2].x); + vec3 mr1 = vec3(<$objectTransform$>._previousModelInverse[0].y, <$objectTransform$>._previousModelInverse[1].y, <$objectTransform$>._previousModelInverse[2].y); + vec3 mr2 = vec3(<$objectTransform$>._previousModelInverse[0].z, <$objectTransform$>._previousModelInverse[1].z, <$objectTransform$>._previousModelInverse[2].z); + + vec3 mvc0 = vec3(dot(<$cameraTransform$>._previousViewInverse[0].xyz, mr0), dot(<$cameraTransform$>._previousViewInverse[0].xyz, mr1), dot(<$cameraTransform$>._previousViewInverse[0].xyz, mr2)); + vec3 mvc1 = vec3(dot(<$cameraTransform$>._previousViewInverse[1].xyz, mr0), dot(<$cameraTransform$>._previousViewInverse[1].xyz, mr1), dot(<$cameraTransform$>._previousViewInverse[1].xyz, mr2)); + vec3 mvc2 = vec3(dot(<$cameraTransform$>._previousViewInverse[2].xyz, mr0), dot(<$cameraTransform$>._previousViewInverse[2].xyz, mr1), dot(<$cameraTransform$>._previousViewInverse[2].xyz, mr2)); + + <$prevEyeDir$> = vec3(dot(mvc0, <$modelDir$>), dot(mvc1, <$modelDir$>), dot(mvc2, <$modelDir$>)); + } +<@endfunc@> + <@func transformEyeToWorldDir(cameraTransform, eyeDir, worldDir)@> { // transformEyeToWorldDir <$worldDir$> = vec3(<$cameraTransform$>._viewInverse * vec4(<$eyeDir$>.xyz, 0.0)); @@ -291,7 +358,34 @@ TransformObject getTransformObject() { { // transformEyeToClipPos <$clipPos$> = <$cameraTransform$>._projection * vec4(<$eyePos$>.xyz, 1.0); - <$transformStereoClipsSpace($cameraTransform$, $clipPos$)$> + <$transformStereoClipSpace($clipPos$)$> + } +<@endfunc@> + +<@func transformEyeToPrevClipPos(cameraTransform, eyePos, prevClipPos)@> + { // transformEyeToClipPos + vec4 worldPos = <$cameraTransform$>._viewInverse * vec4(<$eyePos$>.xyz, 1.0); + <$prevClipPos$> = <$cameraTransform$>._previousProjectionViewUntranslated * worldPos; + // Prev clip pos is in mono clip space + } +<@endfunc@> + +<@func transformEyeToClipPosAndPrevClipPos(cameraTransform, eyePos, clipPos, prevClipPos)@> + { // transformEyeToClipPosAndPrevClipPos + <$clipPos$> = <$cameraTransform$>._projection * vec4(<$eyePos$>.xyz, 1.0); + + <$transformStereoClipSpace($clipPos$)$> + + vec4 worldPos = <$cameraTransform$>._viewInverse * vec4(<$eyePos$>.xyz, 1.0); + <$prevClipPos$> = <$cameraTransform$>._previousProjectionViewUntranslated * worldPos; + // Prev clip pos is in mono clip space + } +<@endfunc@> + +<@func transformPrevEyeToPrevClipPos(cameraTransform, prevEyePos, prevClipPos)@> + { // transformPrevEyeToPrevClipPos + <$prevClipPos$> = <$cameraTransform$>._previousViewInverse * vec4(<$prevEyePos$>.xyz, 1.0) - vec4(<$cameraTransform$>._previousViewInverse[3].xyz, 0.0); + <$prevClipPos$> = <$cameraTransform$>._previousProjectionViewUntranslated * <$prevClipPos$>; } <@endfunc@> diff --git a/libraries/gpu/src/gpu/TransformCamera_shared.slh b/libraries/gpu/src/gpu/TransformCamera_shared.slh index e4a0f8c2cc..a7d8f58590 100644 --- a/libraries/gpu/src/gpu/TransformCamera_shared.slh +++ b/libraries/gpu/src/gpu/TransformCamera_shared.slh @@ -1,22 +1,26 @@ -// glsl / C++ compatible source as interface for FadeEffect +// glsl / C++ compatible source as interface for TransformCamera #ifdef __cplusplus -# define _MAT4 Mat4 -# define _VEC4 Vec4 -# define _MUTABLE mutable +# define TC_MAT4 gpu::Mat4 +# define TC_VEC4 gpu::Vec4 +# define TC_MUTABLE mutable #else -# define _MAT4 mat4 -# define _VEC4 vec4 -# define _MUTABLE +# define TC_MAT4 mat4 +# define TC_VEC4 vec4 +# define TC_MUTABLE #endif struct _TransformCamera { - _MUTABLE _MAT4 _view; - _MUTABLE _MAT4 _viewInverse; - _MUTABLE _MAT4 _projectionViewUntranslated; - _MAT4 _projection; - _MUTABLE _MAT4 _projectionInverse; - _VEC4 _viewport; // Public value is int but float in the shader to stay in floats for all the transform computations. - _MUTABLE _VEC4 _stereoInfo; + TC_MUTABLE TC_MAT4 _view; + TC_MUTABLE TC_MAT4 _viewInverse; + TC_MUTABLE TC_MAT4 _previousView; + TC_MUTABLE TC_MAT4 _previousViewInverse; + TC_MAT4 _projection; + TC_MUTABLE TC_MAT4 _projectionInverse; + TC_MUTABLE TC_MAT4 _projectionViewUntranslated; + // Previous projection view untranslated AND jittered with current jitter + TC_MUTABLE TC_MAT4 _previousProjectionViewUntranslated; + TC_VEC4 _viewport; // Public value is int but float in the shader to stay in floats for all the transform computations. + TC_MUTABLE TC_VEC4 _stereoInfo; }; // <@if 1@> diff --git a/libraries/gpu/src/gpu/TransformObject_shared.slh b/libraries/gpu/src/gpu/TransformObject_shared.slh new file mode 100644 index 0000000000..dce87c9e0b --- /dev/null +++ b/libraries/gpu/src/gpu/TransformObject_shared.slh @@ -0,0 +1,19 @@ +// glsl / C++ compatible source as interface for TransformCamera +#ifdef __cplusplus +# define TO_MAT4 Mat4 +#else +# define TO_MAT4 mat4 +#endif + +struct TransformObject { + TO_MAT4 _model; + TO_MAT4 _modelInverse; + TO_MAT4 _previousModel; + TO_MAT4 _previousModelInverse; +}; + + // <@if 1@> + // Trigger Scribe include + // <@endif@> +// + diff --git a/libraries/gpu/src/gpu/drawColor.slp b/libraries/gpu/src/gpu/drawColor.slp index 1c81242fed..8289f46550 100644 --- a/libraries/gpu/src/gpu/drawColor.slp +++ b/libraries/gpu/src/gpu/drawColor.slp @@ -1,3 +1,2 @@ -VERTEX DrawTransformVertexPosition +VERTEX DrawUnitQuad FRAGMENT DrawColor -r diff --git a/libraries/gpu/src/gpu/drawWhite.slp b/libraries/gpu/src/gpu/drawWhite.slp new file mode 100644 index 0000000000..c3e7ec1121 --- /dev/null +++ b/libraries/gpu/src/gpu/drawWhite.slp @@ -0,0 +1,2 @@ +VERTEX DrawUnitQuad +FRAGMENT DrawWhite diff --git a/libraries/graphics/src/graphics/Haze.slh b/libraries/graphics/src/graphics/Haze.slh index a2d8bb0523..2a98177e83 100644 --- a/libraries/graphics/src/graphics/Haze.slh +++ b/libraries/graphics/src/graphics/Haze.slh @@ -181,10 +181,9 @@ vec4 computeHazeColor(vec3 fragPositionES, vec3 fragPositionWS, vec3 eyePosition // Mix with background at far range const float BLEND_DISTANCE = 27000.0f; - vec4 outFragColor = potentialFragColor; - outFragColor.a *= mix(1.0, hazeParams.backgroundBlend, float(distance > BLEND_DISTANCE)); + potentialFragColor.a *= mix(1.0, hazeParams.backgroundBlend, float(distance > BLEND_DISTANCE)); - return outFragColor; + return potentialFragColor; } <@endif@> diff --git a/libraries/graphics/src/graphics/Skybox.cpp b/libraries/graphics/src/graphics/Skybox.cpp index 7d7fd35018..e0134c337b 100644 --- a/libraries/graphics/src/graphics/Skybox.cpp +++ b/libraries/graphics/src/graphics/Skybox.cpp @@ -73,14 +73,14 @@ void Skybox::prepare(gpu::Batch& batch) const { } } -void Skybox::render(gpu::Batch& batch, const ViewFrustum& frustum, bool forward) const { +void Skybox::render(gpu::Batch& batch, const ViewFrustum& frustum, bool forward, uint transformSlot) const { updateSchemaBuffer(); - Skybox::render(batch, frustum, (*this), forward); + Skybox::render(batch, frustum, (*this), forward, transformSlot); } static std::map _pipelines; -void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Skybox& skybox, bool forward) { +void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Skybox& skybox, bool forward, uint transformSlot) { if (_pipelines.empty()) { static const std::vector> keys = { std::make_tuple(false, shader::graphics::program::skybox), @@ -109,6 +109,8 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky batch.setProjectionTransform(projMat); batch.setViewTransform(viewTransform); + // This is needed if we want to have motion vectors on the sky + batch.saveViewProjectionTransform(transformSlot); batch.setModelTransform(Transform()); // only for Mac batch.setPipeline(_pipelines[forward]); diff --git a/libraries/graphics/src/graphics/Skybox.h b/libraries/graphics/src/graphics/Skybox.h index 5668604c8b..94b12cf1ab 100644 --- a/libraries/graphics/src/graphics/Skybox.h +++ b/libraries/graphics/src/graphics/Skybox.h @@ -44,9 +44,9 @@ public: virtual void clear(); void prepare(gpu::Batch& batch) const; - virtual void render(gpu::Batch& batch, const ViewFrustum& frustum, bool forward) const; + virtual void render(gpu::Batch& batch, const ViewFrustum& frustum, bool forward, uint transformSlot) const; - static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox, bool forward); + static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox, bool forward, uint transformSlot); const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; } diff --git a/libraries/graphics/src/graphics/skybox.slf b/libraries/graphics/src/graphics/skybox.slf index 4ae53a657f..394af6f260 100755 --- a/libraries/graphics/src/graphics/skybox.slf +++ b/libraries/graphics/src/graphics/skybox.slf @@ -5,12 +5,15 @@ // // Created by Sam Gateau on 5/5/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // <@include graphics/ShaderConstants.h@> +<@include skybox.slh@> + <@if HIFI_USE_FORWARD@> <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> @@ -19,20 +22,16 @@ <$declareLightBuffer()$> <@include graphics/Haze.slh@> + + layout(location=0) out vec4 _fragColor; +<@else@> + <$declarePackDeferredFragmentSky()$> <@endif@> -LAYOUT(binding=GRAPHICS_TEXTURE_SKYBOX) uniform samplerCube cubeMap; - -struct Skybox { - vec4 color; -}; - -LAYOUT(binding=GRAPHICS_BUFFER_SKYBOX_PARAMS) uniform skyboxBuffer { - Skybox skybox; -}; - -layout(location=0) in vec3 _normal; -layout(location=0) out vec4 _fragColor; +layout(location=0) in vec3 _normal; +<@if not HIFI_USE_FORWARD@> + layout(location=1) in vec4 _prevPositionCS; +<@endif@> void main(void) { // FIXME: For legacy reasons, when skybox.color.a is 0.5, this is equivalent to: @@ -44,10 +43,10 @@ void main(void) { vec3 normal = normalize(_normal); vec3 skyboxTexel = texture(cubeMap, normal).rgb; vec3 skyboxColor = skybox.color.rgb; - _fragColor = vec4(mix(vec3(1.0), skyboxTexel, float(skybox.color.a > 0.0)) * - mix(vec3(1.0), skyboxColor, float(skybox.color.a < 1.0)), 1.0); + vec3 color = mix(vec3(1.0), skyboxTexel, float(skybox.color.a > 0.0)) * mix(vec3(1.0), skyboxColor, float(skybox.color.a < 1.0)); <@if HIFI_USE_FORWARD@> + _fragColor = vec4(color, 1.0); // FIXME: either move this elsewhere or give it access to isHazeEnabled() (which is in render-utils/LightingModel.slh) if (/*(isHazeEnabled() > 0.0) && */(hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { TransformCamera cam = getTransformCamera(); @@ -63,6 +62,8 @@ void main(void) { vec4 hazeColor = computeHazeColor(fragPositionES.xyz, fragPositionWS.xyz, eyePositionWS.xyz, lightDirectionWS); _fragColor.rgb = mix(_fragColor.rgb, hazeColor.rgb, hazeColor.a); } +<@else@> + packDeferredFragmentSky(_prevPositionCS, color, normal); <@endif@> } diff --git a/libraries/graphics/src/graphics/skybox.slh b/libraries/graphics/src/graphics/skybox.slh new file mode 100644 index 0000000000..e84f770c7d --- /dev/null +++ b/libraries/graphics/src/graphics/skybox.slh @@ -0,0 +1,65 @@ + +<@if not SKYBOX_SLH@> +<@def SKYBOX_SLH@> + +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + +<@include gpu/PackedNormal.slh@> + +LAYOUT(binding=GRAPHICS_TEXTURE_SKYBOX) uniform samplerCube cubeMap; + +struct Skybox { + vec4 color; +}; + +LAYOUT(binding=GRAPHICS_BUFFER_SKYBOX_PARAMS) uniform skyboxBuffer { + Skybox skybox; +}; + +<@func declarePackDeferredFragmentSky()@> +// This code belongs in render-utils/VelocityWrite.slh but because graphics can't include render-utils, we have to have it here + +vec2 getEyeTexcoordPos() { + // No need to add 0.5 as, by default, frag coords are pixel centered at (0.5, 0.5) + vec2 texCoordPos = gl_FragCoord.xy; + texCoordPos *= cam_getInvWidthHeight(); + texCoordPos.x -= cam_getStereoSide(); + return texCoordPos; +} + +vec2 packVelocity(vec4 prevPositionCS) { + vec2 uv = getEyeTexcoordPos(); + vec2 prevUV = (prevPositionCS.xy / prevPositionCS.w) * 0.5 + 0.5; + vec2 deltaUV = uv - prevUV; + // Velocity should be computed without any jitter inside. + return deltaUV; +} + +// Must match layout in DeferredBufferWrite.slh, but only velocity and lighting are used +layout(location = 0) out vec4 _albedoMetallic; // albedo / metallic +layout(location = 1) out vec4 _normalRoughness; // normal / roughness +layout(location = 2) out vec4 _scatteringEmissiveOcclusion; // scattering / emissive / occlusion +layout(location = 3) out vec4 _velocity; // velocity +layout(location = 4) out vec4 _lighting; // emissive + +void packDeferredFragmentSky(vec4 prevPositionCS, vec3 color, vec3 normal) { + _albedoMetallic = vec4(color, 0.6f); + _normalRoughness = vec4(packNormal(normal), 1.0f); + _scatteringEmissiveOcclusion = vec4(0.0f); + _velocity = vec4(packVelocity(prevPositionCS), 0.0f, 0.0f); + _lighting = vec4(color, 1.0f); +} + +<@endfunc@> + +<@endif@> diff --git a/libraries/graphics/src/graphics/skybox.slv b/libraries/graphics/src/graphics/skybox.slv index a6e6930d22..991f0c938f 100755 --- a/libraries/graphics/src/graphics/skybox.slv +++ b/libraries/graphics/src/graphics/skybox.slv @@ -5,6 +5,7 @@ // // Created by Sam Gateau on 5/5/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -14,6 +15,9 @@ <$declareStandardTransform()$> layout(location=0) out vec3 _normal; +<@if not HIFI_USE_FORWARD@> + layout(location=1) out vec4 _prevPositionCS; +<@endif@> void main(void) { const float depth = 0.0; @@ -23,17 +27,20 @@ void main(void) { vec4(-1.0, 1.0, depth, 1.0), vec4(1.0, 1.0, depth, 1.0) ); - vec4 inPosition = UNIT_QUAD[gl_VertexID]; // standard transform TransformCamera cam = getTransformCamera(); - vec3 clipDir = vec3(inPosition.xy, 0.0); + vec3 clipDir = UNIT_QUAD[gl_VertexID].xyz; vec3 eyeDir; <$transformClipToEyeDir(cam, clipDir, eyeDir)$> <$transformEyeToWorldDir(cam, eyeDir, _normal)$> + +<@if not HIFI_USE_FORWARD@> + _prevPositionCS = cam._previousProjectionViewUntranslated * (cam._viewInverse * (cam._projectionInverse * vec4(clipDir, 1.0))); +<@endif@> // Position is supposed to come in clip space - gl_Position = vec4(inPosition.xy, 0.0, 1.0); + gl_Position = vec4(clipDir, 1.0); - <$transformStereoClipsSpace(cam, gl_Position)$> + <$transformStereoClipSpace(gl_Position)$> } diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 432e02fe10..f96dd7f4ed 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -43,15 +43,15 @@ void ProceduralSkybox::clear() { Skybox::clear(); } -void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& frustum, bool forward) const { +void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& frustum, bool forward, uint transformSlot) const { if (_procedural.isReady()) { - ProceduralSkybox::render(batch, frustum, (*this), forward); + ProceduralSkybox::render(batch, frustum, (*this), forward, transformSlot); } else { - Skybox::render(batch, frustum, forward); + Skybox::render(batch, frustum, forward, transformSlot); } } -void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const ProceduralSkybox& skybox, bool forward) { +void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const ProceduralSkybox& skybox, bool forward, uint transformSlot) { glm::mat4 projMat; viewFrustum.evalProjectionMatrix(projMat); @@ -59,6 +59,8 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, viewFrustum.evalViewTransform(viewTransform); batch.setProjectionTransform(projMat); batch.setViewTransform(viewTransform); + // This is needed if we want to have motion vectors on the sky + batch.saveViewProjectionTransform(transformSlot); batch.setModelTransform(Transform()); // only for Mac auto& procedural = skybox._procedural; diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.h b/libraries/procedural/src/procedural/ProceduralSkybox.h index 983b432089..122eeafbf9 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.h +++ b/libraries/procedural/src/procedural/ProceduralSkybox.h @@ -26,8 +26,8 @@ public: bool empty() override; void clear() override; - void render(gpu::Batch& batch, const ViewFrustum& frustum, bool forward) const override; - static void render(gpu::Batch& batch, const ViewFrustum& frustum, const ProceduralSkybox& skybox, bool forward); + void render(gpu::Batch& batch, const ViewFrustum& frustum, bool forward, uint transformSlot) const override; + static void render(gpu::Batch& batch, const ViewFrustum& frustum, const ProceduralSkybox& skybox, bool forward, uint transformSlot); uint64_t getCreated() const { return _created; } diff --git a/libraries/procedural/src/procedural/proceduralSkybox.slf b/libraries/procedural/src/procedural/proceduralSkybox.slf index f938e0b9a2..b6c68faa5c 100644 --- a/libraries/procedural/src/procedural/proceduralSkybox.slf +++ b/libraries/procedural/src/procedural/proceduralSkybox.slf @@ -6,27 +6,21 @@ // // Created by Sam Gateau on 5/5/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // <@include graphics/ShaderConstants.h@> -LAYOUT(binding=GRAPHICS_TEXTURE_SKYBOX) uniform samplerCube cubeMap; - -struct Skybox { - vec4 color; -}; - -LAYOUT(binding=GRAPHICS_BUFFER_SKYBOX_PARAMS) uniform skyboxBuffer { - Skybox skybox; -}; - -layout(location=0) in vec3 _normal; -layout(location=0) out vec4 _fragColor; +<@include graphics/skybox.slh@> +<$declarePackDeferredFragmentSky()$> <@include procedural/ProceduralCommon.slh@> +layout(location=0) in vec3 _normal; +layout(location=1) in vec4 _prevPositionCS; + #line 1001 //PROCEDURAL_BLOCK_BEGIN vec3 getSkyboxColor() { @@ -42,5 +36,6 @@ void main(void) { color = max(color, vec3(0)); // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline color = pow(color, vec3(2.2)); - _fragColor = vec4(color, 1.0); + + packDeferredFragmentSky(_prevPositionCS, color, _normal); } diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index ac0eed9417..444431fabf 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -4,6 +4,7 @@ // // Created by Raffi Bedikian on 8/30/15 // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -13,7 +14,6 @@ #include -#include #include #include #include @@ -21,11 +21,6 @@ #include "render-utils/ShaderConstants.h" #include "StencilMaskPass.h" -#include "TextureCache.h" -#include "DependencyManager.h" -#include "ViewFrustum.h" -#include "GeometryCache.h" -#include "FramebufferCache.h" #include "RandomAndNoise.h" namespace ru { @@ -38,129 +33,149 @@ namespace gr { using graphics::slot::buffer::Buffer; } -#if !ANTIALIASING_USE_TAA +gpu::PipelinePointer Antialiasing::_antialiasingPipeline; +gpu::PipelinePointer Antialiasing::_intensityPipeline; +gpu::PipelinePointer Antialiasing::_blendPipeline; +gpu::PipelinePointer Antialiasing::_debugBlendPipeline; -Antialiasing::Antialiasing() { - _geometryId = DependencyManager::get()->allocateID(); -} + #define TAA_JITTER_SEQUENCE_LENGTH 16 -Antialiasing::~Antialiasing() { - auto geometryCache = DependencyManager::get(); - if (geometryCache) { - geometryCache->releaseID(_geometryId); - } -} - -const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { - if (!_antialiasingPipeline) { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::fxaa); - gpu::StatePointer state = std::make_shared(); - - state->setDepthTest(false, false, gpu::LESS_EQUAL); - PrepareStencil::testNoAA(*state); - - // Good to go add the brand new pipeline - _antialiasingPipeline = gpu::Pipeline::create(program, state); - } - - return _antialiasingPipeline; -} - -const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { - if (!_blendPipeline) { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::fxaa_blend); - gpu::StatePointer state = std::make_shared(); - state->setDepthTest(false, false, gpu::LESS_EQUAL); - PrepareStencil::testNoAA(*state); - - // Good to go add the brand new pipeline - _blendPipeline = gpu::Pipeline::create(program, state); - } - return _blendPipeline; -} - -void Antialiasing::run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceBuffer) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - - RenderArgs* args = renderContext->args; - - gpu::doInBatch("Antialiasing::run", args->_context, [&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setViewportTransform(args->_viewport); - - if (!_paramsBuffer) { - _paramsBuffer = std::make_shared(sizeof(glm::vec4), nullptr); - } - - { - int width = args->_viewport.z; - int height = args->_viewport.w; - if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) { - _antialiasingBuffer.reset(); - } - - if (!_antialiasingBuffer) { - // Link the antialiasing FBO to texture - _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); - auto format = gpu::Element::COLOR_SRGBA_32; - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - _antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); - _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); - glm::vec2 fbExtent { args->_viewport.z, args->_viewport.w }; - glm::vec2 inverseFbExtent = 1.0f / fbExtent; - _paramsBuffer->setSubData(0, glm::vec4(inverseFbExtent, 0.0, 0.0)); - } - } - - - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat, true); - batch.setModelTransform(Transform()); - - // FXAA step - auto pipeline = getAntialiasingPipeline(); - batch.setResourceTexture(0, sourceBuffer->getRenderBuffer(0)); - batch.setFramebuffer(_antialiasingBuffer); - batch.setPipeline(pipeline); - batch.setUniformBuffer(0, _paramsBuffer); - batch.draw(gpu::TRIANGLE_STRIP, 4); - - // Blend step - batch.setResourceTexture(0, _antialiasingTexture); - batch.setFramebuffer(sourceBuffer); - batch.setPipeline(getBlendPipeline()); - batch.draw(gpu::TRIANGLE_STRIP, 4); - }); -} -#else - -void AntialiasingConfig::setAAMode(int mode) { - _mode = std::min((int)AntialiasingConfig::MODE_COUNT, std::max(0, mode)); // Just use unsigned? +void AntialiasingSetupConfig::setIndex(int current) { + _index = (current + TAA_JITTER_SEQUENCE_LENGTH) % TAA_JITTER_SEQUENCE_LENGTH; emit dirty(); } +void AntialiasingSetupConfig::setState(int state) { + _state = (state) % 3; + switch (_state) { + case 0: { + none(); + break; + } + case 1: { + pause(); + break; + } + case 2: + default: { + play(); + break; + } + } + emit dirty(); +} + +int AntialiasingSetupConfig::cycleStopPauseRun() { + _state = (_state + 1) % 3; + switch (_state) { + case 0: { + return none(); + break; + } + case 1: { + return pause(); + break; + } + case 2: + default: { + return play(); + break; + } + } + return _state; +} + +int AntialiasingSetupConfig::prev() { + setIndex(_index - 1); + return _index; +} + +int AntialiasingSetupConfig::next() { + setIndex(_index + 1); + return _index; +} + +int AntialiasingSetupConfig::none() { + _state = 0; + stop = true; + freeze = false; + setIndex(-1); + return _state; +} + +int AntialiasingSetupConfig::pause() { + _state = 1; + stop = false; + freeze = true; + setIndex(0); + return _state; +} + +int AntialiasingSetupConfig::play() { + _state = 2; + stop = false; + freeze = false; + setIndex(0); + return _state; +} + +void AntialiasingSetupConfig::setAAMode(int mode) { + this->mode = glm::clamp(mode, 0, (int)AntialiasingSetupConfig::MODE_COUNT); + emit dirty(); +} + +AntialiasingSetup::AntialiasingSetup() { + _sampleSequence.reserve(TAA_JITTER_SEQUENCE_LENGTH + 1); + // Fill in with jitter samples + for (int i = 0; i < TAA_JITTER_SEQUENCE_LENGTH; i++) { + _sampleSequence.emplace_back(glm::vec2(halton::evaluate<2>(i), halton::evaluate<3>(i)) - vec2(0.5f)); + } +} + +void AntialiasingSetup::configure(const Config& config) { + _isStopped = config.stop; + _isFrozen = config.freeze; + + if (config.freeze) { + _freezedSampleIndex = config.getIndex(); + } + _scale = config.scale; + + _mode = config.mode; +} + +void AntialiasingSetup::run(const render::RenderContextPointer& renderContext, Output& output) { + assert(renderContext->args); + if (!_isStopped && _mode == AntialiasingSetupConfig::Mode::TAA) { + RenderArgs* args = renderContext->args; + + gpu::doInBatch("AntialiasingSetup::run", args->_context, [&](gpu::Batch& batch) { + auto offset = 0; + auto count = _sampleSequence.size(); + if (_isFrozen) { + count = 1; + offset = _freezedSampleIndex; + } + batch.setProjectionJitterSequence(_sampleSequence.data() + offset, count); + batch.setProjectionJitterScale(_scale); + }); + } + + output = _mode; +} + Antialiasing::Antialiasing(bool isSharpenEnabled) : _isSharpenEnabled{ isSharpenEnabled } { } Antialiasing::~Antialiasing() { - _antialiasingBuffers.reset(); - _antialiasingTextures[0].reset(); - _antialiasingTextures[1].reset(); + _antialiasingBuffers.clear(); } -const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(const render::RenderContextPointer& renderContext) { - +const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { if (!_antialiasingPipeline) { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::taa); gpu::StatePointer state = std::make_shared(); - - PrepareStencil::testNoAA(*state); // Good to go add the brand new pipeline _antialiasingPipeline = gpu::Pipeline::create(program, state); @@ -169,11 +184,24 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(const render:: return _antialiasingPipeline; } +const gpu::PipelinePointer& Antialiasing::getIntensityPipeline() { + if (!_intensityPipeline) { + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawWhite); + gpu::StatePointer state = std::make_shared(); + + PrepareStencil::testNoAA(*state); + + // Good to go add the brand new pipeline + _intensityPipeline = gpu::Pipeline::create(program, state); + } + + return _intensityPipeline; +} + const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { if (!_blendPipeline) { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::fxaa_blend); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::aa_blend); gpu::StatePointer state = std::make_shared(); - PrepareStencil::testNoAA(*state); // Good to go add the brand new pipeline _blendPipeline = gpu::Pipeline::create(program, state); } @@ -186,7 +214,6 @@ const gpu::PipelinePointer& Antialiasing::getDebugBlendPipeline() { gpu::StatePointer state = std::make_shared(); PrepareStencil::testNoAA(*state); - // Good to go add the brand new pipeline _debugBlendPipeline = gpu::Pipeline::create(program, state); } @@ -194,12 +221,11 @@ const gpu::PipelinePointer& Antialiasing::getDebugBlendPipeline() { } void Antialiasing::configure(const Config& config) { - _mode = (AntialiasingConfig::Mode) config.getAAMode(); - _sharpen = config.sharpen * 0.25f; if (!_isSharpenEnabled) { _sharpen = 0.0f; } + _params.edit().setSharpenedOutput(_sharpen > 0.0f); _params.edit().blend = config.blend * config.blend; _params.edit().covarianceGamma = config.covarianceGamma; @@ -209,7 +235,9 @@ void Antialiasing::configure(const Config& config) { _params.edit().debugShowVelocityThreshold = config.debugShowVelocityThreshold; _params.edit().regionInfo.x = config.debugX; - _params.edit().regionInfo.z = config.debugFXAAX; + _debugFXAAX = config.debugFXAAX; + + _params.edit().setBicubicHistoryFetch(config.bicubicHistoryFetch); _params.edit().setDebug(config.debug); _params.edit().setShowDebugCursor(config.showCursorPixel); @@ -220,58 +248,83 @@ void Antialiasing::configure(const Config& config) { } -void Antialiasing::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { +void Antialiasing::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output) { assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; auto& deferredFrameTransform = inputs.get0(); - auto& sourceBuffer = inputs.get1(); - auto& linearDepthBuffer = inputs.get2(); - auto& velocityBuffer = inputs.get3(); - + const auto& deferredFrameBuffer = inputs.get1(); + const auto& sourceBuffer = deferredFrameBuffer->getLightingFramebuffer(); + const auto& linearDepthBuffer = inputs.get2(); + const auto& velocityTexture = deferredFrameBuffer->getDeferredVelocityTexture(); + const auto& mode = inputs.get3(); + + _params.edit().regionInfo.z = mode == AntialiasingSetupConfig::Mode::TAA ? _debugFXAAX : 0.0f; + int width = sourceBuffer->getWidth(); int height = sourceBuffer->getHeight(); - if (_antialiasingBuffers && _antialiasingBuffers->get(0) && _antialiasingBuffers->get(0)->getSize() != uvec2(width, height)) { - _antialiasingBuffers.reset(); - _antialiasingTextures[0].reset(); - _antialiasingTextures[1].reset(); + if (_antialiasingBuffers._swapChain && _antialiasingBuffers._swapChain->get(0) && _antialiasingBuffers._swapChain->get(0)->getSize() != uvec2(width, height)) { + _antialiasingBuffers.clear(); } - - if (!_antialiasingBuffers) { + if (!_antialiasingBuffers._swapChain || !_intensityFramebuffer) { std::vector antiAliasingBuffers; // Link the antialiasing FBO to texture - auto format = sourceBuffer->getRenderBuffer(0)->getTexelFormat(); + auto format = gpu::Element(gpu::VEC4, gpu::HALF, gpu::RGBA); auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP); for (int i = 0; i < 2; i++) { antiAliasingBuffers.emplace_back(gpu::Framebuffer::create("antialiasing")); const auto& antiAliasingBuffer = antiAliasingBuffers.back(); - _antialiasingTextures[i] = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); - antiAliasingBuffer->setRenderBuffer(0, _antialiasingTextures[i]); + _antialiasingBuffers._textures[i] = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); + antiAliasingBuffer->setRenderBuffer(0, _antialiasingBuffers._textures[i]); } - _antialiasingBuffers = std::make_shared(antiAliasingBuffers); + _antialiasingBuffers._swapChain = std::make_shared(antiAliasingBuffers); + + _intensityTexture = gpu::Texture::createRenderBuffer(gpu::Element::COLOR_R_8, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); + _intensityFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("taaIntensity")); + _intensityFramebuffer->setRenderBuffer(0, _intensityTexture); + _intensityFramebuffer->setStencilBuffer(deferredFrameBuffer->getDeferredFramebuffer()->getDepthStencilBuffer(), deferredFrameBuffer->getDeferredFramebuffer()->getDepthStencilBufferFormat()); } - + + output = _intensityTexture; + gpu::doInBatch("Antialiasing::run", args->_context, [&](gpu::Batch& batch) { + PROFILE_RANGE_BATCH(batch, "TAA"); + batch.enableStereo(false); batch.setViewportTransform(args->_viewport); + // Set the intensity buffer to 1 except when the stencil is masked as NoAA, where it should be 0 + // This is a bit of a hack as it is not possible and not portable to use the stencil value directly + // as a texture + batch.setFramebuffer(_intensityFramebuffer); + batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, gpu::Vec4(0.0f)); + batch.setResourceTexture(0, nullptr); + batch.setPipeline(getIntensityPipeline()); + batch.draw(gpu::TRIANGLE_STRIP, 4); + // TAA step - getAntialiasingPipeline(renderContext); - batch.setResourceFramebufferSwapChainTexture(ru::Texture::TaaHistory, _antialiasingBuffers, 0); + if (!_params->isFXAAEnabled()) { + batch.setResourceFramebufferSwapChainTexture(ru::Texture::TaaHistory, _antialiasingBuffers._swapChain, 0); + batch.setResourceTexture(ru::Texture::TaaVelocity, velocityTexture); + } else { + batch.setResourceTexture(ru::Texture::TaaHistory, nullptr); + batch.setResourceTexture(ru::Texture::TaaVelocity, nullptr); + } + batch.setResourceTexture(ru::Texture::TaaSource, sourceBuffer->getRenderBuffer(0)); - batch.setResourceTexture(ru::Texture::TaaVelocity, velocityBuffer->getVelocityTexture()); - // This is only used during debug + batch.setResourceTexture(ru::Texture::TaaIntensity, _intensityTexture); + + // This is only used during debug batch.setResourceTexture(ru::Texture::TaaDepth, linearDepthBuffer->getLinearDepthTexture()); batch.setUniformBuffer(ru::Buffer::TaaParams, _params); batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, deferredFrameTransform->getFrameTransformBuffer()); - - batch.setFramebufferSwapChain(_antialiasingBuffers, 1); - batch.setPipeline(getAntialiasingPipeline(renderContext)); + + batch.setFramebufferSwapChain(_antialiasingBuffers._swapChain, 1); + batch.setPipeline(getAntialiasingPipeline()); batch.draw(gpu::TRIANGLE_STRIP, 4); // Blend step @@ -280,11 +333,11 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setFramebuffer(sourceBuffer); if (_params->isDebug()) { batch.setPipeline(getDebugBlendPipeline()); - batch.setResourceFramebufferSwapChainTexture(ru::Texture::TaaNext, _antialiasingBuffers, 1); - } else { + batch.setResourceFramebufferSwapChainTexture(ru::Texture::TaaNext, _antialiasingBuffers._swapChain, 1); + } else { batch.setPipeline(getBlendPipeline()); - // Must match the bindg point in the fxaa_blend.slf shader - batch.setResourceFramebufferSwapChainTexture(0, _antialiasingBuffers, 1); + // Must match the binding point in the aa_blend.slf shader + batch.setResourceFramebufferSwapChainTexture(0, _antialiasingBuffers._swapChain, 1); // Disable sharpen if FXAA if (!_blendParamsBuffer) { _blendParamsBuffer = std::make_shared(sizeof(glm::vec4), nullptr); @@ -293,8 +346,8 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setUniformBuffer(0, _blendParamsBuffer); } batch.draw(gpu::TRIANGLE_STRIP, 4); - batch.advance(_antialiasingBuffers); - + batch.advance(_antialiasingBuffers._swapChain); + batch.setUniformBuffer(ru::Buffer::TaaParams, nullptr); batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, nullptr); @@ -302,114 +355,8 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setResourceTexture(ru::Texture::TaaHistory, nullptr); batch.setResourceTexture(ru::Texture::TaaVelocity, nullptr); batch.setResourceTexture(ru::Texture::TaaNext, nullptr); + + // Reset jitter sequence + batch.setProjectionJitterSequence(nullptr, 0); }); } - -void JitterSampleConfig::setIndex(int current) { - _index = (current) % JitterSample::SEQUENCE_LENGTH; - emit dirty(); -} - -void JitterSampleConfig::setState(int state) { - _state = (state) % 3; - switch (_state) { - case 0: { - none(); - break; - } - case 1: { - pause(); - break; - } - case 2: - default: { - play(); - break; - } - } - emit dirty(); -} - -int JitterSampleConfig::cycleStopPauseRun() { - setState((_state + 1) % 3); - return _state; -} - -int JitterSampleConfig::prev() { - setIndex(_index - 1); - return _index; -} - -int JitterSampleConfig::next() { - setIndex(_index + 1); - return _index; -} - -int JitterSampleConfig::none() { - _state = 0; - stop = true; - freeze = false; - setIndex(-1); - return _state; -} - -int JitterSampleConfig::pause() { - _state = 1; - stop = false; - freeze = true; - setIndex(0); - return _state; -} - - -int JitterSampleConfig::play() { - _state = 2; - stop = false; - freeze = false; - setIndex(0); - return _state; -} - -JitterSample::SampleSequence::SampleSequence(){ - // Halton sequence (2,3) - - for (int i = 0; i < SEQUENCE_LENGTH; i++) { - offsets[i] = glm::vec2(halton::evaluate<2>(i), halton::evaluate<3>(i)); - offsets[i] -= vec2(0.5f); - } - offsets[SEQUENCE_LENGTH] = glm::vec2(0.0f); -} - -void JitterSample::configure(const Config& config) { - _freeze = config.stop || config.freeze; - if (config.freeze) { - auto pausedIndex = config.getIndex(); - if (_sampleSequence.currentIndex != pausedIndex) { - _sampleSequence.currentIndex = pausedIndex; - } - } else if (config.stop) { - _sampleSequence.currentIndex = -1; - } else { - _sampleSequence.currentIndex = config.getIndex(); - } - _scale = config.scale; -} - -void JitterSample::run(const render::RenderContextPointer& renderContext, Output& jitter) { - auto& current = _sampleSequence.currentIndex; - if (!_freeze) { - if (current >= 0) { - current = (current + 1) % SEQUENCE_LENGTH; - } else { - current = -1; - } - } - - if (current >= 0) { - jitter = _sampleSequence.offsets[current]; - } else { - jitter = glm::vec2(0.0f); - } -} - -#endif diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index 540810bbc3..c2c4f53057 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -4,6 +4,7 @@ // // Created by Raffi Bedikian on 8/30/15 // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. @@ -18,98 +19,20 @@ #include "render/DrawTask.h" #include "DeferredFrameTransform.h" -#include "VelocityBufferPass.h" +#include "DeferredFramebuffer.h" +#include "SurfaceGeometryPass.h" - -class JitterSampleConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(float scale MEMBER scale NOTIFY dirty) - Q_PROPERTY(bool freeze MEMBER freeze NOTIFY dirty) - Q_PROPERTY(bool stop MEMBER stop NOTIFY dirty) - Q_PROPERTY(int index READ getIndex NOTIFY dirty) - Q_PROPERTY(int state READ getState WRITE setState NOTIFY dirty) -public: - JitterSampleConfig() : render::Job::Config(true) {} - - float scale{ 0.5f }; - bool stop{ false }; - bool freeze{ false }; - - void setIndex(int current); - void setState(int state); - -public slots: - int cycleStopPauseRun(); - int prev(); - int next(); - int none(); - int pause(); - int play(); - - int getIndex() const { return _index; } - int getState() const { return _state; } -signals: - void dirty(); - -private: - int _state{ 0 }; - int _index{ 0 }; - -}; - - -class JitterSample { -public: - - enum { - SEQUENCE_LENGTH = 64 - }; - - using Config = JitterSampleConfig; - using Output = glm::vec2; - using JobModel = render::Job::ModelO; - - void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, Output& jitter); - -private: - - struct SampleSequence { - SampleSequence(); - - glm::vec2 offsets[SEQUENCE_LENGTH + 1]; - int sequenceLength{ SEQUENCE_LENGTH }; - int currentIndex{ 0 }; - }; - - SampleSequence _sampleSequence; - float _scale{ 1.0 }; - bool _freeze{ false }; -}; - - -class AntialiasingConfig : public render::Job::Config { +class AntialiasingSetupConfig : public render::Job::Config { Q_OBJECT + Q_PROPERTY(float scale MEMBER scale NOTIFY dirty) + Q_PROPERTY(bool freeze MEMBER freeze NOTIFY dirty) + Q_PROPERTY(bool stop MEMBER stop NOTIFY dirty) + Q_PROPERTY(int index READ getIndex NOTIFY dirty) + Q_PROPERTY(int state READ getState WRITE setState NOTIFY dirty) Q_PROPERTY(int mode READ getAAMode WRITE setAAMode NOTIFY dirty) - Q_PROPERTY(float blend MEMBER blend NOTIFY dirty) - Q_PROPERTY(float sharpen MEMBER sharpen NOTIFY dirty) - Q_PROPERTY(float covarianceGamma MEMBER covarianceGamma NOTIFY dirty) - - Q_PROPERTY(bool constrainColor MEMBER constrainColor NOTIFY dirty) - Q_PROPERTY(bool feedbackColor MEMBER feedbackColor NOTIFY dirty) - - Q_PROPERTY(bool debug MEMBER debug NOTIFY dirty) - Q_PROPERTY(float debugX MEMBER debugX NOTIFY dirty) - Q_PROPERTY(bool fxaaOnOff READ debugFXAA WRITE setDebugFXAA NOTIFY dirty) - Q_PROPERTY(float debugShowVelocityThreshold MEMBER debugShowVelocityThreshold NOTIFY dirty) - Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty) - Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty) - Q_PROPERTY(float debugOrbZoom MEMBER debugOrbZoom NOTIFY dirty) - - Q_PROPERTY(bool showClosestFragment MEMBER showClosestFragment NOTIFY dirty) public: - AntialiasingConfig() : render::Job::Config(true) {} + AntialiasingSetupConfig() : render::Job::Config(true) {} /*@jsdoc *Antialiasing modes. @@ -133,30 +56,103 @@ public: }; Q_ENUM(Mode) // Stored as signed int. + float scale { 0.75f }; + bool stop { false }; + bool freeze { false }; + int mode { TAA }; + + void setIndex(int current); + void setState(int state); + +public slots: + int cycleStopPauseRun(); + int prev(); + int next(); + int none(); + int pause(); + int play(); + + int getIndex() const { return _index; } + int getState() const { return _state; } + void setAAMode(int mode); - int getAAMode() const { return _mode; } + int getAAMode() const { return mode; } + +signals: + void dirty(); + +private: + int _state { 0 }; + int _index { 0 }; + +}; + +class AntialiasingSetup { +public: + + using Config = AntialiasingSetupConfig; + using Output = int; + using JobModel = render::Job::ModelO; + + AntialiasingSetup(); + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, Output& output); + +private: + + std::vector _sampleSequence; + float _scale { 1.0f }; + int _freezedSampleIndex { 0 }; + bool _isStopped { false }; + bool _isFrozen { false }; + int _mode { AntialiasingSetupConfig::Mode::TAA }; +}; + + +class AntialiasingConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float blend MEMBER blend NOTIFY dirty) + Q_PROPERTY(float sharpen MEMBER sharpen NOTIFY dirty) + Q_PROPERTY(float covarianceGamma MEMBER covarianceGamma NOTIFY dirty) + + Q_PROPERTY(bool constrainColor MEMBER constrainColor NOTIFY dirty) + Q_PROPERTY(bool feedbackColor MEMBER feedbackColor NOTIFY dirty) + Q_PROPERTY(bool bicubicHistoryFetch MEMBER bicubicHistoryFetch NOTIFY dirty) + + Q_PROPERTY(bool debug MEMBER debug NOTIFY dirty) + Q_PROPERTY(float debugX MEMBER debugX NOTIFY dirty) + Q_PROPERTY(bool fxaaOnOff READ debugFXAA WRITE setDebugFXAA NOTIFY dirty) + Q_PROPERTY(float debugShowVelocityThreshold MEMBER debugShowVelocityThreshold NOTIFY dirty) + Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty) + Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty) + Q_PROPERTY(float debugOrbZoom MEMBER debugOrbZoom NOTIFY dirty) + + Q_PROPERTY(bool showClosestFragment MEMBER showClosestFragment NOTIFY dirty) + +public: + AntialiasingConfig() : render::Job::Config(true) {} void setDebugFXAA(bool debug) { debugFXAAX = (debug ? 0.0f : 1.0f); emit dirty();} bool debugFXAA() const { return (debugFXAAX == 0.0f ? true : false); } - int _mode{ TAA }; // '_' prefix but not private? + float blend { 0.2f }; + float sharpen { 0.05f }; - float blend{ 0.25f }; - float sharpen{ 0.05f }; + bool constrainColor { true }; + float covarianceGamma { 1.15f }; + bool feedbackColor { false }; + bool bicubicHistoryFetch { true }; - bool constrainColor{ true }; - float covarianceGamma{ 0.65f }; - bool feedbackColor{ false }; - - float debugX{ 0.0f }; - float debugFXAAX{ 1.0f }; - float debugShowVelocityThreshold{ 1.0f }; - glm::vec2 debugCursorTexcoord{ 0.5f, 0.5f }; - float debugOrbZoom{ 2.0f }; + float debugX { 0.0f }; + float debugFXAAX { 1.0f }; + float debugShowVelocityThreshold { 1.0f }; + glm::vec2 debugCursorTexcoord { 0.5f, 0.5f }; + float debugOrbZoom { 2.0f }; bool debug { false }; bool showCursorPixel { false }; - bool showClosestFragment{ false }; + bool showClosestFragment { false }; signals: void dirty(); @@ -165,19 +161,15 @@ signals: #define SET_BIT(bitfield, bitIndex, value) bitfield = ((bitfield) & ~(1 << (bitIndex))) | ((value) << (bitIndex)) #define GET_BIT(bitfield, bitIndex) ((bitfield) & (1 << (bitIndex))) -#define ANTIALIASING_USE_TAA 1 - -#if ANTIALIASING_USE_TAA - struct TAAParams { - float nope{ 0.0f }; - float blend{ 0.15f }; - float covarianceGamma{ 1.0f }; - float debugShowVelocityThreshold{ 1.0f }; + float nope { 0.0f }; + float blend { 0.15f }; + float covarianceGamma { 0.9f }; + float debugShowVelocityThreshold { 1.0f }; - glm::ivec4 flags{ 0 }; - glm::vec4 pixelInfo{ 0.5f, 0.5f, 2.0f, 0.0f }; - glm::vec4 regionInfo{ 0.0f, 0.0f, 1.0f, 0.0f }; + glm::ivec4 flags { 0 }; + glm::vec4 pixelInfo { 0.5f, 0.5f, 2.0f, 0.0f }; + glm::vec4 regionInfo { 0.0f, 0.0f, 1.0f, 0.0f }; void setConstrainColor(bool enabled) { SET_BIT(flags.y, 1, enabled); } bool isConstrainColor() const { return (bool)GET_BIT(flags.y, 1); } @@ -185,6 +177,12 @@ struct TAAParams { void setFeedbackColor(bool enabled) { SET_BIT(flags.y, 4, enabled); } bool isFeedbackColor() const { return (bool)GET_BIT(flags.y, 4); } + void setBicubicHistoryFetch(bool enabled) { SET_BIT(flags.y, 0, enabled); } + bool isBicubicHistoryFetch() const { return (bool)GET_BIT(flags.y, 0); } + + void setSharpenedOutput(bool enabled) { SET_BIT(flags.y, 2, enabled); } + bool isSharpenedOutput() const { return (bool)GET_BIT(flags.y, 2); } + void setDebug(bool enabled) { SET_BIT(flags.x, 0, enabled); } bool isDebug() const { return (bool) GET_BIT(flags.x, 0); } @@ -199,71 +197,52 @@ struct TAAParams { void setShowClosestFragment(bool enabled) { SET_BIT(flags.x, 3, enabled); } + bool isFXAAEnabled() const { return regionInfo.z == 0.0f; } }; using TAAParamsBuffer = gpu::StructBuffer; class Antialiasing { public: - using Inputs = render::VaryingSet4 < DeferredFrameTransformPointer, gpu::FramebufferPointer, LinearDepthFramebufferPointer, VelocityFramebufferPointer > ; + using Inputs = render::VaryingSet4; + using Outputs = gpu::TexturePointer; using Config = AntialiasingConfig; - using JobModel = render::Job::ModelI; + using JobModel = render::Job::ModelIO; Antialiasing(bool isSharpenEnabled = true); ~Antialiasing(); void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); - const gpu::PipelinePointer& getAntialiasingPipeline(const render::RenderContextPointer& renderContext); + const gpu::PipelinePointer& getAntialiasingPipeline(); + const gpu::PipelinePointer& getIntensityPipeline(); const gpu::PipelinePointer& getBlendPipeline(); const gpu::PipelinePointer& getDebugBlendPipeline(); private: + struct AntialiasingBuffer { + gpu::FramebufferSwapChainPointer _swapChain; + gpu::TexturePointer _textures[2]; - gpu::FramebufferSwapChainPointer _antialiasingBuffers; - gpu::TexturePointer _antialiasingTextures[2]; + void clear() { + _swapChain.reset(); + _textures[0].reset(); + _textures[1].reset(); + } + }; + AntialiasingBuffer _antialiasingBuffers; + gpu::FramebufferPointer _intensityFramebuffer; + gpu::TexturePointer _intensityTexture; gpu::BufferPointer _blendParamsBuffer; - gpu::PipelinePointer _antialiasingPipeline; - gpu::PipelinePointer _blendPipeline; - gpu::PipelinePointer _debugBlendPipeline; + + static gpu::PipelinePointer _antialiasingPipeline; + static gpu::PipelinePointer _intensityPipeline; + static gpu::PipelinePointer _blendPipeline; + static gpu::PipelinePointer _debugBlendPipeline; TAAParamsBuffer _params; - AntialiasingConfig::Mode _mode{ AntialiasingConfig::TAA }; - float _sharpen{ 0.15f }; - bool _isSharpenEnabled{ true }; + float _sharpen { 0.15f }; + bool _isSharpenEnabled { true }; + float _debugFXAAX { 0.0f }; }; - -#else // User setting for antialias mode will probably be broken. -class AntiAliasingConfig : public render::Job::Config { // Not to be confused with AntialiasingConfig... - Q_OBJECT - Q_PROPERTY(bool enabled MEMBER enabled) -public: - AntiAliasingConfig() : render::Job::Config(true) {} -}; - -class Antialiasing { -public: - using Config = AntiAliasingConfig; - using JobModel = render::Job::ModelI; - - Antialiasing(); - ~Antialiasing(); - void configure(const Config& config) {} - void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceBuffer); - - const gpu::PipelinePointer& getAntialiasingPipeline(); - const gpu::PipelinePointer& getBlendPipeline(); - -private: - gpu::FramebufferPointer _antialiasingBuffer; - - gpu::TexturePointer _antialiasingTexture; - gpu::BufferPointer _paramsBuffer; - - gpu::PipelinePointer _antialiasingPipeline; - gpu::PipelinePointer _blendPipeline; - int _geometryId { 0 }; -}; -#endif - #endif // hifi_AntialiasingEffect_h diff --git a/libraries/render-utils/src/BackgroundStage.cpp b/libraries/render-utils/src/BackgroundStage.cpp index 91b766d90b..38fe0aea3d 100644 --- a/libraries/render-utils/src/BackgroundStage.cpp +++ b/libraries/render-utils/src/BackgroundStage.cpp @@ -3,6 +3,7 @@ // // Created by Sam Gateau on 5/9/2017. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -80,6 +81,7 @@ void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext, auto args = renderContext->args; gpu::doInBatch("DrawBackgroundStage::run", args->_context, [&](gpu::Batch& batch) { + PROFILE_RANGE_BATCH(batch, "Background"); args->_batch = &batch; batch.enableSkybox(true); @@ -87,16 +89,11 @@ void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext, batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); + bool forward = args->_renderMethod == render::Args::RenderMethod::FORWARD; + batch.setProjectionJitterEnabled(!forward); // If we're using forward rendering, we need to calculate haze - if (args->_renderMethod == render::Args::RenderMethod::FORWARD) { + if (forward) { const auto& hazeStage = args->_scene->getStage(); if (hazeStage && hazeFrame->_hazes.size() > 0) { const auto& hazePointer = hazeStage->getHaze(hazeFrame->_hazes.front()); @@ -106,7 +103,7 @@ void DrawBackgroundStage::run(const render::RenderContextPointer& renderContext, } } - skybox->render(batch, args->getViewFrustum(), args->_renderMethod == render::Args::RenderMethod::FORWARD); + skybox->render(batch, args->getViewFrustum(), forward, _transformSlot); }); args->_batch = nullptr; } diff --git a/libraries/render-utils/src/BackgroundStage.h b/libraries/render-utils/src/BackgroundStage.h index 3015b721b1..9a43b069df 100644 --- a/libraries/render-utils/src/BackgroundStage.h +++ b/libraries/render-utils/src/BackgroundStage.h @@ -85,9 +85,12 @@ public: using Inputs = render::VaryingSet3; using JobModel = render::Job::ModelI; - DrawBackgroundStage() {} + DrawBackgroundStage(uint transformSlot) : _transformSlot(transformSlot) {} void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + +private: + uint _transformSlot; }; #endif diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index 5cb2f06021..1aae75e97d 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -16,12 +16,15 @@ #include #include "render-utils/ShaderConstants.h" +#include "StencilMaskPass.h" #define BLOOM_BLUR_LEVEL_COUNT 3 BloomThreshold::BloomThreshold(unsigned int downsamplingFactor) { assert(downsamplingFactor > 0); - _parameters.edit()._sampleCount = downsamplingFactor; + auto& params = _parameters.edit(); + params._sampleCount = downsamplingFactor; + params._offset = (1.0f - downsamplingFactor) * 0.5f; } void BloomThreshold::configure(const Config& config) {} @@ -50,11 +53,6 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons auto inputBuffer = inputFrameBuffer->getRenderBuffer(0); auto bufferSize = gpu::Vec2u(inputBuffer->getDimensions()); - const auto downSamplingFactor = _parameters.get()._sampleCount; - - // Downsample resolution - bufferSize.x /= downSamplingFactor; - bufferSize.y /= downSamplingFactor; if (!_outputBuffer || _outputBuffer->getSize() != bufferSize) { auto colorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(inputBuffer->getTexelFormat(), bufferSize.x, bufferSize.y, @@ -62,6 +60,7 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons _outputBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("BloomThreshold")); _outputBuffer->setRenderBuffer(0, colorTexture); + _outputBuffer->setStencilBuffer(inputFrameBuffer->getDepthStencilBuffer(), inputFrameBuffer->getDepthStencilBufferFormat()); _parameters.edit()._deltaUV = { 1.0f / bufferSize.x, 1.0f / bufferSize.y }; } diff --git a/libraries/render-utils/src/BloomThreshold.shared.slh b/libraries/render-utils/src/BloomThreshold.shared.slh index 5ad490a1ca..e1bcae11ea 100644 --- a/libraries/render-utils/src/BloomThreshold.shared.slh +++ b/libraries/render-utils/src/BloomThreshold.shared.slh @@ -8,8 +8,10 @@ struct Parameters { BT_VEC2 _deltaUV; + float _offset; float _threshold; int _sampleCount; + float _padding[3]; }; // <@if 1@> diff --git a/libraries/render-utils/src/BloomThreshold.slf b/libraries/render-utils/src/BloomThreshold.slf index bbf863994f..1a12f070ba 100644 --- a/libraries/render-utils/src/BloomThreshold.slf +++ b/libraries/render-utils/src/BloomThreshold.slf @@ -17,11 +17,10 @@ LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_BLOOM_PARAMS) uniform parametersBuffer Parameters parameters; }; -layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; void main(void) { - vec2 startUv = varTexCoord0; + vec2 startUv = (vec2(gl_FragCoord.xy) + vec2(parameters._offset)) * parameters._deltaUV; vec4 maskedColor = vec4(0,0,0,0); for (int y=0 ; y 1e-4 ? velColor : vec4(0.0f, 0.0f, 1.0f, 0.0f);" + "}" +}; + +static const std::string DEFAULT_ANTIALIASING_INTENSITY_SHADER{ + "vec4 getFragmentColor() {" + " return vec4(texture(debugTexture0, uv).rrr, 1.0);" " }" }; @@ -251,7 +259,7 @@ static std::string getFileContent(const std::string& fileName, const std::string } #include // TODO REMOVE: Temporary until UI -DebugDeferredBuffer::DebugDeferredBuffer() { +DebugDeferredBuffer::DebugDeferredBuffer(uint transformSlot) : _transformSlot(transformSlot) { // TODO REMOVE: Temporary until UI static const auto DESKTOP_PATH = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); static const auto CUSTOM_FILE = DESKTOP_PATH.toStdString() + "/custom.slh"; @@ -325,6 +333,8 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, const std::strin return DEFAULT_HALF_NORMAL_SHADER; case VelocityMode: return DEFAULT_VELOCITY_SHADER; + case AntialiasingIntensityMode: + return DEFAULT_ANTIALIASING_INTENSITY_SHADER; case CustomMode: return getFileContent(customFile, DEFAULT_CUSTOM_SHADER); default: @@ -401,9 +411,9 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I auto& linearDepthTarget = inputs.get1(); auto& surfaceGeometryFramebuffer = inputs.get2(); auto& ambientOcclusionFramebuffer = inputs.get3(); - auto& velocityFramebuffer = inputs.get4(); - auto& frameTransform = inputs.get5(); - auto& shadowFrame = inputs.get6(); + auto& frameTransform = inputs.get4(); + auto& shadowFrame = inputs.get5(); + const auto& antialiasingIntensityTexture = inputs.get6(); gpu::doInBatch("DebugDeferredBuffer::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); @@ -412,12 +422,7 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I const auto geometryBuffer = DependencyManager::get(); const auto textureCache = DependencyManager::get(); - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat, true); + batch.setSavedViewProjectionTransform(_transformSlot); batch.setModelTransform(Transform()); using Textures = render_utils::slot::texture::Texture; @@ -435,8 +440,8 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Textures::DeferredDepth, deferredFramebuffer->getPrimaryDepthTexture()); batch.setResourceTexture(Textures::DeferredLighting, deferredFramebuffer->getLightingTexture()); } - if (velocityFramebuffer && _mode == VelocityMode) { - batch.setResourceTexture(Textures::DebugTexture0, velocityFramebuffer->getVelocityTexture()); + if (_mode == VelocityMode) { + batch.setResourceTexture(Textures::DebugTexture0, deferredFramebuffer->getDeferredVelocityTexture()); } if (!shadowFrame->_objects.empty()) { @@ -472,6 +477,10 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Textures::DebugTexture0, ambientOcclusionFramebuffer->getNormalTexture()); } } + if (antialiasingIntensityTexture && _mode == AntialiasingIntensityMode) { + batch.setResourceTexture(Textures::DebugTexture0, antialiasingIntensityTexture); + } + const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); const glm::vec2 bottomLeft(_size.x, _size.y); const glm::vec2 topRight(_size.z, _size.w); diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index 5ff3ab28c9..4e4104f221 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -19,7 +19,6 @@ #include "DeferredFramebuffer.h" #include "SurfaceGeometryPass.h" #include "AmbientOcclusionEffect.h" -#include "VelocityBufferPass.h" #include "LightStage.h" @@ -44,13 +43,13 @@ public: LinearDepthFramebufferPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer, - VelocityFramebufferPointer, DeferredFrameTransformPointer, - LightStage::ShadowFramePointer>; + LightStage::ShadowFramePointer, + gpu::TexturePointer>; using Config = DebugDeferredBufferConfig; using JobModel = render::Job::ModelI; - DebugDeferredBuffer(); + DebugDeferredBuffer(uint transformSlot); ~DebugDeferredBuffer(); void configure(const Config& config); @@ -92,6 +91,7 @@ protected: AmbientOcclusionBlurredMode, AmbientOcclusionNormalMode, VelocityMode, + AntialiasingIntensityMode, CustomMode, // Needs to stay last NumModes, @@ -100,6 +100,7 @@ protected: private: Mode _mode{ Off }; glm::vec4 _size; + uint _transformSlot; #include "debug_deferred_buffer_shared.slh" diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index 868b93ff91..3198594825 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -45,6 +45,7 @@ struct DeferredFragment { vec3 fresnel; float roughness; int mode; + int side; float scattering; float depthVal; }; @@ -58,6 +59,9 @@ vec3 getFresnelF0(float metallic, vec3 metalF0) { } <@endif@> +<@include DeferredTransform.slh@> +<$declareDeferredFrameTransform()$> + DeferredFragment unpackDeferredFragmentNoPosition(vec2 texcoord) { vec4 normalVal; vec4 diffuseVal; @@ -82,6 +86,8 @@ DeferredFragment unpackDeferredFragmentNoPosition(vec2 texcoord) { frag.scattering = float(frag.mode == FRAG_MODE_SCATTERING) * specularVal.x; frag.fresnel = getFresnelF0(frag.metallic, diffuseVal.xyz); + frag.side = getStereoSideFromUV(texcoord.x); + return frag; } @@ -109,18 +115,14 @@ DeferredFragment unpackDeferredFragmentNoPositionNoAmbient(vec2 texcoord) { frag.fresnel = getFresnelF0(frag.metallic, diffuseVal.xyz); + frag.side = getStereoSideFromUV(texcoord.x); + return frag; } - -<@include DeferredTransform.slh@> -<$declareDeferredFrameTransform()$> - -vec4 unpackDeferredPosition(float depthValue, vec2 texcoord) { +vec4 unpackDeferredPosition(int side, float depthValue, vec2 texcoord) { float check = float(isStereo()); - float check2 = check * float(texcoord.x > 0.5); - texcoord.x -= check2 * 0.5; - int side = int(check2); + texcoord.x -= check * 0.5 * float(side); texcoord.x *= 1.0 + check; return vec4(evalEyePositionFromZdb(side, depthValue, texcoord), 1.0); @@ -129,7 +131,7 @@ vec4 unpackDeferredPosition(float depthValue, vec2 texcoord) { // This method to unpack position is fastesst vec4 unpackDeferredPositionFromZdb(vec2 texcoord) { float Zdb = texture(depthMap, texcoord).x; - return unpackDeferredPosition(Zdb, texcoord); + return unpackDeferredPosition(getStereoSideFromUV(texcoord.x), Zdb, texcoord); } vec4 unpackDeferredPositionFromZeye(vec2 texcoord) { @@ -144,13 +146,13 @@ vec4 unpackDeferredPositionFromZeye(vec2 texcoord) { return vec4(evalEyePositionFromZeye(side, Zeye, texcoord), 1.0); } -DeferredFragment unpackDeferredFragment(DeferredFrameTransform deferredTransform, vec2 texcoord) { +DeferredFragment unpackDeferredFragment(vec2 texcoord) { float depthValue = texture(depthMap, texcoord).r; DeferredFragment frag = unpackDeferredFragmentNoPosition(texcoord); frag.depthVal = depthValue; - frag.position = unpackDeferredPosition(frag.depthVal, texcoord); + frag.position = unpackDeferredPosition(frag.side, frag.depthVal, texcoord); return frag; } diff --git a/libraries/render-utils/src/DeferredBufferWrite.slh b/libraries/render-utils/src/DeferredBufferWrite.slh index de3d0a3087..99e62153fa 100644 --- a/libraries/render-utils/src/DeferredBufferWrite.slh +++ b/libraries/render-utils/src/DeferredBufferWrite.slh @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 1/12/15. // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -12,12 +13,14 @@ <@def DEFERRED_BUFFER_WRITE_SLH@> <@include DeferredBuffer.slh@> +<@include DeferredBufferWrite_shared.slh@> - -layout(location=0) out vec4 _fragColor0; // albedo / metallic -layout(location=1) out vec4 _fragColor1; // Normal -layout(location=2) out vec4 _fragColor2; // scattering / emissive / occlusion -layout(location=3) out vec4 _fragColor3; // emissive +// Must match layout in skybox.slh +layout(location = DEFERRED_COLOR_SLOT) out vec4 _albedoMetallic; // albedo / metallic +layout(location = DEFERRED_NORMAL_SLOT) out vec4 _normalRoughness; // normal / roughness +layout(location = DEFERRED_SPECULAR_SLOT) out vec4 _scatteringEmissiveOcclusion; // scattering / emissive / occlusion +layout(location = DEFERRED_VELOCITY_SLOT) out vec4 _velocity; // velocity +layout(location = DEFERRED_LIGHTING_SLOT) out vec4 _lighting; // emissive // the alpha threshold const float alphaThreshold = 0.5; @@ -25,51 +28,67 @@ float evalOpaqueFinalAlpha(float alpha, float mapAlpha) { return mix(alpha, 1.0 - alpha, step(mapAlpha, alphaThreshold)); } +<@include VelocityWrite.slh@> <@include DefaultMaterials.slh@> <@include LightingModel.slh@> -void packDeferredFragment(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 emissive, float occlusion, float scattering) { +void packDeferredFragment(vec4 prevPositionCS, vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 emissive, float occlusion, float scattering) { if (alpha < 1.0) { discard; } float check = float(scattering > 0.0); - _fragColor0 = vec4(albedo, mix(packShadedMetallic(metallic), packScatteringMetallic(metallic), check)); - _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); - _fragColor2 = vec4(mix(emissive, vec3(scattering), check), occlusion); - _fragColor3 = vec4(isEmissiveEnabled() * emissive, 1.0); + _albedoMetallic = vec4(albedo, mix(packShadedMetallic(metallic), packScatteringMetallic(metallic), check)); + _normalRoughness = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); + _scatteringEmissiveOcclusion = vec4(mix(emissive, vec3(scattering), check), occlusion); + _velocity = vec4(packVelocity(prevPositionCS), 0.0, 0.0); + _lighting = vec4(isEmissiveEnabled() * emissive, 1.0); } -void packDeferredFragmentLightmap(vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 lightmap) { +void packDeferredFragmentLightmap(vec4 prevPositionCS, vec3 normal, float alpha, vec3 albedo, float roughness, float metallic, vec3 lightmap) { if (alpha < 1.0) { discard; } - _fragColor0 = vec4(albedo, packLightmappedMetallic(metallic)); - _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); - _fragColor2 = vec4(isLightmapEnabled() * lightmap, 1.0); - _fragColor3 = vec4(isLightmapEnabled() * lightmap * albedo, 1.0); + _albedoMetallic = vec4(albedo, packLightmappedMetallic(metallic)); + _normalRoughness = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); + _scatteringEmissiveOcclusion = vec4(isLightmapEnabled() * lightmap, 1.0); + _velocity = vec4(packVelocity(prevPositionCS), 0.0, 0.0); + _lighting = vec4(isLightmapEnabled() * lightmap * albedo, 1.0); } -void packDeferredFragmentUnlit(vec3 normal, float alpha, vec3 color) { +void packDeferredFragmentUnlit(vec4 prevPositionCS, vec3 normal, float alpha, vec3 color) { // to reduce texel flickering for floating point error we discard when alpha is "almost one" if (alpha < 0.999999) { discard; } - _fragColor0 = vec4(color, packUnlit()); - _fragColor1 = vec4(packNormal(normal), 1.0); - _fragColor2 = vec4(vec3(0.0), 1.0); - _fragColor3 = vec4(color, 1.0); + _albedoMetallic = vec4(color, packUnlit()); + _normalRoughness = vec4(packNormal(normal), 1.0); + _scatteringEmissiveOcclusion = vec4(vec3(0.0), 1.0); + _velocity = vec4(packVelocity(prevPositionCS), 0.0, 0.0); + _lighting = vec4(color, 1.0); } -void packDeferredFragmentTranslucent(vec3 normal, float alpha, vec3 albedo, float roughness) { +void packDeferredFragmentTranslucent(vec4 prevPositionCS, vec3 normal, float alpha, vec3 albedo, float roughness) { if (alpha <= 0.0) { discard; } - _fragColor0 = vec4(albedo.rgb, alpha); - _fragColor1 = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); - _fragColor2 = vec4(vec3(0.0), 1.0); - _fragColor3 = vec4(0.0); + _albedoMetallic = vec4(albedo.rgb, alpha); + _normalRoughness = vec4(packNormal(normal), clamp(roughness, 0.0, 1.0)); + _scatteringEmissiveOcclusion = vec4(vec3(0.0), 1.0); + _velocity = vec4(packVelocity(prevPositionCS), 0.0, 0.0); + _lighting = vec4(0.0); +} + +void packDeferredFragmentTranslucentUnlit(vec4 prevPositionCS, vec3 normal, float alpha, vec3 color) { + if (alpha <= 0.0) { + discard; + } + _albedoMetallic = vec4(color, alpha); + _normalRoughness = vec4(packNormal(normal), 1.0); + _scatteringEmissiveOcclusion = vec4(vec3(0.0), 1.0); + _velocity = vec4(packVelocity(prevPositionCS), 0.0, 0.0); + _lighting = vec4(color, 1.0); } <@endif@> diff --git a/libraries/render-utils/src/DeferredBufferWrite_shared.slh b/libraries/render-utils/src/DeferredBufferWrite_shared.slh new file mode 100644 index 0000000000..a1954d1062 --- /dev/null +++ b/libraries/render-utils/src/DeferredBufferWrite_shared.slh @@ -0,0 +1,12 @@ +// glsl / C++ compatible source as interface for DeferredBuffer layout + +#define DEFERRED_COLOR_SLOT 0 +#define DEFERRED_NORMAL_SLOT 1 +#define DEFERRED_SPECULAR_SLOT 2 +#define DEFERRED_VELOCITY_SLOT 3 +#define DEFERRED_LIGHTING_SLOT 4 + + // <@if 1@> + // Trigger Scribe include + // <@endif@> +// diff --git a/libraries/render-utils/src/DeferredFrameTransform.cpp b/libraries/render-utils/src/DeferredFrameTransform.cpp index 21d5b120d6..5c379a0324 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.cpp +++ b/libraries/render-utils/src/DeferredFrameTransform.cpp @@ -18,73 +18,51 @@ DeferredFrameTransform::DeferredFrameTransform() { _frameTransformBuffer = gpu::BufferView(std::make_shared(sizeof(FrameTransform), (const gpu::Byte*) &frameTransform)); } -void DeferredFrameTransform::update(RenderArgs* args, glm::vec2 jitter) { +void DeferredFrameTransform::update(RenderArgs* args) { // Update the depth info with near and far (same for stereo) auto nearZ = args->getViewFrustum().getNearClip(); auto farZ = args->getViewFrustum().getFarClip(); auto& frameTransformBuffer = _frameTransformBuffer.edit(); - frameTransformBuffer.depthInfo = glm::vec4(nearZ*farZ, farZ - nearZ, -farZ, 0.0f); + frameTransformBuffer.infos.depthInfo = glm::vec4(nearZ * farZ, farZ - nearZ, -farZ, 0.0f); + frameTransformBuffer.infos.pixelInfo = args->_viewport; - frameTransformBuffer.pixelInfo = args->_viewport; - - //_parametersBuffer.edit()._ditheringInfo.y += 0.25f; - - Transform cameraTransform; - args->getViewFrustum().evalViewTransform(cameraTransform); - cameraTransform.getMatrix(frameTransformBuffer.invView); - cameraTransform.getInverseMatrix(frameTransformBuffer.view); - - args->getViewFrustum().evalProjectionMatrix(frameTransformBuffer.projectionMono); - - // There may be some sort of mismatch here if the viewport size isn't the same as the frame buffer size as - // jitter is normalized by frame buffer size in TransformCamera. But we should be safe. - jitter.x /= args->_viewport.z; - jitter.y /= args->_viewport.w; + args->getViewFrustum().evalProjectionMatrix(frameTransformBuffer.infos.projectionMono); // Running in stereo ? bool isStereo = args->isStereo(); if (!isStereo) { - frameTransformBuffer.projectionUnjittered[0] = frameTransformBuffer.projectionMono; - frameTransformBuffer.invProjectionUnjittered[0] = glm::inverse(frameTransformBuffer.projectionUnjittered[0]); - - frameTransformBuffer.stereoInfo = glm::vec4(0.0f, (float)args->_viewport.z, 0.0f, 0.0f); - frameTransformBuffer.invpixelInfo = glm::vec4(1.0f / args->_viewport.z, 1.0f / args->_viewport.w, 0.0f, 0.0f); - - frameTransformBuffer.projection[0] = frameTransformBuffer.projectionUnjittered[0]; - frameTransformBuffer.projection[0][2][0] += jitter.x; - frameTransformBuffer.projection[0][2][1] += jitter.y; - frameTransformBuffer.invProjection[0] = glm::inverse(frameTransformBuffer.projection[0]); + frameTransformBuffer.infos.stereoInfo = glm::vec4(0.0f, (float)args->_viewport.z, 0.0f, 0.0f); + frameTransformBuffer.infos.invPixelInfo = glm::vec4(1.0f / args->_viewport.z, 1.0f / args->_viewport.w, 0.0f, 0.0f); } else { - - mat4 projMats[2]; - mat4 eyeViews[2]; - args->_context->getStereoProjections(projMats); - args->_context->getStereoViews(eyeViews); - - jitter.x *= 2.0f; - - for (int i = 0; i < 2; i++) { - // Compose the mono Eye space to Stereo clip space Projection Matrix - auto sideViewMat = projMats[i] * eyeViews[i]; - frameTransformBuffer.projectionUnjittered[i] = sideViewMat; - frameTransformBuffer.invProjectionUnjittered[i] = glm::inverse(sideViewMat); - - frameTransformBuffer.projection[i] = frameTransformBuffer.projectionUnjittered[i]; - frameTransformBuffer.projection[i][2][0] += jitter.x; - frameTransformBuffer.projection[i][2][1] += jitter.y; - frameTransformBuffer.invProjection[i] = glm::inverse(frameTransformBuffer.projection[i]); - } - - frameTransformBuffer.stereoInfo = glm::vec4(1.0f, (float)(args->_viewport.z >> 1), 0.0f, 1.0f); - frameTransformBuffer.invpixelInfo = glm::vec4(1.0f / (float)(args->_viewport.z >> 1), 1.0f / args->_viewport.w, 0.0f, 0.0f); + frameTransformBuffer.infos.pixelInfo.z *= 0.5f; + frameTransformBuffer.infos.stereoInfo = glm::vec4(1.0f, (float)(args->_viewport.z >> 1), 0.0f, 1.0f); + frameTransformBuffer.infos.invPixelInfo = glm::vec4(2.0f / (float)(args->_viewport.z), 1.0f / args->_viewport.w, 0.0f, 0.0f); } } -void GenerateDeferredFrameTransform::run(const render::RenderContextPointer& renderContext, const Input& jitter, Output& frameTransform) { +void GenerateDeferredFrameTransform::run(const render::RenderContextPointer& renderContext, Output& frameTransform) { if (!frameTransform) { frameTransform = std::make_shared(); } - frameTransform->update(renderContext->args, jitter); + + RenderArgs* args = renderContext->args; + frameTransform->update(args); + + gpu::doInBatch("GenerateDeferredFrameTransform::run", args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + // This is the main view / projection transform that will be reused later on + batch.saveViewProjectionTransform(_transformSlot); + // Copy it to the deferred transform for the lighting pass + batch.copySavedViewProjectionTransformToBuffer(_transformSlot, frameTransform->getFrameTransformBuffer()._buffer, + sizeof(DeferredFrameTransform::DeferredFrameInfo)); + }); } diff --git a/libraries/render-utils/src/DeferredFrameTransform.h b/libraries/render-utils/src/DeferredFrameTransform.h index f7700cb2dc..a0f7879199 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.h +++ b/libraries/render-utils/src/DeferredFrameTransform.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau 6/3/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -20,12 +21,13 @@ // DeferredFrameTransform is a helper class gathering in one place the needed camera transform // and frame resolution needed for all the deferred rendering passes taking advantage of the Deferred buffers class DeferredFrameTransform { + friend class GenerateDeferredFrameTransform; public: using UniformBufferView = gpu::BufferView; DeferredFrameTransform(); - void update(RenderArgs* args, glm::vec2 jitter); + void update(RenderArgs* args); UniformBufferView getFrameTransformBuffer() const { return _frameTransformBuffer; } @@ -34,54 +36,29 @@ protected: // Class describing the uniform buffer with the transform info common to the AO shaders // It s changing every frame - class FrameTransform { +#include "DeferredTransform_shared.slh" + class FrameTransform : public _DeferredFrameTransform { public: - // Pixel info is { viewport width height} - glm::vec4 pixelInfo; - glm::vec4 invpixelInfo; - // Depth info is { n.f, f - n, -f} - glm::vec4 depthInfo; - // Stereo info is { isStereoFrame, halfWidth } - glm::vec4 stereoInfo{ 0.0 }; - // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space - glm::mat4 projection[2]; - // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space - glm::mat4 invProjection[2]; - // THe mono projection for sure - glm::mat4 projectionMono; - // Inv View matrix from eye space (mono) to world space - glm::mat4 invView; - // View matrix from world space to eye space (mono) - glm::mat4 view; - // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space without jittering - glm::mat4 projectionUnjittered[2]; - // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space without jittering - glm::mat4 invProjectionUnjittered[2]; - - FrameTransform() {} + FrameTransform() { infos.stereoInfo = glm::vec4(0.0f); } }; - UniformBufferView _frameTransformBuffer; - + UniformBufferView _frameTransformBuffer; }; using DeferredFrameTransformPointer = std::shared_ptr; - - class GenerateDeferredFrameTransform { public: - - using Input = glm::vec2; using Output = DeferredFrameTransformPointer; - using JobModel = render::Job::ModelIO; + using JobModel = render::Job::ModelO; - GenerateDeferredFrameTransform() {} + GenerateDeferredFrameTransform(unsigned int transformSlot) : _transformSlot{ transformSlot } {} - void run(const render::RenderContextPointer& renderContext, const Input& jitter, Output& frameTransform); + void run(const render::RenderContextPointer& renderContext, Output& frameTransform); private: + unsigned int _transformSlot; }; #endif // hifi_DeferredFrameTransform_h diff --git a/libraries/render-utils/src/DeferredFramebuffer.cpp b/libraries/render-utils/src/DeferredFramebuffer.cpp index 1906375654..c99073a18a 100644 --- a/libraries/render-utils/src/DeferredFramebuffer.cpp +++ b/libraries/render-utils/src/DeferredFramebuffer.cpp @@ -10,6 +10,11 @@ // #include "DeferredFramebuffer.h" +#include "DeferredBufferWrite_shared.slh" + +#include "gpu/Batch.h" +#include "gpu/Context.h" + DeferredFramebuffer::DeferredFramebuffer() { } @@ -36,8 +41,10 @@ void DeferredFramebuffer::updatePrimaryDepth(const gpu::TexturePointer& depthBuf _deferredColorTexture.reset(); _deferredNormalTexture.reset(); _deferredSpecularTexture.reset(); + _deferredVelocityTexture.reset(); _lightingTexture.reset(); _lightingFramebuffer.reset(); + _lightingWithVelocityFramebuffer.reset(); } } @@ -46,8 +53,9 @@ void DeferredFramebuffer::allocate() { _deferredFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("deferred")); _deferredFramebufferDepthColor = gpu::FramebufferPointer(gpu::Framebuffer::create("deferredDepthColor")); - auto colorFormat = gpu::Element::COLOR_SRGBA_32; - auto linearFormat = gpu::Element::COLOR_RGBA_32; + const auto colorFormat = gpu::Element::COLOR_SRGBA_32; + const auto linearFormat = gpu::Element::COLOR_RGBA_32; + const auto halfFormat = gpu::Element(gpu::VEC2, gpu::HALF, gpu::XY); auto width = _frameSize.x; auto height = _frameSize.y; @@ -56,10 +64,12 @@ void DeferredFramebuffer::allocate() { _deferredColorTexture = gpu::Texture::createRenderBuffer(colorFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); _deferredNormalTexture = gpu::Texture::createRenderBuffer(linearFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); _deferredSpecularTexture = gpu::Texture::createRenderBuffer(linearFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); + _deferredVelocityTexture = gpu::Texture::createRenderBuffer(halfFormat, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); - _deferredFramebuffer->setRenderBuffer(0, _deferredColorTexture); - _deferredFramebuffer->setRenderBuffer(1, _deferredNormalTexture); - _deferredFramebuffer->setRenderBuffer(2, _deferredSpecularTexture); + _deferredFramebuffer->setRenderBuffer(DEFERRED_COLOR_SLOT, _deferredColorTexture); + _deferredFramebuffer->setRenderBuffer(DEFERRED_NORMAL_SLOT, _deferredNormalTexture); + _deferredFramebuffer->setRenderBuffer(DEFERRED_SPECULAR_SLOT, _deferredSpecularTexture); + _deferredFramebuffer->setRenderBuffer(DEFERRED_VELOCITY_SLOT, _deferredVelocityTexture); _deferredFramebufferDepthColor->setRenderBuffer(0, _deferredColorTexture); @@ -80,8 +90,12 @@ void DeferredFramebuffer::allocate() { _lightingFramebuffer->setRenderBuffer(0, _lightingTexture); _lightingFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); - _deferredFramebuffer->setRenderBuffer(3, _lightingTexture); + _lightingWithVelocityFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("lighting_velocity")); + _lightingWithVelocityFramebuffer->setRenderBuffer(0, _lightingTexture); + _lightingWithVelocityFramebuffer->setRenderBuffer(1, _deferredVelocityTexture); + _lightingWithVelocityFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, depthFormat); + _deferredFramebuffer->setRenderBuffer(DEFERRED_LIGHTING_SLOT, _lightingTexture); } @@ -127,6 +141,13 @@ gpu::TexturePointer DeferredFramebuffer::getDeferredSpecularTexture() { return _deferredSpecularTexture; } +gpu::TexturePointer DeferredFramebuffer::getDeferredVelocityTexture() { + if (!_deferredVelocityTexture) { + allocate(); + } + return _deferredVelocityTexture; +} + gpu::FramebufferPointer DeferredFramebuffer::getLightingFramebuffer() { if (!_lightingFramebuffer) { allocate(); @@ -134,6 +155,13 @@ gpu::FramebufferPointer DeferredFramebuffer::getLightingFramebuffer() { return _lightingFramebuffer; } +gpu::FramebufferPointer DeferredFramebuffer::getLightingWithVelocityFramebuffer() { + if (!_lightingWithVelocityFramebuffer) { + allocate(); + } + return _lightingWithVelocityFramebuffer; +} + gpu::TexturePointer DeferredFramebuffer::getLightingTexture() { if (!_lightingTexture) { allocate(); diff --git a/libraries/render-utils/src/DeferredFramebuffer.h b/libraries/render-utils/src/DeferredFramebuffer.h index 6002bf6494..ed107b677f 100644 --- a/libraries/render-utils/src/DeferredFramebuffer.h +++ b/libraries/render-utils/src/DeferredFramebuffer.h @@ -15,10 +15,10 @@ #include "gpu/Resource.h" #include "gpu/Framebuffer.h" - // DeferredFramebuffer is a helper class gathering in one place the GBuffer (Framebuffer) and lighting framebuffer class DeferredFramebuffer { public: + DeferredFramebuffer(); gpu::FramebufferPointer getDeferredFramebuffer(); @@ -27,8 +27,10 @@ public: gpu::TexturePointer getDeferredColorTexture(); gpu::TexturePointer getDeferredNormalTexture(); gpu::TexturePointer getDeferredSpecularTexture(); + gpu::TexturePointer getDeferredVelocityTexture(); gpu::FramebufferPointer getLightingFramebuffer(); + gpu::FramebufferPointer getLightingWithVelocityFramebuffer(); gpu::TexturePointer getLightingTexture(); // Update the depth buffer which will drive the allocation of all the other resources according to its size. @@ -47,13 +49,15 @@ protected: gpu::TexturePointer _deferredColorTexture; gpu::TexturePointer _deferredNormalTexture; gpu::TexturePointer _deferredSpecularTexture; + gpu::TexturePointer _deferredVelocityTexture; gpu::TexturePointer _lightingTexture; gpu::FramebufferPointer _lightingFramebuffer; + gpu::FramebufferPointer _lightingWithVelocityFramebuffer; glm::ivec2 _frameSize; }; using DeferredFramebufferPointer = std::shared_ptr; -#endif // hifi_DeferredFramebuffer_h \ No newline at end of file +#endif // hifi_DeferredFramebuffer_h diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 8d7fc345ac..aa2b0a8e7c 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -286,6 +286,7 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input outputs.edit0() = _deferredFramebuffer; outputs.edit1() = _deferredFramebuffer->getLightingFramebuffer(); + outputs.edit2() = _deferredFramebuffer->getLightingWithVelocityFramebuffer(); gpu::doInBatch("PrepareDeferred::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); @@ -298,8 +299,9 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input // Clear Color, Depth and Stencil for deferred buffer batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_COLOR1 | gpu::Framebuffer::BUFFER_COLOR2 | gpu::Framebuffer::BUFFER_COLOR3 | - gpu::Framebuffer::BUFFER_DEPTH | + gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_COLOR1 | + gpu::Framebuffer::BUFFER_COLOR2 | gpu::Framebuffer::BUFFER_COLOR3 | + gpu::Framebuffer::BUFFER_COLOR4 | gpu::Framebuffer::BUFFER_DEPTH | gpu::Framebuffer::BUFFER_STENCIL, vec4(vec3(0), 0), 1.0, 0, true); diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 4779376410..a83be6e5d2 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -78,8 +78,7 @@ class PrepareDeferred { public: // Inputs: primaryFramebuffer and lightingModel using Inputs = render::VaryingSet2 ; - // Output: DeferredFramebuffer, LightingFramebuffer - using Outputs = render::VaryingSet2; + using Outputs = render::VaryingSet3; using JobModel = render::Job::ModelIO; diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index 93a3e61c51..b354e27f92 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 6/2/16. // Copyright 2016 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -16,109 +17,103 @@ <@func declareDeferredFrameTransform()@> -struct CameraCorrection { - mat4 _correction; - mat4 _correctionInverse; - - mat4 _prevView; - mat4 _prevViewInverse; -}; - -LAYOUT(binding=GPU_BUFFER_CAMERA_CORRECTION) uniform cameraCorrectionBuffer { - CameraCorrection cameraCorrection; -}; +<@include DeferredTransform_shared.slh@> -struct DeferredFrameTransform { - vec4 _pixelInfo; - vec4 _invPixelInfo; - vec4 _depthInfo; - vec4 _stereoInfo; - mat4 _projection[2]; - mat4 _invProjection[2]; - mat4 _projectionMono; - mat4 _viewInverse; - mat4 _view; - mat4 _projectionUnJittered[2]; - mat4 _invProjectionUnJittered[2]; -}; +#define DeferredFrameTransform _DeferredFrameTransform +#define TransformCamera _TransformCamera -LAYOUT(binding=RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM) uniform deferredFrameTransformBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM) uniform deferredFrameTransformBuffer { DeferredFrameTransform frameTransform; }; vec2 getWidthHeight(int resolutionLevel) { - return vec2(ivec2(frameTransform._pixelInfo.zw) >> resolutionLevel); + return vec2(ivec2(frameTransform.infos.pixelInfo.zw) >> resolutionLevel); } vec2 getInvWidthHeight() { - return frameTransform._invPixelInfo.xy; + return frameTransform.infos.invPixelInfo.xy; +} + +mat4 getProjection(int side) { + return frameTransform.cameras[side]._projection; +} + +mat4 getProjectionInverse(int side) { + return frameTransform.cameras[side]._projectionInverse; } float getProjScaleEye() { - return frameTransform._projection[0][1][1]; + return getProjection(0)[1][1]; } float getProjScale(int resolutionLevel) { - return getWidthHeight(resolutionLevel).y * frameTransform._projection[0][1][1] * 0.5; -} -mat4 getProjection(int side) { - return frameTransform._projection[side]; + return getWidthHeight(resolutionLevel).y * getProjScaleEye() * 0.5; } + mat4 getProjectionMono() { - return frameTransform._projectionMono; -} -mat4 getUnjitteredProjection(int side) { - return frameTransform._projectionUnJittered[side]; -} -mat4 getUnjitteredInvProjection(int side) { - return frameTransform._invProjectionUnJittered[side]; + return frameTransform.infos.projectionMono; } // positive near distance of the projection float getProjectionNear() { - float planeC = frameTransform._projection[0][2][3] + frameTransform._projection[0][2][2]; - float planeD = frameTransform._projection[0][3][2]; + mat4 projection = getProjection(0); + float planeC = projection[2][3] + projection[2][2]; + float planeD = projection[3][2]; return planeD / planeC; } // positive far distance of the projection float getPosLinearDepthFar() { - return -frameTransform._depthInfo.z; + return -frameTransform.infos.depthInfo.z; } -mat4 getViewInverse() { - return frameTransform._viewInverse * cameraCorrection._correctionInverse; +mat4 getViewInverse(int side) { + return frameTransform.cameras[side]._viewInverse; } -mat4 getView() { - return cameraCorrection._correction * frameTransform._view; +mat4 getView(int side) { + return frameTransform.cameras[side]._view; } -mat4 getPreviousView() { - return cameraCorrection._prevView; +mat4 getPreviousView(int side) { + return frameTransform.cameras[side]._previousView; } -mat4 getPreviousViewInverse() { - return cameraCorrection._prevViewInverse; -} - -DeferredFrameTransform getDeferredFrameTransform() { - DeferredFrameTransform result = frameTransform; - result._view = getView(); - result._viewInverse = getViewInverse(); - return result; +mat4 getPreviousViewInverse(int side) { + return frameTransform.cameras[side]._previousViewInverse; } bool isStereo() { - return frameTransform._stereoInfo.x > 0.0f; + return frameTransform.infos.stereoInfo.x > 0.0f; } float getStereoSideWidth(int resolutionLevel) { - return float(int(frameTransform._stereoInfo.y) >> resolutionLevel); + return float(int(frameTransform.infos.stereoInfo.y) >> resolutionLevel); } float getStereoSideHeight(int resolutionLevel) { - return float(int(frameTransform._pixelInfo.w) >> resolutionLevel); + return float(int(frameTransform.infos.pixelInfo.w) >> resolutionLevel); +} + +vec2 getSideImageSize(int resolutionLevel) { + return vec2(float(int(frameTransform.infos.stereoInfo.y) >> resolutionLevel), float(int(frameTransform.infos.pixelInfo.w) >> resolutionLevel)); +} + +int getStereoSideFromPixel(int xPos, int resolutionLevel) { + int sideWidth = int(getStereoSideWidth(resolutionLevel)); + return int(xPos >= sideWidth && isStereo()); +} + +int getStereoSideFromPixel(int xPos) { + return getStereoSideFromPixel(xPos, 0); +} + +int getStereoSideFromFragCoord() { + return getStereoSideFromPixel(int(gl_FragCoord.x), 0); +} + +int getStereoSideFromUV(float uPos) { + return int(uPos >= 0.5 && isStereo()); } vec2 getStereoSideSize(int resolutionLevel) { @@ -134,17 +129,16 @@ ivec4 getStereoSideInfo(int xPos, int resolutionLevel) { return getStereoSideInfoFromWidth(xPos, sideWidth); } - int getStereoSide(ivec4 sideInfo) { return sideInfo.x; } float evalZeyeFromZdb(float depth) { - return frameTransform._depthInfo.x / (depth * frameTransform._depthInfo.y + frameTransform._depthInfo.z); + return frameTransform.infos.depthInfo.x / (depth * frameTransform.infos.depthInfo.y + frameTransform.infos.depthInfo.z); } float evalZdbFromZeye(float Zeye) { - return (frameTransform._depthInfo.x - Zeye * frameTransform._depthInfo.z) / (Zeye * frameTransform._depthInfo.y); + return (frameTransform.infos.depthInfo.x - Zeye * frameTransform.infos.depthInfo.z) / (Zeye * frameTransform.infos.depthInfo.y); } vec3 evalEyeNormal(vec3 C) { @@ -155,15 +149,7 @@ vec3 evalEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { // compute the view space position using the depth vec3 clipPos; clipPos.xyz = vec3(texcoord.xy, Zdb) * 2.0 - 1.0; - vec4 eyePos = frameTransform._invProjection[side] * vec4(clipPos.xyz, 1.0); - return eyePos.xyz / eyePos.w; -} - -vec3 evalUnjitteredEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { - // compute the view space position using the depth - vec3 clipPos; - clipPos.xyz = vec3(texcoord.xy, Zdb) * 2.0 - 1.0; - vec4 eyePos = frameTransform._invProjectionUnJittered[side] * vec4(clipPos.xyz, 1.0); + vec4 eyePos = getProjectionInverse(side) * vec4(clipPos.xyz, 1.0); return eyePos.xyz / eyePos.w; } diff --git a/libraries/render-utils/src/DeferredTransform_shared.slh b/libraries/render-utils/src/DeferredTransform_shared.slh new file mode 100644 index 0000000000..2973b627ce --- /dev/null +++ b/libraries/render-utils/src/DeferredTransform_shared.slh @@ -0,0 +1,33 @@ +// glsl / C++ compatible source as interface for DeferredFrameTransform layout +#ifdef __cplusplus +# define DFT_VEC4 glm::vec4 +# define DFT_MAT4 glm::mat4 +#include "gpu/TransformCamera_shared.slh" +#else +# define DFT_VEC4 vec4 +# define DFT_MAT4 mat4 +<@include gpu/TransformCamera_shared.slh@> +#endif + +struct DeferredFrameInfo { + // Pixel info is { viewport width height} + DFT_VEC4 pixelInfo; + DFT_VEC4 invPixelInfo; + // Depth info is { n.f, f - n, -f} + DFT_VEC4 depthInfo; + // Stereo info is { isStereoFrame, halfWidth } + DFT_VEC4 stereoInfo; + // The mono projection for sure + DFT_MAT4 projectionMono; +}; + +struct _DeferredFrameTransform { + DeferredFrameInfo infos; + // The camera transforms for the two eyes (or only first one if mono, of course) + _TransformCamera cameras[2]; +}; + + // <@if 1@> + // Trigger Scribe include + // <@endif@> +// diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 4ac097e31d..c1cbdbe5e4 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -799,7 +799,7 @@ render::ShapePipelinePointer GeometryCache::getFadingShapePipeline(bool textured graphics::MaterialKey::CullFaceMode cullFaceMode) { auto fadeEffect = DependencyManager::get(); auto fadeBatchSetter = fadeEffect->getBatchSetter(); - auto fadeItemSetter = fadeEffect->getItemUniformSetter(); + auto fadeItemSetter = fadeEffect->getItemStoredSetter(); return std::make_shared(getSimplePipeline(textured, transparent, unlit, depthBias, true, true, forward, cullFaceMode), nullptr, [fadeBatchSetter, fadeItemSetter](const render::ShapePipeline& shapePipeline, gpu::Batch& batch, render::Args* args) { batch.setResourceTexture(gr::Texture::MaterialAlbedo, DependencyManager::get()->getWhiteTexture()); @@ -1995,7 +1995,7 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { // enable decal blend state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - PrepareStencil::testMask(*state); + PrepareStencil::testMaskResetNoAA(*state); _standardDrawPipeline = gpu::Pipeline::create(program, state); @@ -2027,7 +2027,7 @@ void GeometryCache::useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bo gpu::StatePointer state = std::make_shared(); state->setDepthTest(true, !std::get<0>(key), gpu::LESS_EQUAL); if (std::get<0>(key)) { - PrepareStencil::testMask(*state); + PrepareStencil::testMaskResetNoAA(*state); } else { PrepareStencil::testMaskDrawShape(*state); } @@ -2134,7 +2134,6 @@ gpu::PipelinePointer GeometryCache::getWebBrowserProgram(bool transparent, bool gpu::StatePointer state = std::make_shared(); state->setDepthTest(true, !transparent, gpu::LESS_EQUAL); - // FIXME: do we need a testMaskDrawNoAA? PrepareStencil::testMaskDrawShapeNoAA(*state); state->setBlendFunction(transparent, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, @@ -2215,7 +2214,7 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); if (config.isAntiAliased()) { - config.isTransparent() ? PrepareStencil::testMask(*state) : PrepareStencil::testMaskDrawShape(*state); + config.isTransparent() ? PrepareStencil::testMaskResetNoAA(*state) : PrepareStencil::testMaskDrawShape(*state); } else { PrepareStencil::testMaskDrawShapeNoAA(*state); } diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index 899f36b8eb..69f38c56f1 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -23,13 +23,13 @@ LAYOUT(binding=RENDER_UTILS_TEXTURE_HAZE_LINEAR_DEPTH) uniform sampler2D linearDepthMap; -vec4 unpackPositionFromZeye(vec2 texcoord) { +vec4 unpackPositionFromZeyeAndGetSide(vec2 texcoord, out int side) { float Zeye = -texture(linearDepthMap, texcoord).x; float check = float(isStereo()); float check2 = check * float(texcoord.x > 0.5); texcoord.x -= check2 * 0.5; - int side = int(check2); + side = int(check2); texcoord.x *= 1.0 + check; return vec4(evalEyePositionFromZeye(side, Zeye, texcoord), 1.0); @@ -43,9 +43,10 @@ void main(void) { discard; } - vec4 fragPositionES = unpackPositionFromZeye(varTexCoord0); + int side; + vec4 fragPositionES = unpackPositionFromZeyeAndGetSide(varTexCoord0, side); - mat4 viewInverse = getViewInverse(); + mat4 viewInverse = getViewInverse(side); vec4 fragPositionWS = viewInverse * fragPositionES; vec4 eyePositionWS = viewInverse[3]; diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 2152219e77..6d5da67cad 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -124,7 +124,8 @@ gpu::PipelinePointer DrawHighlightMask::_stencilMaskPipeline; gpu::PipelinePointer DrawHighlightMask::_stencilMaskFillPipeline; DrawHighlightMask::DrawHighlightMask(unsigned int highlightIndex, render::ShapePlumberPointer shapePlumber, - HighlightSharedParametersPointer parameters) : _highlightPassIndex(highlightIndex), _shapePlumber(shapePlumber), _sharedParameters(parameters) {} + HighlightSharedParametersPointer parameters, uint transformSlot) : + _highlightPassIndex(highlightIndex), _shapePlumber(shapePlumber), _sharedParameters(parameters), _transformSlot(transformSlot) {} void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { assert(renderContext->args); @@ -177,8 +178,6 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c batch.clearDepthStencilFramebuffer(1.0f, 0); }); - const auto jitter = inputs.get2(); - render::ItemBounds itemBounds; gpu::doInBatch("DrawHighlightMask::run", args->_context, [&](gpu::Batch& batch) { @@ -190,9 +189,8 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c args->getViewFrustum().evalProjectionMatrix(projMat); args->getViewFrustum().evalViewTransform(viewMat); batch.setViewportTransform(args->_viewport); - batch.setProjectionTransform(projMat); - batch.setProjectionJitter(jitter.x, jitter.y); - batch.setViewTransform(viewMat); + batch.setProjectionJitterEnabled(true); + batch.setSavedViewProjectionTransform(_transformSlot); sortAndRenderZPassShapes(_shapePlumber, renderContext, inShapes, itemBounds); }); @@ -209,6 +207,11 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c } gpu::doInBatch("DrawHighlightMask::run::end", args->_context, [&](gpu::Batch& batch) { + // Setup camera, projection and viewport for all items + batch.setViewportTransform(args->_viewport); + batch.setProjectionJitterEnabled(true); + batch.setSavedViewProjectionTransform(_transformSlot); + // Draw stencil mask with object bounding boxes auto stencilPipeline = highlight._style.isFilled() ? _stencilMaskFillPipeline : _stencilMaskPipeline; batch.setPipeline(stencilPipeline); @@ -269,7 +272,6 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const shaderParameters._size.y = size; } - auto primaryFramebuffer = inputs.get4(); gpu::doInBatch("DrawHighlight::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(destinationFrameBuffer); @@ -285,9 +287,6 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const batch.setResourceTexture(ru::Texture::HighlightSceneDepth, sceneDepthBuffer->getPrimaryDepthTexture()); batch.setResourceTexture(ru::Texture::HighlightDepth, highlightedDepthTexture); batch.draw(gpu::TRIANGLE_STRIP, 4); - - // Reset the framebuffer for overlay drawing - batch.setFramebuffer(primaryFramebuffer); }); } } @@ -311,7 +310,7 @@ const gpu::PipelinePointer& DrawHighlight::getPipeline(const render::HighlightSt return style.isFilled() ? _pipelineFilled : _pipeline; } -DebugHighlight::DebugHighlight() { +DebugHighlight::DebugHighlight(uint transformSlot) : _transformSlot(transformSlot) { _geometryDepthId = DependencyManager::get()->allocateID(); } @@ -334,22 +333,15 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; - const auto jitter = input.get2(); - auto primaryFramebuffer = input.get3(); gpu::doInBatch("DebugHighlight::run", args->_context, [&](gpu::Batch& batch) { batch.setViewportTransform(args->_viewport); batch.setFramebuffer(highlightResources->getColorFramebuffer()); const auto geometryBuffer = DependencyManager::get(); - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setProjectionJitter(jitter.x, jitter.y); - batch.setViewTransform(viewMat); + batch.setProjectionJitterEnabled(true); + batch.setSavedViewProjectionTransform(_transformSlot); batch.setModelTransform(Transform()); const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); @@ -361,9 +353,6 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryDepthId); batch.setResourceTexture(0, nullptr); - - // Reset the framebuffer for overlay drawing - batch.setFramebuffer(primaryFramebuffer); }); } } @@ -465,13 +454,12 @@ void DrawHighlightTask::configure(const Config& config) { } -void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) { +void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, uint transformSlot) { const auto items = inputs.getN(0).get(); const auto& outlines = items[RenderFetchCullSortTask::OUTLINE]; const auto sceneFrameBuffer = inputs.getN(1); const auto primaryFramebuffer = inputs.getN(2); const auto deferredFrameTransform = inputs.getN(3); - const auto jitter = inputs.getN(4); // Prepare the ShapePipeline auto shapePlumber = std::make_shared(); @@ -514,8 +502,8 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren stream << "HighlightMask" << i; name = stream.str(); } - const auto drawMaskInputs = DrawHighlightMask::Inputs(sortedBounds, highlightResources, jitter).asVarying(); - const auto highlightedRect = task.addJob(name, drawMaskInputs, i, shapePlumber, sharedParameters); + const auto drawMaskInputs = DrawHighlightMask::Inputs(sortedBounds, highlightResources).asVarying(); + const auto highlightedRect = task.addJob(name, drawMaskInputs, i, shapePlumber, sharedParameters, transformSlot); if (i == 0) { highlight0Rect = highlightedRect; } @@ -526,7 +514,7 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren stream << "HighlightEffect" << i; name = stream.str(); } - const auto drawHighlightInputs = DrawHighlight::Inputs(deferredFrameTransform, highlightResources, sceneFrameBuffer, highlightedRect, primaryFramebuffer).asVarying(); + const auto drawHighlightInputs = DrawHighlight::Inputs(deferredFrameTransform, highlightResources, sceneFrameBuffer, highlightedRect).asVarying(); task.addJob(name, drawHighlightInputs, i, sharedParameters); } @@ -535,8 +523,8 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren task.addJob("HighlightCleanup", cleanupInput); // Debug highlight - const auto debugInputs = DebugHighlight::Inputs(highlightResources, const_cast(highlight0Rect), jitter, primaryFramebuffer).asVarying(); - task.addJob("HighlightDebug", debugInputs); + const auto debugInputs = DebugHighlight::Inputs(highlightResources, const_cast(highlight0Rect)).asVarying(); + task.addJob("HighlightDebug", debugInputs, transformSlot); } const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const render::Varying& selectionName, diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h index a55ecdf46d..dc95d96394 100644 --- a/libraries/render-utils/src/HighlightEffect.h +++ b/libraries/render-utils/src/HighlightEffect.h @@ -114,11 +114,10 @@ private: class DrawHighlightMask { public: - using Inputs = render::VaryingSet3; - using Outputs = glm::ivec4; + using Inputs = render::VaryingSet2; using Outputs = glm::ivec4; using JobModel = render::Job::ModelIO; - DrawHighlightMask(unsigned int highlightIndex, render::ShapePlumberPointer shapePlumber, HighlightSharedParametersPointer parameters); + DrawHighlightMask(unsigned int highlightIndex, render::ShapePlumberPointer shapePlumber, HighlightSharedParametersPointer parameters, uint transformSlot); void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); @@ -128,6 +127,7 @@ protected: HighlightSharedParametersPointer _sharedParameters; gpu::BufferPointer _boundsBuffer; gpu::StructBuffer _outlineWidth; + uint _transformSlot { 0 }; static gpu::PipelinePointer _stencilMaskPipeline; static gpu::PipelinePointer _stencilMaskFillPipeline; @@ -136,7 +136,7 @@ protected: class DrawHighlight { public: - using Inputs = render::VaryingSet5; + using Inputs = render::VaryingSet4; using Config = render::Job::Config; using JobModel = render::Job::ModelI; @@ -174,11 +174,10 @@ signals: class DebugHighlight { public: - using Inputs = render::VaryingSet4; - using Config = DebugHighlightConfig; + using Inputs = render::VaryingSet2; using Config = DebugHighlightConfig; using JobModel = render::Job::ModelI; - DebugHighlight(); + DebugHighlight(uint transformSlot); ~DebugHighlight(); void configure(const Config& config); @@ -187,8 +186,9 @@ public: private: gpu::PipelinePointer _depthPipeline; - int _geometryDepthId{ 0 }; - bool _isDisplayEnabled{ false }; + int _geometryDepthId { 0 }; + bool _isDisplayEnabled { false }; + uint _transformSlot { 0 }; const gpu::PipelinePointer& getDepthPipeline(); void initializePipelines(); @@ -197,14 +197,13 @@ private: class DrawHighlightTask { public: - using Inputs = render::VaryingSet5; - using Config = render::Task::Config; + using Inputs = render::VaryingSet4; using Config = render::Task::Config; using JobModel = render::Task::ModelI; DrawHighlightTask(); void configure(const Config& config); - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, uint transformSlot); private: static const render::Varying addSelectItemJobs(JobModel& task, const render::Varying& selectionName, const RenderFetchCullSortTask::BucketList& items); diff --git a/libraries/render-utils/src/Highlight_aabox.slv b/libraries/render-utils/src/Highlight_aabox.slv index 65b98355ae..e75694d910 100644 --- a/libraries/render-utils/src/Highlight_aabox.slv +++ b/libraries/render-utils/src/Highlight_aabox.slv @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// +// <$_SCRIBE_FILENAME$> +// Generated on <$_SCRIBE_DATE$> // Draw and transform the fed vertex position with the standard MVP stack // and offset the vertices by a certain amount in the vertex direction // @@ -12,9 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/ShaderConstants.h@> <@include gpu/Transform.slh@> - <$declareStandardTransform()$> struct ItemBound { @@ -107,5 +105,5 @@ void main(void) { vec4 offsetPosition; <$transformModelToMonoClipPos(cam, obj, pos, offsetPosition)$> gl_Position.xy += normalize(offsetPosition.xy-gl_Position.xy) * _parameters.outlineWidth * gl_Position.w; - <$transformStereoClipsSpace(cam, gl_Position)$> + <$transformStereoClipSpace(gl_Position)$> } diff --git a/libraries/render-utils/src/LightClusters.cpp b/libraries/render-utils/src/LightClusters.cpp index 3dc826d56c..a1bd4001fc 100644 --- a/libraries/render-utils/src/LightClusters.cpp +++ b/libraries/render-utils/src/LightClusters.cpp @@ -575,7 +575,7 @@ void LightClusteringPass::run(const render::RenderContextPointer& renderContext, config->setNumClusteredLightReferences(clusteringStats.z); } -DebugLightClusters::DebugLightClusters() { +DebugLightClusters::DebugLightClusters(uint transformSlot) : _transformSlot(transformSlot) { } @@ -650,13 +650,7 @@ void DebugLightClusters::run(const render::RenderContextPointer& renderContext, // Assign the camera transform batch.setViewportTransform(args->_viewport); - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat, true); - + batch.setSavedViewProjectionTransform(_transformSlot); // Then the actual ClusterGrid attributes batch.setModelTransform(Transform()); @@ -668,8 +662,6 @@ void DebugLightClusters::run(const render::RenderContextPointer& renderContext, batch.setUniformBuffer(ru::Buffer::LightClusterGrid, lightClusters->_clusterGridBuffer); batch.setUniformBuffer(ru::Buffer::LightClusterContent, lightClusters->_clusterContentBuffer); - - if (doDrawClusterFromDepth) { batch.setPipeline(getDrawClusterFromDepthPipeline()); batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, deferredTransform->getFrameTransformBuffer()); diff --git a/libraries/render-utils/src/LightClusters.h b/libraries/render-utils/src/LightClusters.h index 60978e76e8..be39e8ffc1 100644 --- a/libraries/render-utils/src/LightClusters.h +++ b/libraries/render-utils/src/LightClusters.h @@ -217,7 +217,7 @@ public: using Config = DebugLightClustersConfig; using JobModel = render::Job::ModelI; - DebugLightClusters(); + DebugLightClusters(uint transformSlot); void configure(const Config& config); @@ -228,6 +228,7 @@ protected: gpu::PipelinePointer _drawClusterGrid; gpu::PipelinePointer _drawClusterFromDepth; gpu::PipelinePointer _drawClusterContent; + uint _transformSlot; const gpu::PipelinePointer getDrawClusterGridPipeline(); const gpu::PipelinePointer getDrawClusterFromDepthPipeline(); const gpu::PipelinePointer getDrawClusterContentPipeline(); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index f6e4b3b460..c3d40c4954 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -193,7 +193,14 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& tra if (_clusterBuffer) { batch.setUniformBuffer(graphics::slot::buffer::Skinning, _clusterBuffer); } - batch.setModelTransform(transform); + // TODO: I'm not sure of this + //batch.setModelTransform(transform, _previousModelTransform); + batch.setModelTransform(transform, _previousRenderTransform); + if (renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + // TODO: I'm not sure of this + //_prevRenderTransform = _drawTransform; + _previousRenderTransform = transform; + } } void ModelMeshPartPayload::drawCall(gpu::Batch& batch) const { diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index b502865f94..9aad004002 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -70,6 +70,9 @@ public: static bool enableMaterialProceduralShaders; +protected: + mutable Transform _previousRenderTransform; + private: void initCache(const ModelPointer& model, int shapeID); diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index 7cf7f1129f..cf20bff06b 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2018/01/09 // Copyright 2013-2018 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -27,17 +28,18 @@ namespace gr { using namespace render; -extern void initForwardPipelines(ShapePlumber& plumber); void BeginGPURangeTimer::run(const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer) { timer = _gpuTimer; gpu::doInBatch("BeginGPURangeTimer", renderContext->args->_context, [&](gpu::Batch& batch) { _gpuTimer->begin(batch); + batch.pushProfileRange(timer->name().c_str()); }); } void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, const gpu::RangeTimerPointer& timer) { gpu::doInBatch("EndGPURangeTimer", renderContext->args->_context, [&](gpu::Batch& batch) { + batch.popProfileRange(); timer->end(batch); }); @@ -45,10 +47,11 @@ void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, co config->setGPUBatchRunTime(timer->getGPUAverage(), timer->getBatchAverage()); } -DrawLayered3D::DrawLayered3D(bool opaque) : - _shapePlumber(std::make_shared()), - _opaquePass(opaque) { - initForwardPipelines(*_shapePlumber); +DrawLayered3D::DrawLayered3D(const render::ShapePlumberPointer& shapePlumber, bool opaque, bool jitter, unsigned int transformSlot) : + _shapePlumber(shapePlumber), + _transformSlot(transformSlot), + _opaquePass(opaque), + _isJitterEnabled(jitter) { } void DrawLayered3D::run(const RenderContextPointer& renderContext, const Inputs& inputs) { @@ -58,9 +61,9 @@ void DrawLayered3D::run(const RenderContextPointer& renderContext, const Inputs& auto config = std::static_pointer_cast(renderContext->jobConfig); const auto& inItems = inputs.get0(); - const auto& lightingModel = inputs.get1(); - const auto& hazeFrame = inputs.get2(); - const auto jitter = inputs.get3(); + const auto& frameTransform = inputs.get1(); + const auto& lightingModel = inputs.get2(); + const auto& hazeFrame = inputs.get3(); config->setNumDrawn((int)inItems.size()); emit config->numDrawnChanged(); @@ -80,29 +83,25 @@ void DrawLayered3D::run(const RenderContextPointer& renderContext, const Inputs& if (_opaquePass) { gpu::doInBatch("DrawLayered3D::run::clear", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); - batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false); + batch.clearDepthFramebuffer(true, false); }); } if (!inItems.empty()) { // Render the items gpu::doInBatch("DrawLayered3D::main", args->_context, [&](gpu::Batch& batch) { + PROFILE_RANGE_BATCH(batch, "DrawLayered3D::main"); args->_batch = &batch; batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setProjectionJitter(jitter.x, jitter.y); - batch.setViewTransform(viewMat); + batch.setProjectionJitterEnabled(_isJitterEnabled); + batch.setSavedViewProjectionTransform(_transformSlot); // Setup lighting model for all items; batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer()); batch.setResourceTexture(ru::Texture::AmbientFresnel, lightingModel->getAmbientFresnelLUT()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); if (haze) { batch.setUniformBuffer(graphics::slot::buffer::Buffer::HazeParams, haze->getHazeParametersBuffer()); diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h index 15d6ff9895..e8603c003f 100644 --- a/libraries/render-utils/src/RenderCommonTask.h +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -13,6 +13,7 @@ #include "LightStage.h" #include "HazeStage.h" #include "LightingModel.h" +#include "DeferredFrameTransform.h" class BeginGPURangeTimer { public: @@ -61,11 +62,11 @@ protected: class DrawLayered3D { public: - using Inputs = render::VaryingSet4; + using Inputs = render::VaryingSet4; using Config = DrawLayered3DConfig; using JobModel = render::Job::ModelI; - DrawLayered3D(bool opaque); + DrawLayered3D(const render::ShapePlumberPointer& shapePlumber, bool opaque, bool jitter, unsigned int transformSlot); void configure(const Config& config) { _maxDrawn = config.maxDrawn; } void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); @@ -73,7 +74,9 @@ public: protected: render::ShapePlumberPointer _shapePlumber; int _maxDrawn; // initialized by Config + uint _transformSlot; bool _opaquePass { true }; + bool _isJitterEnabled { false }; }; class Blit { diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index c506f22bc7..cfee877bbd 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -5,6 +5,7 @@ // // Created by Sam Gateau on 5/29/15. // Copyright 2016 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -21,6 +22,7 @@ #include #include #include +#include #include #include @@ -39,7 +41,6 @@ #include "DeferredFramebuffer.h" #include "DeferredLightingEffect.h" #include "SurfaceGeometryPass.h" -#include "VelocityBufferPass.h" #include "FramebufferCache.h" #include "TextureCache.h" #include "ZoneRenderer.h" @@ -60,6 +61,7 @@ using namespace render; extern void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); +extern void initForwardPipelines(render::ShapePlumber& plumber); namespace ru { using render_utils::slot::texture::Texture; @@ -74,25 +76,17 @@ namespace gr { class RenderDeferredTaskDebug { public: - using ExtraBuffers = render::VaryingSet6; + using ExtraBuffers = render::VaryingSet5; using Input = render::VaryingSet9; + LightingModel, Antialiasing::Outputs>; using JobModel = render::Task::ModelI; - RenderDeferredTaskDebug(); - - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); -private: + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, uint mainViewTransformSlot); }; - -RenderDeferredTask::RenderDeferredTask() -{ -} - void RenderDeferredTask::configure(const Config& config) { // Propagate resolution scale to sub jobs who need it auto preparePrimaryBufferConfig = config.getConfig("PreparePrimaryBufferDeferred"); @@ -100,11 +94,16 @@ void RenderDeferredTask::configure(const Config& config) { preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale); } -void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { +void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset) { auto fadeEffect = DependencyManager::get(); // Prepare the ShapePipelines - ShapePlumberPointer shapePlumber = std::make_shared(); - initDeferredPipelines(*shapePlumber, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); + ShapePlumberPointer shapePlumberDeferred = std::make_shared(); + initDeferredPipelines(*shapePlumberDeferred, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); + ShapePlumberPointer shapePlumberForward = std::make_shared(); + initForwardPipelines(*shapePlumberForward); + + uint backgroundViewTransformSlot = render::RenderEngine::TS_BACKGROUND_VIEW + transformOffset; + uint mainViewTransformSlot = render::RenderEngine::TS_MAIN_VIEW + transformOffset; const auto& inputs = input.get(); @@ -142,25 +141,26 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren fadeEffect->build(task, opaques); - const auto jitter = task.addJob("JitterCam"); + const auto antialiasingMode = task.addJob("AntialiasingSetup"); // GPU jobs: Start preparing the primary, deferred and lighting buffer const auto scaledPrimaryFramebuffer = task.addJob("PreparePrimaryBufferDeferred"); // Prepare deferred, generate the shared Deferred Frame Transform. Only valid with the scaled frame buffer - const auto deferredFrameTransform = task.addJob("DeferredFrameTransform", jitter); + const auto deferredFrameTransform = task.addJob("DeferredFrameTransform", mainViewTransformSlot); const auto prepareDeferredInputs = PrepareDeferred::Inputs(scaledPrimaryFramebuffer, lightingModel).asVarying(); const auto prepareDeferredOutputs = task.addJob("PrepareDeferred", prepareDeferredInputs); const auto deferredFramebuffer = prepareDeferredOutputs.getN(0); const auto lightingFramebuffer = prepareDeferredOutputs.getN(1); + const auto lightingWithVelocityFramebuffer = prepareDeferredOutputs.getN(2); // draw a stencil mask in hidden regions of the framebuffer. task.addJob("PrepareStencil", scaledPrimaryFramebuffer); // Render opaque objects in DeferredBuffer - const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel, jitter).asVarying(); - task.addJob("DrawOpaqueDeferred", opaqueInputs, shapePlumber); + const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel, deferredFrameTransform).asVarying(); + task.addJob("DrawOpaqueDeferred", opaqueInputs, shapePlumberDeferred, mainViewTransformSlot); // Opaque all rendered @@ -186,11 +186,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto ambientOcclusionFramebuffer = ambientOcclusionOutputs.getN(0); const auto ambientOcclusionUniforms = ambientOcclusionOutputs.getN(1); - // Velocity - const auto velocityBufferInputs = VelocityBufferPass::Inputs(deferredFrameTransform, deferredFramebuffer).asVarying(); - const auto velocityBufferOutputs = task.addJob("VelocityBuffer", velocityBufferInputs); - const auto velocityBuffer = velocityBufferOutputs.getN(0); - // Light Clustering // Create the cluster grid of lights, cpu job for now const auto lightClusteringPassInputs = LightClusteringPass::Input(deferredFrameTransform, lightingModel, lightFrame, linearDepthTarget).asVarying(); @@ -203,28 +198,28 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job const auto backgroundInputs = DrawBackgroundStage::Inputs(lightingModel, backgroundFrame, hazeFrame).asVarying(); - task.addJob("DrawBackgroundDeferred", backgroundInputs); + task.addJob("DrawBackgroundDeferred", backgroundInputs, backgroundViewTransformSlot); const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeFrame, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingModel, lightFrame)); task.addJob("DrawHazeDeferred", drawHazeInputs); // Render transparent objects forward in LightingBuffer - const auto transparentsInputs = RenderTransparentDeferred::Inputs(transparents, hazeFrame, lightFrame, lightingModel, lightClusters, shadowFrame, jitter).asVarying(); - task.addJob("DrawTransparentDeferred", transparentsInputs, shapePlumber); + const auto transparentsInputs = RenderTransparentDeferred::Inputs(transparents, hazeFrame, lightFrame, lightingModel, lightClusters, shadowFrame, deferredFrameTransform).asVarying(); + task.addJob("DrawTransparentDeferred", transparentsInputs, shapePlumberDeferred, mainViewTransformSlot); // Highlight - const auto outlineInputs = DrawHighlightTask::Inputs(items, deferredFramebuffer, lightingFramebuffer, deferredFrameTransform, jitter).asVarying(); - task.addJob("DrawHighlight", outlineInputs); + const auto outlineInputs = DrawHighlightTask::Inputs(items, deferredFramebuffer, lightingFramebuffer, deferredFrameTransform).asVarying(); + task.addJob("DrawHighlight", outlineInputs, mainViewTransformSlot); // Layered Over (in front) - const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, hazeFrame, jitter).asVarying(); - const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, lightingModel, hazeFrame, jitter).asVarying(); - task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, true); - task.addJob("DrawInFrontTransparent", inFrontTransparentsInputs, false); + const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, deferredFrameTransform, lightingModel, hazeFrame).asVarying(); + task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, shapePlumberForward, true, true, mainViewTransformSlot); + const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, deferredFrameTransform, lightingModel, hazeFrame).asVarying(); + task.addJob("DrawInFrontTransparent", inFrontTransparentsInputs, shapePlumberForward, false, true, mainViewTransformSlot); // AA job before bloom to limit flickering - const auto antialiasingInputs = Antialiasing::Inputs(deferredFrameTransform, lightingFramebuffer, linearDepthTarget, velocityBuffer).asVarying(); - task.addJob("Antialiasing", antialiasingInputs); + const auto antialiasingInputs = Antialiasing::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget, antialiasingMode).asVarying(); + const auto antialiasingIntensityTexture = task.addJob("Antialiasing", antialiasingInputs); // Add bloom const auto bloomInputs = BloomEffect::Inputs(deferredFrameTransform, lightingFramebuffer, bloomFrame, lightingModel).asVarying(); @@ -238,21 +233,18 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Debugging task is happening in the "over" layer after tone mapping and just before HUD { // Debug the bounds of the rendered items, still look at the zbuffer - const auto extraDebugBuffers = RenderDeferredTaskDebug::ExtraBuffers(linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, ambientOcclusionUniforms, scatteringResource, velocityBuffer); + const auto extraDebugBuffers = RenderDeferredTaskDebug::ExtraBuffers(linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, ambientOcclusionUniforms, scatteringResource); const auto debugInputs = RenderDeferredTaskDebug::Input(fetchedItems, shadowTaskOutputs, lightingStageInputs, lightClusters, prepareDeferredOutputs, extraDebugBuffers, - deferredFrameTransform, jitter, lightingModel).asVarying(); - task.addJob("DebugRenderDeferredTask", debugInputs); + deferredFrameTransform, lightingModel, antialiasingIntensityTexture).asVarying(); + task.addJob("DebugRenderDeferredTask", debugInputs, mainViewTransformSlot); } // HUD Layer - const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(toneMappedBuffer, lightingModel, hudOpaque, hudTransparent, hazeFrame).asVarying(); - task.addJob("RenderHUDLayer", renderHUDLayerInputs); + const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(toneMappedBuffer, lightingModel, hudOpaque, hudTransparent, hazeFrame, deferredFrameTransform).asVarying(); + task.addJob("RenderHUDLayer", renderHUDLayerInputs, shapePlumberForward, mainViewTransformSlot); } -RenderDeferredTaskDebug::RenderDeferredTaskDebug() { -} - -void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input, render::Varying& outputs) { +void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input, render::Varying& outputs, uint mainViewTransformSlot) { const auto& inputs = input.get(); @@ -274,14 +266,13 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input // RenderShadowTask out const auto& shadowOut = inputs.get1(); - - const auto& renderShadowTaskOut = shadowOut[0]; - const auto& shadowFrame = shadowOut[1]; + const auto& renderShadowTaskOut = shadowOut[0]; + const auto& shadowFrame = shadowOut[1]; // Extract the Lighting Stages Current frame ( and zones) const auto lightingStageInputs = inputs.get2(); // Fetch the current frame stacks from all the stages - const auto stageCurrentFrames = lightingStageInputs.get0(); + const auto stageCurrentFrames = lightingStageInputs[0]; const auto lightFrame = stageCurrentFrames[0]; const auto backgroundFrame = stageCurrentFrames[1]; const auto hazeFrame = stageCurrentFrames[2]; @@ -304,33 +295,30 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input const auto& ambientOcclusionFramebuffer = extraDeferredBuffer[2]; const auto& ambientOcclusionUniforms = extraDeferredBuffer[3]; const auto& scatteringResource = extraDeferredBuffer[4]; - const auto& velocityBuffer = extraDeferredBuffer[5]; // GenerateDeferredFrameTransform out const auto& deferredFrameTransform = inputs[6]; - // Jitter out - const auto& jitter = inputs[7]; - // Lighting Model out - const auto& lightingModel = inputs[8]; - + const auto& lightingModel = inputs[7]; + // Antialiasing out + const auto& antialiasingIntensityTexture = inputs[8]; // Light Cluster Grid Debuging job { const auto debugLightClustersInputs = DebugLightClusters::Inputs(deferredFrameTransform, lightingModel, linearDepthTarget, lightClusters).asVarying(); - task.addJob("DebugLightClusters", debugLightClustersInputs); + task.addJob("DebugLightClusters", debugLightClustersInputs, mainViewTransformSlot); } { // Debug the bounds of the rendered items, still look at the zbuffer - task.addJob("DrawMetaBounds", metas); - task.addJob("DrawOpaqueBounds", opaques); - task.addJob("DrawTransparentBounds", transparents); + task.addJob("DrawMetaBounds", metas, mainViewTransformSlot); + task.addJob("DrawOpaqueBounds", opaques, mainViewTransformSlot); + task.addJob("DrawTransparentBounds", transparents, mainViewTransformSlot); - task.addJob("DrawLightBounds", lights); - task.addJob("DrawZones", zones); + task.addJob("DrawLightBounds", lights, mainViewTransformSlot); + task.addJob("DrawZones", zones, mainViewTransformSlot); const auto frustums = task.addJob("ExtractFrustums", shadowFrame); const auto viewFrustum = frustums.getN(ExtractFrustums::VIEW_FRUSTUM); task.addJob("DrawViewFrustum", viewFrustum, glm::vec3(0.0f, 1.0f, 0.0f)); @@ -362,25 +350,25 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input const auto selectedItems = task.addJob("TransparentSelection", selectItemInput, selectionBaseName); // Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true - task.addJob("DrawSelectionBounds", selectedItems); + task.addJob("DrawSelectionBounds", selectedItems, mainViewTransformSlot); } { // Debug the bounds of the layered objects, still look at the zbuffer - task.addJob("DrawInFrontOpaqueBounds", inFrontOpaque); - task.addJob("DrawInFrontTransparentBounds", inFrontTransparent); + task.addJob("DrawInFrontOpaqueBounds", inFrontOpaque, mainViewTransformSlot); + task.addJob("DrawInFrontTransparentBounds", inFrontTransparent, mainViewTransformSlot); } { // Debug the bounds of the layered objects, still look at the zbuffer - task.addJob("DrawHUDOpaqueBounds", hudOpaque); - task.addJob("DrawHUDTransparentBounds", hudTransparent); + task.addJob("DrawHUDOpaqueBounds", hudOpaque, mainViewTransformSlot); + task.addJob("DrawHUDTransparentBounds", hudTransparent, mainViewTransformSlot); } // Debugging stages { // Debugging Deferred buffer job - const auto debugFramebuffers = DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, velocityBuffer, deferredFrameTransform, shadowFrame).asVarying(); - task.addJob("DebugDeferredBuffer", debugFramebuffers); + const auto debugFramebuffers = DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, deferredFrameTransform, shadowFrame, antialiasingIntensityTexture).asVarying(); + task.addJob("DebugDeferredBuffer", debugFramebuffers, mainViewTransformSlot); const auto debugSubsurfaceScatteringInputs = DebugSubsurfaceScattering::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, scatteringResource).asVarying(); @@ -391,8 +379,8 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input // Scene Octree Debugging job { - task.addJob("DrawSceneOctree", spatialSelection); - task.addJob("DrawItemSelection", spatialSelection); + task.addJob("DrawSceneOctree", spatialSelection, mainViewTransformSlot); + task.addJob("DrawItemSelection", spatialSelection, mainViewTransformSlot); } // Status icon rendering job @@ -400,8 +388,7 @@ void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input // Grab a texture map representing the different status icons and assign that to the drawStatusJob auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg"; auto statusIconMap = DependencyManager::get()->getImageTexture(iconMapPath, image::TextureUsage::STRICT_TEXTURE); - const auto drawStatusInputs = DrawStatus::Input(opaques, jitter).asVarying(); - task.addJob("DrawStatus", drawStatusInputs, DrawStatus(statusIconMap)); + task.addJob("DrawStatus", opaques, DrawStatus(statusIconMap, mainViewTransformSlot)); } const auto debugZoneInputs = DebugZoneLighting::Inputs(deferredFrameTransform, lightFrame, backgroundFrame).asVarying(); @@ -462,7 +449,7 @@ void RenderTransparentDeferred::run(const RenderContextPointer& renderContext, c const auto& lightingModel = inputs.get3(); const auto& lightClusters = inputs.get4(); // Not needed yet: const auto& shadowFrame = inputs.get5(); - const auto jitter = inputs.get6(); + const auto& deferredFrameTransform = inputs.get6(); auto deferredLightingEffect = DependencyManager::get(); RenderArgs* args = renderContext->args; @@ -474,18 +461,13 @@ void RenderTransparentDeferred::run(const RenderContextPointer& renderContext, c batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setProjectionJitter(jitter.x, jitter.y); - batch.setViewTransform(viewMat); + batch.setProjectionJitterEnabled(true); + batch.setSavedViewProjectionTransform(_transformSlot); // Setup lighting model for all items; batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer()); batch.setResourceTexture(ru::Texture::AmbientFresnel, lightingModel->getAmbientFresnelLUT()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, deferredFrameTransform->getFrameTransformBuffer()); // Set the light deferredLightingEffect->setupKeyLightBatch(args, batch, *lightFrame); @@ -529,7 +511,7 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const const auto& inItems = inputs.get0(); const auto& lightingModel = inputs.get1(); - const auto jitter = inputs.get2(); + const auto deferredFrameTransform = inputs.get2(); RenderArgs* args = renderContext->args; @@ -540,18 +522,13 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setProjectionJitter(jitter.x, jitter.y); - batch.setViewTransform(viewMat); + batch.setProjectionJitterEnabled(true); + batch.setSavedViewProjectionTransform(_transformSlot); // Setup lighting model for all items; batch.setUniformBuffer(ru::Buffer::LightModel, lightingModel->getParametersBuffer()); batch.setResourceTexture(ru::Texture::AmbientFresnel, lightingModel->getAmbientFresnelLUT()); + batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, deferredFrameTransform->getFrameTransformBuffer()); // From the lighting model define a global shapeKey ORED with individiual keys ShapeKey::Builder keyBuilder; diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 969094488e..ce4348f257 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -43,18 +43,19 @@ protected: class RenderTransparentDeferred { public: - using Inputs = render::VaryingSet7; + using Inputs = render::VaryingSet7; using Config = RenderTransparentDeferredConfig; using JobModel = render::Job::ModelI; - RenderTransparentDeferred(render::ShapePlumberPointer shapePlumber) - : _shapePlumber{ shapePlumber } {} + RenderTransparentDeferred(render::ShapePlumberPointer shapePlumber, uint transformSlot) + : _shapePlumber(shapePlumber), _transformSlot(transformSlot) {} void configure(const Config& config) { _maxDrawn = config.maxDrawn; } void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); protected: render::ShapePlumberPointer _shapePlumber; + uint _transformSlot; int _maxDrawn; // initialized by Config }; @@ -83,13 +84,13 @@ protected: class DrawStateSortDeferred { public: - using Inputs = render::VaryingSet3; + using Inputs = render::VaryingSet3; using Config = DrawStateSortConfig; using JobModel = render::Job::ModelI; - DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber) - : _shapePlumber{ shapePlumber } { + DrawStateSortDeferred(render::ShapePlumberPointer shapePlumber, uint transformSlot) + : _shapePlumber(shapePlumber), _transformSlot(transformSlot) { } void configure(const Config& config) { @@ -100,6 +101,7 @@ public: protected: render::ShapePlumberPointer _shapePlumber; + uint _transformSlot; int _maxDrawn; // initialized by Config bool _stateSort; }; @@ -141,12 +143,8 @@ public: using Config = RenderDeferredTaskConfig; using JobModel = render::Task::ModelI; - RenderDeferredTask(); - void configure(const Config& config); - void build(JobModel& task, const render::Varying& input, render::Varying& output); - -private: + void build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset); }; diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index e34db755b7..fe774a100e 100644 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -66,7 +66,7 @@ void RenderForwardTask::configure(const Config& config) { preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale); } -void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { +void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset) { task.addJob("SetRenderMethodTask", render::Args::FORWARD); // Prepare the ShapePipelines @@ -74,6 +74,9 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend ShapePlumberPointer shapePlumber = std::make_shared(); initForwardPipelines(*shapePlumber); + uint backgroundViewTransformSlot = render::RenderEngine::TS_BACKGROUND_VIEW + transformOffset; + uint mainViewTransformSlot = render::RenderEngine::TS_MAIN_VIEW + transformOffset; + // Unpack inputs const auto& inputs = input.get(); @@ -112,7 +115,7 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend const auto scaledPrimaryFramebuffer = task.addJob("PreparePrimaryBufferForward"); // Prepare deferred, generate the shared Deferred Frame Transform. Only valid with the scaled frame buffer - const auto deferredFrameTransform = task.addJob("DeferredFrameTransform"); + const auto deferredFrameTransform = task.addJob("DeferredFrameTransform", mainViewTransformSlot); // Prepare Forward Framebuffer pass const auto prepareForwardInputs = PrepareForward::Inputs(scaledPrimaryFramebuffer, lightFrame).asVarying(); @@ -123,30 +126,30 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // Draw opaques forward const auto opaqueInputs = DrawForward::Inputs(opaques, lightingModel, hazeFrame).asVarying(); - task.addJob("DrawOpaques", opaqueInputs, shapePlumber, true); + task.addJob("DrawOpaques", opaqueInputs, shapePlumber, true, mainViewTransformSlot); // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job const auto backgroundInputs = DrawBackgroundStage::Inputs(lightingModel, backgroundFrame, hazeFrame).asVarying(); - task.addJob("DrawBackgroundForward", backgroundInputs); + task.addJob("DrawBackgroundForward", backgroundInputs, backgroundViewTransformSlot); // Draw transparent objects forward const auto transparentInputs = DrawForward::Inputs(transparents, lightingModel, hazeFrame).asVarying(); - task.addJob("DrawTransparents", transparentInputs, shapePlumber, false); + task.addJob("DrawTransparents", transparentInputs, shapePlumber, false, mainViewTransformSlot); // Layered const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); - const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, lightingModel, hazeFrame, nullJitter).asVarying(); - const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, lightingModel, hazeFrame, nullJitter).asVarying(); - task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, true); - task.addJob("DrawInFrontTransparent", inFrontTransparentsInputs, false); + const auto inFrontOpaquesInputs = DrawLayered3D::Inputs(inFrontOpaque, deferredFrameTransform, lightingModel, hazeFrame).asVarying(); + const auto inFrontTransparentsInputs = DrawLayered3D::Inputs(inFrontTransparent, deferredFrameTransform, lightingModel, hazeFrame).asVarying(); + task.addJob("DrawInFrontOpaque", inFrontOpaquesInputs, shapePlumber, true, false, mainViewTransformSlot); + task.addJob("DrawInFrontTransparent", inFrontTransparentsInputs, shapePlumber, false, false, mainViewTransformSlot); { // Debug the bounds of the rendered items, still look at the zbuffer - task.addJob("DrawMetaBounds", metas); - task.addJob("DrawBounds", opaques); - task.addJob("DrawTransparentBounds", transparents); + task.addJob("DrawMetaBounds", metas, mainViewTransformSlot); + task.addJob("DrawBounds", opaques, mainViewTransformSlot); + task.addJob("DrawTransparentBounds", transparents, mainViewTransformSlot); - task.addJob("DrawZones", zones); + task.addJob("DrawZones", zones, mainViewTransformSlot); const auto debugZoneInputs = DebugZoneLighting::Inputs(deferredFrameTransform, lightFrame, backgroundFrame).asVarying(); task.addJob("DrawZoneStack", debugZoneInputs); } @@ -161,8 +164,8 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend const auto toneMappingInputs = ToneMapAndResample::Input(resolvedFramebuffer, destFramebuffer).asVarying(); const auto toneMappedBuffer = task.addJob("ToneMapping", toneMappingInputs); // HUD Layer - const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(toneMappedBuffer, lightingModel, hudOpaque, hudTransparent, hazeFrame).asVarying(); - task.addJob("RenderHUDLayer", renderHUDLayerInputs); + const auto renderHUDLayerInputs = RenderHUDLayerTask::Input(toneMappedBuffer, lightingModel, hudOpaque, hudTransparent, hazeFrame, deferredFrameTransform).asVarying(); + task.addJob("RenderHUDLayer", renderHUDLayerInputs, shapePlumber, mainViewTransformSlot); } gpu::FramebufferPointer PreparePrimaryFramebufferMSAA::createFramebuffer(const char* name, const glm::uvec2& frameSize, int numSamples) { @@ -264,12 +267,7 @@ void DrawForward::run(const RenderContextPointer& renderContext, const Inputs& i args->_batch = &batch; // Setup projection - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); + batch.setSavedViewProjectionTransform(_transformSlot); batch.setModelTransform(Transform()); // Setup lighting model for all items; diff --git a/libraries/render-utils/src/RenderForwardTask.h b/libraries/render-utils/src/RenderForwardTask.h index 6833e42449..c3deb002b6 100644 --- a/libraries/render-utils/src/RenderForwardTask.h +++ b/libraries/render-utils/src/RenderForwardTask.h @@ -36,7 +36,7 @@ public: RenderForwardTask() {} void configure(const Config& config); - void build(JobModel& task, const render::Varying& input, render::Varying& output); + void build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset); }; @@ -93,13 +93,15 @@ public: using Inputs = render::VaryingSet3; using JobModel = render::Job::ModelI; - DrawForward(const render::ShapePlumberPointer& shapePlumber, bool opaquePass) : _shapePlumber(shapePlumber), _opaquePass(opaquePass) {} + DrawForward(const render::ShapePlumberPointer& shapePlumber, bool opaquePass, uint transformSlot) : + _shapePlumber(shapePlumber), _opaquePass(opaquePass), _transformSlot(transformSlot) {} void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); private: render::ShapePlumberPointer _shapePlumber; bool _opaquePass; + uint _transformSlot; }; #endif // hifi_RenderForwardTask_h diff --git a/libraries/render-utils/src/RenderHUDLayerTask.cpp b/libraries/render-utils/src/RenderHUDLayerTask.cpp index 743e59eebc..b3720a4cf7 100644 --- a/libraries/render-utils/src/RenderHUDLayerTask.cpp +++ b/libraries/render-utils/src/RenderHUDLayerTask.cpp @@ -1,6 +1,7 @@ // // Created by Sam Gateau on 2019/06/14 // Copyright 2013-2019 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -24,12 +25,8 @@ void CompositeHUD::run(const RenderContextPointer& renderContext, const gpu::Fra // Grab the HUD texture #if !defined(DISABLE_QML) gpu::doInBatch("CompositeHUD", renderContext->args->_context, [&](gpu::Batch& batch) { - glm::mat4 projMat; - Transform viewMat; - renderContext->args->getViewFrustum().evalProjectionMatrix(projMat); - renderContext->args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat, true); + PROFILE_RANGE_BATCH(batch, "HUD"); + batch.setSavedViewProjectionTransform(_transformSlot); if (inputs) { batch.setFramebuffer(inputs); } @@ -40,7 +37,8 @@ void CompositeHUD::run(const RenderContextPointer& renderContext, const gpu::Fra #endif } -void RenderHUDLayerTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { +void RenderHUDLayerTask::build(JobModel& task, const render::Varying& input, render::Varying& output, + render::ShapePlumberPointer shapePlumber, uint transformSlot) { const auto& inputs = input.get(); const auto& primaryFramebuffer = inputs[0]; @@ -48,14 +46,15 @@ void RenderHUDLayerTask::build(JobModel& task, const render::Varying& input, ren const auto& hudOpaque = inputs[2]; const auto& hudTransparent = inputs[3]; const auto& hazeFrame = inputs[4]; + const auto& deferredFrameTransform = inputs[5]; // Composite the HUD and HUD overlays - task.addJob("HUD", primaryFramebuffer); + task.addJob("HUD", primaryFramebuffer, transformSlot); // And HUD Layer objects const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); - const auto hudOpaquesInputs = DrawLayered3D::Inputs(hudOpaque, lightingModel, hazeFrame, nullJitter).asVarying(); - const auto hudTransparentsInputs = DrawLayered3D::Inputs(hudTransparent, lightingModel, hazeFrame, nullJitter).asVarying(); - task.addJob("DrawHUDOpaque", hudOpaquesInputs, true); - task.addJob("DrawHUDTransparent", hudTransparentsInputs, false); + const auto hudOpaquesInputs = DrawLayered3D::Inputs(hudOpaque, deferredFrameTransform, lightingModel, hazeFrame).asVarying(); + const auto hudTransparentsInputs = DrawLayered3D::Inputs(hudTransparent, deferredFrameTransform, lightingModel, hazeFrame).asVarying(); + task.addJob("DrawHUDOpaque", hudOpaquesInputs, shapePlumber, true, false, transformSlot); + task.addJob("DrawHUDTransparent", hudTransparentsInputs, shapePlumber, false, false, transformSlot); } diff --git a/libraries/render-utils/src/RenderHUDLayerTask.h b/libraries/render-utils/src/RenderHUDLayerTask.h index c30b0498a8..5e70e73584 100644 --- a/libraries/render-utils/src/RenderHUDLayerTask.h +++ b/libraries/render-utils/src/RenderHUDLayerTask.h @@ -1,6 +1,7 @@ // // Created by Sam Gateau on 2019/06/14 // Copyright 2013-2019 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -11,6 +12,7 @@ #include "LightingModel.h" #include "HazeStage.h" +#include "DeferredFrameTransform.h" class CompositeHUD { public: @@ -19,16 +21,21 @@ public: //using Inputs = gpu::FramebufferPointer; using JobModel = render::Job::ModelI; + CompositeHUD(uint transformSlot) : _transformSlot(transformSlot) {} + void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& inputs); + +private: + uint _transformSlot; }; class RenderHUDLayerTask { public: // Framebuffer where to draw, lighting model, opaque items, transparent items - using Input = render::VaryingSet5; + using Input = render::VaryingSet6; using JobModel = render::Task::ModelI; - void build(JobModel& task, const render::Varying& input, render::Varying& output); + void build(JobModel& task, const render::Varying& input, render::Varying& output, render::ShapePlumberPointer shapePlumber, uint transformSlot); }; #endif // hifi_RenderHUDLayerTask_h \ No newline at end of file diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index ccbbb4d66e..f136282b10 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -312,7 +312,7 @@ void addPlumberPipeline(ShapePlumber& plumber, bool isWireframed = (i & 2); for (int cullFaceMode = graphics::MaterialKey::CullFaceMode::CULL_NONE; cullFaceMode < graphics::MaterialKey::CullFaceMode::NUM_CULL_FACE_MODES; cullFaceMode++) { auto state = std::make_shared(); - key.isTranslucent() ? PrepareStencil::testMask(*state) : PrepareStencil::testMaskDrawShape(*state); + key.isTranslucent() ? PrepareStencil::testMaskResetNoAA(*state) : PrepareStencil::testMaskDrawShape(*state); // Depth test depends on transparency state->setDepthTest(true, !key.isTranslucent(), gpu::LESS_EQUAL); diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 93a3ff2d67..6dbad76e80 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -15,7 +15,7 @@ #include "RenderDeferredTask.h" #include "RenderForwardTask.h" -void RenderShadowsAndDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask) { +void RenderShadowsAndDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask, uint8_t transformOffset) { task.addJob("SetRenderMethodTask", render::Args::DEFERRED); const auto items = input.getN(0); @@ -28,16 +28,16 @@ void RenderShadowsAndDeferredTask::build(JobModel& task, const render::Varying& const auto shadowTaskOut = task.addJob("RenderShadowTask", shadowTaskIn, cullFunctor, tagBits, tagMask); const auto renderDeferredInput = RenderDeferredTask::Input(items, lightingModel, lightingStageFramesAndZones, shadowTaskOut).asVarying(); - task.addJob("RenderDeferredTask", renderDeferredInput); + task.addJob("RenderDeferredTask", renderDeferredInput, transformOffset); } -void DeferredForwardSwitchJob::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask) { - task.addBranch("RenderShadowsAndDeferredTask", 0, input, cullFunctor, tagBits, tagMask); +void DeferredForwardSwitchJob::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask, uint8_t transformOffset) { + task.addBranch("RenderShadowsAndDeferredTask", 0, input, cullFunctor, tagBits, tagMask, transformOffset); - task.addBranch("RenderForwardTask", 1, input); + task.addBranch("RenderForwardTask", 1, input, transformOffset); } -void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask) { +void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask, TransformOffset transformOffset) { const auto items = task.addJob("FetchCullSort", cullFunctor, tagBits, tagMask); // Issue the lighting model, aka the big global settings for the view @@ -48,9 +48,9 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render: #ifndef Q_OS_ANDROID const auto deferredForwardIn = DeferredForwardSwitchJob::Input(items, lightingModel, lightingStageFramesAndZones).asVarying(); - task.addJob("DeferredForwardSwitch", deferredForwardIn, cullFunctor, tagBits, tagMask); + task.addJob("DeferredForwardSwitch", deferredForwardIn, cullFunctor, tagBits, tagMask, transformOffset); #else const auto renderInput = RenderForwardTask::Input(items, lightingModel, lightingStageFramesAndZones).asVarying(); - task.addJob("RenderForwardTask", renderInput); + task.addJob("RenderForwardTask", renderInput, transformOffset); #endif } diff --git a/libraries/render-utils/src/RenderViewTask.h b/libraries/render-utils/src/RenderViewTask.h index cdb56a2189..8911431c88 100644 --- a/libraries/render-utils/src/RenderViewTask.h +++ b/libraries/render-utils/src/RenderViewTask.h @@ -24,7 +24,8 @@ public: RenderShadowsAndDeferredTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, + uint8_t tagBits, uint8_t tagMask, uint8_t transformOffset); }; @@ -36,7 +37,8 @@ public: DeferredForwardSwitchJob() {} void configure(const render::SwitchConfig& config) {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, + uint8_t tagBits, uint8_t tagMask, uint8_t transformOffset); }; @@ -47,7 +49,13 @@ public: RenderViewTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00); + enum TransformOffset: uint8_t { + MAIN_VIEW = 0, + SECONDARY_VIEW = 2 // each view uses 1 transform for the main view, and one for the background, so these need to be increments of 2 + }; + + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, + uint8_t tagBits = 0x00, uint8_t tagMask = 0x00, TransformOffset transformOffset = TransformOffset::MAIN_VIEW); }; diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index 7c89e6b601..76ee597592 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -122,6 +122,12 @@ void PrepareStencil::testMask(gpu::State& state) { gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); } +// Pass if this area has NOT been marked as MASK or anything containing MASK and reset NO_AA if it passes +void PrepareStencil::testMaskResetNoAA(gpu::State& state) { + state.setStencilTest(true, STENCIL_NO_AA, gpu::State::StencilTest(STENCIL_MASK, STENCIL_MASK, gpu::NOT_EQUAL, + gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE)); +} + // Pass if this area has NOT been marked as NO_AA or anything containing NO_AA void PrepareStencil::testNoAA(gpu::State& state) { state.setStencilTest(true, 0x00, gpu::State::StencilTest(STENCIL_NO_AA, STENCIL_NO_AA, gpu::NOT_EQUAL, diff --git a/libraries/render-utils/src/StencilMaskPass.h b/libraries/render-utils/src/StencilMaskPass.h index bca2ef17a5..5599d792ba 100644 --- a/libraries/render-utils/src/StencilMaskPass.h +++ b/libraries/render-utils/src/StencilMaskPass.h @@ -52,6 +52,7 @@ public: static void drawMask(gpu::State& state); static void drawBackground(gpu::State& state); static void testMask(gpu::State& state); + static void testMaskResetNoAA(gpu::State& state); static void testNoAA(gpu::State& state); static void testBackground(gpu::State& state); static void testShape(gpu::State& state); diff --git a/libraries/render-utils/src/VelocityBufferPass.cpp b/libraries/render-utils/src/VelocityBufferPass.cpp deleted file mode 100644 index 9437ead3b2..0000000000 --- a/libraries/render-utils/src/VelocityBufferPass.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// -// VelocityBufferPass.cpp -// libraries/render-utils/src/ -// -// Created by Sam Gateau 8/15/2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -#include "VelocityBufferPass.h" - -#include - -#include -#include - -#include "StencilMaskPass.h" -#include "render-utils/ShaderConstants.h" - -namespace ru { - using render_utils::slot::texture::Texture; - using render_utils::slot::buffer::Buffer; -} - -VelocityFramebuffer::VelocityFramebuffer() { -} - - -void VelocityFramebuffer::updatePrimaryDepth(const gpu::TexturePointer& depthBuffer) { - //If the depth buffer or size changed, we need to delete our FBOs - bool reset = false; - if ((_primaryDepthTexture != depthBuffer)) { - _primaryDepthTexture = depthBuffer; - reset = true; - } - if (_primaryDepthTexture) { - auto newFrameSize = glm::ivec2(_primaryDepthTexture->getDimensions()); - if (_frameSize != newFrameSize) { - _frameSize = newFrameSize; - _halfFrameSize = newFrameSize >> 1; - - reset = true; - } - } - - if (reset) { - clear(); - } -} - -void VelocityFramebuffer::clear() { - _velocityFramebuffer.reset(); - _velocityTexture.reset(); -} - -void VelocityFramebuffer::allocate() { - - auto width = _frameSize.x; - auto height = _frameSize.y; - - // For Velocity Buffer: - _velocityTexture = gpu::Texture::createRenderBuffer(gpu::Element(gpu::VEC2, gpu::HALF, gpu::RGB), width, height, gpu::Texture::SINGLE_MIP, - gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR)); - _velocityFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("velocity")); - _velocityFramebuffer->setRenderBuffer(0, _velocityTexture); - _velocityFramebuffer->setDepthStencilBuffer(_primaryDepthTexture, _primaryDepthTexture->getTexelFormat()); -} - -gpu::FramebufferPointer VelocityFramebuffer::getVelocityFramebuffer() { - if (!_velocityFramebuffer) { - allocate(); - } - return _velocityFramebuffer; -} - -gpu::TexturePointer VelocityFramebuffer::getVelocityTexture() { - if (!_velocityTexture) { - allocate(); - } - return _velocityTexture; -} - -VelocityBufferPass::VelocityBufferPass() { -} - -void VelocityBufferPass::configure(const Config& config) { -} - -void VelocityBufferPass::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - - RenderArgs* args = renderContext->args; - - const auto& frameTransform = inputs.get0(); - const auto& deferredFramebuffer = inputs.get1(); - - if (!_gpuTimer) { - _gpuTimer = std::make_shared < gpu::RangeTimer>(__FUNCTION__); - } - - if (!_velocityFramebuffer) { - _velocityFramebuffer = std::make_shared(); - } - _velocityFramebuffer->updatePrimaryDepth(deferredFramebuffer->getPrimaryDepthTexture()); - - auto depthBuffer = deferredFramebuffer->getPrimaryDepthTexture(); - - auto velocityFBO = _velocityFramebuffer->getVelocityFramebuffer(); - auto velocityTexture = _velocityFramebuffer->getVelocityTexture(); - - outputs.edit0() = _velocityFramebuffer; - outputs.edit1() = velocityFBO; - outputs.edit2() = velocityTexture; - - auto cameraMotionPipeline = getCameraMotionPipeline(renderContext); - - auto fullViewport = args->_viewport; - - gpu::doInBatch("VelocityBufferPass::run", args->_context, [=](gpu::Batch& batch) { - _gpuTimer->begin(batch); - batch.enableStereo(false); - - batch.setViewportTransform(fullViewport); - batch.setProjectionTransform(glm::mat4()); - batch.resetViewTransform(); - batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_velocityFramebuffer->getDepthFrameSize(), fullViewport)); - - batch.setUniformBuffer(ru::Buffer::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); - - // Velocity buffer camera motion - batch.setFramebuffer(velocityFBO); - batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f)); - batch.setPipeline(cameraMotionPipeline); - batch.setResourceTexture(ru::Texture::TaaDepth, depthBuffer); - batch.draw(gpu::TRIANGLE_STRIP, 4); - - _gpuTimer->end(batch); - }); - - auto config = std::static_pointer_cast(renderContext->jobConfig); - config->setGPUBatchRunTime(_gpuTimer->getGPUAverage(), _gpuTimer->getBatchAverage()); -} - - -const gpu::PipelinePointer& VelocityBufferPass::getCameraMotionPipeline(const render::RenderContextPointer& renderContext) { - if (!_cameraMotionPipeline) { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::velocityBuffer_cameraMotion); - gpu::StatePointer state = std::make_shared(); - - // Stencil test the curvature pass for objects pixels only, not the background - // PrepareStencil::testShape(*state); - - state->setColorWriteMask(true, true, false, false); - - // Good to go add the brand new pipeline - _cameraMotionPipeline = gpu::Pipeline::create(program, state); - } - - return _cameraMotionPipeline; -} - - - diff --git a/libraries/render-utils/src/VelocityBufferPass.h b/libraries/render-utils/src/VelocityBufferPass.h deleted file mode 100644 index 50b994f6db..0000000000 --- a/libraries/render-utils/src/VelocityBufferPass.h +++ /dev/null @@ -1,89 +0,0 @@ -// -// VelocityBufferPass.h -// libraries/render-utils/src/ -// -// Created by Sam Gateau 8/15/2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_VelocityBufferPass_h -#define hifi_VelocityBufferPass_h - -#include "SurfaceGeometryPass.h" - - -// VelocityFramebuffer is a helper class gathering in one place theframebuffers and targets describing the surface geometry linear depth -// from a z buffer -class VelocityFramebuffer { -public: - VelocityFramebuffer(); - - gpu::FramebufferPointer getVelocityFramebuffer(); - gpu::TexturePointer getVelocityTexture(); - - // Update the depth buffer which will drive the allocation of all the other resources according to its size. - void updatePrimaryDepth(const gpu::TexturePointer& depthBuffer); - - gpu::TexturePointer getPrimaryDepthTexture(); - const glm::ivec2& getDepthFrameSize() const { return _frameSize; } - - void setResolutionLevel(int level); - int getResolutionLevel() const { return _resolutionLevel; } - -protected: - void clear(); - void allocate(); - - gpu::TexturePointer _primaryDepthTexture; - - gpu::FramebufferPointer _velocityFramebuffer; - gpu::TexturePointer _velocityTexture; - - glm::ivec2 _frameSize; - glm::ivec2 _halfFrameSize; - int _resolutionLevel{ 0 }; -}; - -using VelocityFramebufferPointer = std::shared_ptr; - -class VelocityBufferPassConfig : public render::GPUJobConfig { - Q_OBJECT - Q_PROPERTY(float depthThreshold MEMBER depthThreshold NOTIFY dirty) - -public: - VelocityBufferPassConfig() : render::GPUJobConfig(true) {} - - float depthThreshold{ 5.0f }; - -signals: - void dirty(); -}; - -class VelocityBufferPass { -public: - using Inputs = render::VaryingSet2; - using Outputs = render::VaryingSet3; - using Config = VelocityBufferPassConfig; - using JobModel = render::Job::ModelIO; - - VelocityBufferPass(); - - void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); - -private: - typedef gpu::BufferView UniformBufferView; - - VelocityFramebufferPointer _velocityFramebuffer; - - const gpu::PipelinePointer& getCameraMotionPipeline(const render::RenderContextPointer& renderContext); - gpu::PipelinePointer _cameraMotionPipeline; - - gpu::RangeTimerPointer _gpuTimer; -}; - - -#endif // hifi_VelocityBufferPass_h diff --git a/libraries/render-utils/src/VelocityWrite.slh b/libraries/render-utils/src/VelocityWrite.slh new file mode 100644 index 0000000000..0ef0242787 --- /dev/null +++ b/libraries/render-utils/src/VelocityWrite.slh @@ -0,0 +1,34 @@ + +<@if not VELOCITY_WRITE_SLH@> +<@def VELOCITY_WRITE_SLH@> + +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + +vec2 getEyeTexcoordPos() { + // No need to add 0.5 as, by default, frag coords are pixel centered at (0.5, 0.5) + vec2 texCoordPos = gl_FragCoord.xy; + texCoordPos *= cam_getInvWidthHeight(); + texCoordPos.x -= cam_getStereoSide(); + return texCoordPos; +} + +vec2 packVelocity(vec4 prevPositionCS) { + vec2 uv = getEyeTexcoordPos(); + vec2 prevUV = (prevPositionCS.xy / prevPositionCS.w) * 0.5 + 0.5; + vec2 deltaUV = uv - prevUV; + // Velocity should be computed without any jitter inside. + return deltaUV; +} + +<@endif@> diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 5332e13816..5e8a8df9d3 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -87,7 +87,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::zone_drawKeyLight); gpu::StatePointer state = std::make_shared(); - PrepareStencil::testMask(*state); + PrepareStencil::testMaskResetNoAA(*state); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); _keyLightPipeline = gpu::Pipeline::create(program, state); } @@ -99,7 +99,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getAmbientPipeline() { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::zone_drawAmbient); gpu::StatePointer state = std::make_shared(); - PrepareStencil::testMask(*state); + PrepareStencil::testMaskResetNoAA(*state); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); _ambientPipeline = gpu::Pipeline::create(program, state); } @@ -110,7 +110,7 @@ const gpu::PipelinePointer& DebugZoneLighting::getBackgroundPipeline() { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::zone_drawSkybox); gpu::StatePointer state = std::make_shared(); - PrepareStencil::testMask(*state); + PrepareStencil::testMaskResetNoAA(*state); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); _backgroundPipeline = gpu::Pipeline::create(program, state); } diff --git a/libraries/render-utils/src/aa_blend.slf b/libraries/render-utils/src/aa_blend.slf new file mode 100644 index 0000000000..8391bba221 --- /dev/null +++ b/libraries/render-utils/src/aa_blend.slf @@ -0,0 +1,52 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// <$_SCRIBE_FILENAME$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Raffi Bedikian on 8/30/15 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/ShaderConstants.h@> + +layout(location=0) in vec2 varTexCoord0; +layout(location=0) out vec4 outFragColor; + +layout(binding=0) uniform sampler2D colorTexture; + +struct aaBlendParams { + vec4 sharpenIntensity; +}; + +layout(binding=0) uniform aaBlendParamsBuffer { + aaBlendParams params; +}; + +void main(void) { + if (params.sharpenIntensity.x > 0.0) { + vec4 pixels[9]; + vec4 sharpenedPixel; + pixels[0] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy) + ivec2(-1,-1), 0); + pixels[1] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy) + ivec2(0,-1), 0); + pixels[2] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy) + ivec2(1,-1), 0); + + pixels[3] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy) + ivec2(-1,0), 0); + pixels[4] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy), 0); + pixels[5] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy) + ivec2(1,0), 0); + + pixels[6] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy) + ivec2(-1,1), 0); + pixels[7] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy) + ivec2(0,1), 0); + pixels[8] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy) + ivec2(1,1), 0); + + sharpenedPixel = pixels[4] * 6.8 - (pixels[1] + pixels[3] + pixels[5] + pixels[7]) - (pixels[0] + pixels[2] + pixels[6] + pixels[8]) *0.7; + + vec4 minColor = max(vec4(0), pixels[4] - vec4(0.5)); + vec4 maxColor = pixels[4] + vec4(0.5); + outFragColor = clamp(pixels[4] + sharpenedPixel * params.sharpenIntensity.x, minColor, maxColor); + } else { + outFragColor = texelFetch(colorTexture, ivec2(gl_FragCoord.xy), 0); + } +} diff --git a/libraries/render-utils/src/deferred_light_limited.slv b/libraries/render-utils/src/deferred_light_limited.slv index 0126d54664..5757e99f78 100644 --- a/libraries/render-utils/src/deferred_light_limited.slv +++ b/libraries/render-utils/src/deferred_light_limited.slv @@ -54,8 +54,7 @@ void main(void) { #ifdef GPU_TRANSFORM_IS_STEREO #ifdef GPU_TRANSFORM_STEREO_SPLIT_SCREEN - TransformCamera cam = getTransformCamera(); - <$transformStereoClipsSpace(cam, pos)$> + <$transformStereoClipSpace(pos)$> #endif #endif diff --git a/libraries/render-utils/src/directional_skybox_light.slf b/libraries/render-utils/src/directional_skybox_light.slf index 20026283be..77935c3e19 100644 --- a/libraries/render-utils/src/directional_skybox_light.slf +++ b/libraries/render-utils/src/directional_skybox_light.slf @@ -30,12 +30,11 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; layout(location=0) out vec4 _fragColor; void main(void) { - DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); - DeferredFragment frag = unpackDeferredFragment(deferredTransform, _texCoord0); + DeferredFragment frag = unpackDeferredFragment(_texCoord0); <@if HIFI_USE_SHADOW@> vec4 viewPos = vec4(frag.position.xyz, 1.0); - vec4 worldPos = getViewInverse() * viewPos; + vec4 worldPos = getViewInverse(frag.side) * viewPos; Light shadowLight = getKeyLight(); vec3 worldLightDirection = getLightDirection(shadowLight); float shadowAttenuation = evalShadowAttenuation(worldLightDirection, worldPos, -viewPos.z, frag.normal); @@ -55,7 +54,7 @@ void main(void) { <@if HIFI_USE_AMBIENT@> vec3 color = evalAmbientSphereGlobalColor( - getViewInverse(), + getViewInverse(frag.side), shadowAttenuation, frag.obscurance, frag.position.xyz, @@ -69,7 +68,7 @@ void main(void) { lowNormalCurvature); <@else@> vec3 color = evalSkyboxGlobalColor( - getViewInverse(), + getViewInverse(frag.side), shadowAttenuation, frag.obscurance, frag.position.xyz, diff --git a/libraries/render-utils/src/drawWorkloadProxy.slf b/libraries/render-utils/src/drawWorkloadProxy.slf index f0bd9d474c..107d1a6c4b 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slf +++ b/libraries/render-utils/src/drawWorkloadProxy.slf @@ -16,6 +16,7 @@ layout(location=0) in vec4 varColor; layout(location=1) in vec3 varTexcoord; layout(location=2) in vec3 varEyePos; +layout(location=3) in vec4 _prevPositionCS; void main(void) { if (varColor.w > 0.0) { @@ -28,6 +29,7 @@ void main(void) { } packDeferredFragmentUnlit( + _prevPositionCS, vec3(0.0, 1.0, 0.0), 1.0, varColor.rgb); diff --git a/libraries/render-utils/src/drawWorkloadProxy.slv b/libraries/render-utils/src/drawWorkloadProxy.slv index e485f14a93..a6e5ab7022 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slv +++ b/libraries/render-utils/src/drawWorkloadProxy.slv @@ -7,6 +7,7 @@ // // Created by Sam Gateau on 6/29/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -22,6 +23,20 @@ layout(location=0) out vec4 varColor; layout(location=1) out vec3 varTexcoord; layout(location=2) out vec3 varEyePos; +layout(location=3) out vec4 _prevPositionCS; + +vec4 getPosition(WorkloadProxy proxy, vec4 spriteVert, vec4 proxyPosEye) { + vec3 dirZ = -normalize(proxyPosEye.xyz); + vec3 dirX = normalize(cross(vec3(0.0, 1.0, 0.0), dirZ)); + vec3 dirY = vec3(0.0, 1.0, 0.0); + // Workaround for Nvidia driver bug + vec4 pos = vec4(1.0, 1.0, 1.0, 1.0); + pos.x = proxyPosEye.x + proxy.sphere.w * ( dirX.x * spriteVert.x + dirY.x * spriteVert.y + dirZ.x * spriteVert.z); + pos.y = proxyPosEye.y + proxy.sphere.w * ( dirX.y * spriteVert.x + dirY.y * spriteVert.y + dirZ.y * spriteVert.z); + pos.z = proxyPosEye.z + proxy.sphere.w * ( dirX.z * spriteVert.x + dirY.z * spriteVert.y + dirZ.z * spriteVert.z); + return pos; + //return vec4(proxyPosEye.xyz + proxy.sphere.w * (dirX * spriteVert.x + dirY * spriteVert.y /* + dirZ * spriteVert.z*/), 1.0); +} void main(void) { const vec4 UNIT_SPRITE[3] = vec4[3]( @@ -44,22 +59,17 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); vec4 proxyPosEye; - <$transformModelToEyePos(cam, obj, proxyPosWorld, proxyPosEye)$> + vec4 prevProxyPosEye; + <$transformModelToEyePosAndPrevEyePos(cam, obj, proxyPosWorld, proxyPosEye, prevProxyPosEye)$> // Define the billboarded space - vec3 dirZ = -normalize(proxyPosEye.xyz); - vec3 dirX = normalize(cross(vec3(0.0, 1.0, 0.0), dirZ)); - vec3 dirY = vec3(0.0, 1.0, 0.0); + vec4 pos = getPosition(proxy, spriteVert, proxyPosEye); + vec4 prevPos = getPosition(proxy, spriteVert, prevProxyPosEye); - //vec4 pos = vec4(proxyPosEye.xyz + proxy.sphere.w * ( dirX * spriteVert.x + dirY * spriteVert.y + dirZ * spriteVert.z), 1.0); - //Nvidia driver workaround - vec4 pos = vec4(1.0, 1.0, 1.0, 1.0); - pos.x = proxyPosEye.x + proxy.sphere.w * ( dirX.x * spriteVert.x + dirY.x * spriteVert.y + dirZ.x * spriteVert.z); - pos.y = proxyPosEye.y + proxy.sphere.w * ( dirX.y * spriteVert.x + dirY.y * spriteVert.y + dirZ.y * spriteVert.z); - pos.z = proxyPosEye.z + proxy.sphere.w * ( dirX.z * spriteVert.x + dirY.z * spriteVert.y + dirZ.z * spriteVert.z); varEyePos = pos.xyz; varTexcoord = spriteVert.xyz; <$transformEyeToClipPos(cam, pos, gl_Position)$> + <$transformPrevEyeToPrevClipPos(cam, prevPos, _prevPositionCS)$>; // Convert region to color int region = floatBitsToInt(proxy.region.x); diff --git a/libraries/render-utils/src/drawWorkloadView.slf b/libraries/render-utils/src/drawWorkloadView.slf index b638824204..c838a2f1c8 100644 --- a/libraries/render-utils/src/drawWorkloadView.slf +++ b/libraries/render-utils/src/drawWorkloadView.slf @@ -17,6 +17,7 @@ layout(location=0) in vec4 varColor; layout(location=1) in vec3 varTexcoord; layout(location=2) in vec3 varEyePos; +layout(location=3) in vec4 _prevPositionCS; void main(void) { if (varColor.w > 0.0) { @@ -29,6 +30,7 @@ void main(void) { } packDeferredFragmentUnlit( + _prevPositionCS, vec3(0.0, 1.0, 0.0), 1.0, varColor.rgb); diff --git a/libraries/render-utils/src/drawWorkloadView.slv b/libraries/render-utils/src/drawWorkloadView.slv index 2fdf3d773e..fe7c3f6692 100644 --- a/libraries/render-utils/src/drawWorkloadView.slv +++ b/libraries/render-utils/src/drawWorkloadView.slv @@ -21,6 +21,7 @@ layout(location=0) out vec4 varColor; layout(location=1) out vec3 varTexcoord; layout(location=2) out vec3 varEyePos; +layout(location=3) out vec4 _prevPositionCS; const int NUM_VERTICES_PER_SEGMENT = 2; const int NUM_SEGMENT_PER_VIEW_REGION = 65; @@ -36,6 +37,12 @@ LAYOUT_STD140(binding=0) uniform DrawMeshBuffer { DrawMesh _drawMeshBuffer; }; +vec4 getPosition(int regionID, int segmentVertexID, vec4 posEye, vec3 tanEye) { + vec3 lateralDir = normalize(cross(vec3(0.0, 0.0, 1.0), normalize(tanEye))); + posEye.xyz += (0.005 * abs(posEye.z) * float(regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; + return posEye; +} + void main(void) { int viewID = gl_VertexID / NUM_VERTICES_PER_VIEW; int viewVertexID = gl_VertexID - viewID * NUM_VERTICES_PER_VIEW; @@ -51,8 +58,6 @@ void main(void) { vec4 spriteVert = vec4(segment.y, 0.0, segment.x, 1.0); vec3 spriteTan = vec3(segment.x, 0.0, -segment.y); - vec3 lateralDir = vec3(0.0, -1.0 + 2.0 * float(segmentVertexID), 0.0); - WorkloadView view = getWorkloadView(viewID); vec4 region = view.regions[regionID]; vec4 proxyPosWorld = vec4(region.xyz, 1.0); @@ -74,15 +79,19 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); vec4 posEye; - <$transformModelToEyePos(cam, obj, pos, posEye)$> + vec4 prevPosEye; + <$transformModelToEyePosAndPrevEyePos(cam, obj, pos, posEye, prevPosEye)$> vec3 tanEye; + vec3 prevTanEye; <$transformModelToEyeDir(cam, obj, originSpaceTan, tanEye)$> + <$transformModelToPrevEyeDir(cam, obj, originSpaceTan, prevTanEye)$> - lateralDir = normalize(cross(vec3(0.0, 0.0, 1.0), normalize(tanEye))); - posEye.xyz += (0.005 * abs(posEye.z) * float(regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; + posEye = getPosition(regionID, segmentVertexID, posEye, tanEye); + prevPosEye = getPosition(regionID, segmentVertexID, prevPosEye, prevTanEye); varEyePos = posEye.xyz; <$transformEyeToClipPos(cam, posEye, gl_Position)$> + <$transformPrevEyeToPrevClipPos(cam, prevPosEye, _prevPositionCS)$>; varTexcoord = spriteVert.xyz; diff --git a/libraries/render-utils/src/grid.slf b/libraries/render-utils/src/grid.slf index 8d54dfef4a..ff98e2b56d 100644 --- a/libraries/render-utils/src/grid.slf +++ b/libraries/render-utils/src/grid.slf @@ -5,6 +5,7 @@ // // Created by Zach Pomerantz on 2/16/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -15,6 +16,7 @@ <@if not HIFI_USE_FORWARD@> <@include DeferredBufferWrite.slh@> + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) in vec4 _prevPositionCS; <@else@> layout(location=0) out vec4 _fragColor0; <@endif@> @@ -43,9 +45,9 @@ void main(void) { <@if not HIFI_USE_FORWARD@> vec3 NORMAL = vec3(1.0, 0.0, 0.0); <@if not HIFI_USE_TRANSLUCENT@> - packDeferredFragmentUnlit(NORMAL, 1.0, varColor.rgb); + packDeferredFragmentUnlit(_prevPositionCS, NORMAL, 1.0, varColor.rgb); <@else@> - packDeferredFragmentTranslucent(NORMAL, varColor.a, varColor.rgb, DEFAULT_ROUGHNESS); + packDeferredFragmentTranslucent(_prevPositionCS, NORMAL, varColor.a, varColor.rgb, DEFAULT_ROUGHNESS); <@endif@> <@else@> _fragColor0 = varColor; diff --git a/libraries/render-utils/src/grid.slv b/libraries/render-utils/src/grid.slv new file mode 100644 index 0000000000..1e71893159 --- /dev/null +++ b/libraries/render-utils/src/grid.slv @@ -0,0 +1,36 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// <$_SCRIBE_FILENAME$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by HifiExperiments on 7/24/2020 +// Copyright 2020 Vircadia contributors. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include render-utils/ShaderConstants.h@> + +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +layout(location=GPU_ATTR_POSITION) out vec3 varPosition; +layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) out vec4 _prevPositionCS; +layout(location=GPU_ATTR_NORMAL) out vec3 varNormal; +layout(location=GPU_ATTR_TEXCOORD0) out vec2 varTexCoord0; +layout(location=GPU_ATTR_COLOR) out vec4 varColor; + +void main(void) { + varTexCoord0 = inTexCoord0.st; + varColor = color_sRGBAToLinear(inColor); + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPosAndPrevClipPos(cam, obj, inPosition, gl_Position, _prevPositionCS)$> + <$transformModelToWorldDir(cam, obj, inNormal.xyz, varNormal)$> + varPosition = inPosition.xyz; +} diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slf b/libraries/render-utils/src/lightClusters_drawClusterContent.slf index 80013bc3cc..b3b81544a7 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slf @@ -33,7 +33,7 @@ void main(void) { vec2 texCoord = varTexCoord0.st; vec4 fragEyePos = unpackDeferredPositionFromZdb(texCoord); - vec4 fragWorldPos = getViewInverse() * fragEyePos; + vec4 fragWorldPos = getViewInverse(getStereoSideFromUV(texCoord.x)) * fragEyePos; // From frag world pos find the cluster vec4 clusterEyePos = frustumGrid_worldToEye(fragWorldPos); diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf index 0e3f8a5ea5..5e3714abee 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf @@ -30,7 +30,7 @@ void main(void) { vec2 texCoord = varTexCoord0.st; vec4 fragEyePos = unpackDeferredPositionFromZdb(texCoord); - vec4 fragWorldPos = getViewInverse() * fragEyePos; + vec4 fragWorldPos = getViewInverse(getStereoSideFromUV(texCoord.x)) * fragEyePos; // From frag world pos find the cluster vec4 clusterEyePos = frustumGrid_worldToEye(fragWorldPos); diff --git a/libraries/render-utils/src/local_lights_drawOutline.slf b/libraries/render-utils/src/local_lights_drawOutline.slf index a2b4cc1d10..f9f78aabb5 100644 --- a/libraries/render-utils/src/local_lights_drawOutline.slf +++ b/libraries/render-utils/src/local_lights_drawOutline.slf @@ -54,7 +54,7 @@ void main(void) { // Frag pos in world - mat4 invViewMat = getViewInverse(); + mat4 invViewMat = getViewInverse(frag.side); vec4 fragPos = invViewMat * fragPosition; <$fetchClusterInfo(fragPos)$>; diff --git a/libraries/render-utils/src/local_lights_shading.slf b/libraries/render-utils/src/local_lights_shading.slf index 538bdacc99..9339d6b7ca 100644 --- a/libraries/render-utils/src/local_lights_shading.slf +++ b/libraries/render-utils/src/local_lights_shading.slf @@ -32,8 +32,7 @@ void main(void) { // Grab the fragment data from the uv vec2 texCoord = _texCoord0.st; - DeferredFrameTransform deferredTransform = getDeferredFrameTransform(); - DeferredFragment frag = unpackDeferredFragment(deferredTransform, texCoord); + DeferredFragment frag = unpackDeferredFragment(texCoord); vec4 fragPosition = frag.position; if (frag.mode == FRAG_MODE_UNLIT) { @@ -41,7 +40,7 @@ void main(void) { } // Frag pos in world - mat4 invViewMat = getViewInverse(); + mat4 invViewMat = getViewInverse(frag.side); vec4 fragWorldPos = invViewMat * fragPosition; <$fetchClusterInfo(fragWorldPos)$>; diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index 4b40aa171c..8b3f1b2c23 100644 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -41,7 +41,6 @@ <@if not HIFI_USE_SHADOW@> <@if HIFI_USE_MTOON@> - <@include DefaultMaterials.slh@> <@include GlobalLight.slh@> <$declareEvalGlobalLightingAlphaBlendedMToon()$> @@ -97,6 +96,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; <@if not HIFI_USE_MTOON@> layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; <@endif@> + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) in vec4 _prevPositionCS; <@if HIFI_USE_NORMALMAP@> layout(location=RENDER_UTILS_ATTR_TANGENT_WS) in vec3 _tangentWS; <@endif@> @@ -152,10 +152,17 @@ void main(void) { <@if HIFI_USE_SHADOW@> _fragColor0 = vec4(1.0); - <@elif HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> + <@elif HIFI_USE_FORWARD@> _fragColor0 = vec4(albedo * isUnlitEnabled(), opacity); + <@elif HIFI_USE_TRANSLUCENT@> + packDeferredFragmentTranslucentUnlit( + _prevPositionCS, + evalFrontOrBackFaceNormal(normalize(_normalWS)), + opacity, + albedo * isUnlitEnabled()); <@else@> packDeferredFragmentUnlit( + _prevPositionCS, evalFrontOrBackFaceNormal(normalize(_normalWS)), opacity, albedo * isUnlitEnabled()); @@ -347,6 +354,7 @@ void main(void) { <@if not HIFI_USE_TRANSLUCENT@> <@if not HIFI_USE_LIGHTMAP@> packDeferredFragment( + _prevPositionCS, fragNormalWS, opacity, albedo, @@ -361,6 +369,7 @@ void main(void) { scattering); <@else@> packDeferredFragmentLightmap( + _prevPositionCS, fragNormalWS, evalOpaqueFinalAlpha(getMaterialOpacity(mat), opacity), albedo, @@ -390,7 +399,7 @@ void main(void) { vec4(0), vec4(0), opacity); } - _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + vec4 outColor = vec4(evalGlobalLightingAlphaBlended( cam._viewInverse, 1.0, occlusion, @@ -406,8 +415,9 @@ void main(void) { , surfaceWS, opacity, localLighting.rgb), opacity); + packDeferredFragmentTranslucent(_prevPositionCS, fragNormalWS, outColor.a, outColor.rgb, roughness); <@else@> - _fragColor0 = vec4(evalLightmappedColor( + vec4 outColor = vec4(evalLightmappedColor( cam._viewInverse, 1.0, DEFAULT_OCCLUSION, @@ -415,6 +425,7 @@ void main(void) { albedo, lightmap), opacity); + packDeferredFragmentLightmap(_prevPositionCS, fragNormalWS, outColor.a, outColor.rgb, roughness, metallic, outColor.rgb); <@endif@> <@endif@> <@endif@> diff --git a/libraries/render-utils/src/model.slv b/libraries/render-utils/src/model.slv index 848acfc331..dc4bcde7fe 100644 --- a/libraries/render-utils/src/model.slv +++ b/libraries/render-utils/src/model.slv @@ -51,6 +51,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; <@if not HIFI_USE_MTOON@> layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; <@endif@> + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) out vec4 _prevPositionCS; <@if HIFI_USE_NORMALMAP@> layout(location=RENDER_UTILS_ATTR_TANGENT_WS) out vec3 _tangentWS; <@endif@> @@ -76,7 +77,7 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <@if not HIFI_USE_SHADOW@> - <$transformModelToWorldAndEyeAndClipPos(cam, obj, positionMS, _positionWS, _positionES, gl_Position)$> + <$transformModelToWorldEyeClipPosAndPrevClipPos(cam, obj, positionMS, _positionWS, _positionES, gl_Position, _prevPositionCS)$> <$transformModelToWorldDir(cam, obj, normalMS, _normalWS)$> <@else@> <$transformModelToClipPos(cam, obj, positionMS, gl_Position)$> diff --git a/libraries/render-utils/src/parabola.slf b/libraries/render-utils/src/parabola.slf index f19f82ec59..cd97a79113 100644 --- a/libraries/render-utils/src/parabola.slf +++ b/libraries/render-utils/src/parabola.slf @@ -10,20 +10,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +layout(location=0) in vec4 _color; + <@if not HIFI_USE_FORWARD@> <@include DeferredBufferWrite.slh@> + layout(location=1) in vec4 _prevPositionCS; <@else@> layout(location=0) out vec4 _fragColor0; <@endif@> -layout(location=0) in vec4 _color; - void main(void) { <@if not HIFI_USE_FORWARD@> <@if not HIFI_USE_TRANSLUCENT@> - packDeferredFragmentUnlit(vec3(1.0, 0.0, 0.0), 1.0, _color.rgb); + packDeferredFragmentUnlit(_prevPositionCS, vec3(1.0, 0.0, 0.0), 1.0, _color.rgb); <@else@> - packDeferredFragmentTranslucent(vec3(1.0, 0.0, 0.0), _color.a, _color.rgb, DEFAULT_ROUGHNESS); + packDeferredFragmentTranslucent(_prevPositionCS, vec3(1.0, 0.0, 0.0), _color.a, _color.rgb, DEFAULT_ROUGHNESS); <@endif@> <@else@> _fragColor0 = _color; diff --git a/libraries/render-utils/src/parabola.slv b/libraries/render-utils/src/parabola.slv index 6032452d1d..731c14f122 100644 --- a/libraries/render-utils/src/parabola.slv +++ b/libraries/render-utils/src/parabola.slv @@ -27,6 +27,9 @@ LAYOUT_STD140(binding=0) uniform parabolaData { }; layout(location=0) out vec4 _color; +<@if not HIFI_USE_FORWARD@> + layout(location=1) out vec4 _prevPositionCS; +<@endif@> void main(void) { _color = _parabolaData.color; @@ -51,5 +54,9 @@ void main(void) { pos += 0.5 * _parabolaData.width * normal * (-1.0 + 2.0 * float(gl_VertexID % 2 == 0)); - <$transformModelToClipPos(cam, obj, pos, gl_Position)$> + <@if HIFI_USE_FORWARD@> + <$transformModelToClipPos(cam, obj, pos, gl_Position)$> + <@else@> + <$transformModelToClipPosAndPrevClipPos(cam, obj, pos, gl_Position, _prevPositionCS)$> + <@endif@> } diff --git a/libraries/render-utils/src/render-utils/ShaderConstants.h b/libraries/render-utils/src/render-utils/ShaderConstants.h index 19eb4dd249..5d00736a29 100644 --- a/libraries/render-utils/src/render-utils/ShaderConstants.h +++ b/libraries/render-utils/src/render-utils/ShaderConstants.h @@ -36,10 +36,13 @@ // don't conflict with GPU_ATTR_V2F_STEREO_SIDE in the GPU shader constants #define RENDER_UTILS_ATTR_DO_NOT_USE 8 +// Clip space +#define RENDER_UTILS_ATTR_PREV_POSITION_CS 9 + // Fade -#define RENDER_UTILS_ATTR_FADE1 9 -#define RENDER_UTILS_ATTR_FADE2 10 -#define RENDER_UTILS_ATTR_FADE3 11 +#define RENDER_UTILS_ATTR_FADE1 10 +#define RENDER_UTILS_ATTR_FADE2 11 +#define RENDER_UTILS_ATTR_FADE3 12 #define RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM 0 @@ -103,6 +106,7 @@ #define RENDER_UTILS_TEXTURE_TAA_VELOCITY 2 #define RENDER_UTILS_TEXTURE_TAA_DEPTH 3 #define RENDER_UTILS_TEXTURE_TAA_NEXT 4 +#define RENDER_UTILS_TEXTURE_TAA_INTENSITY 5 // Surface Geometry #define RENDER_UTILS_BUFFER_SG_PARAMS 1 @@ -189,6 +193,7 @@ enum Texture { TaaVelocity = RENDER_UTILS_TEXTURE_TAA_VELOCITY, TaaDepth = RENDER_UTILS_TEXTURE_TAA_DEPTH, TaaNext = RENDER_UTILS_TEXTURE_TAA_NEXT, + TaaIntensity = RENDER_UTILS_TEXTURE_TAA_INTENSITY, SsaoOcclusion = RENDER_UTILS_TEXTURE_SSAO_OCCLUSION, SsaoDepth = RENDER_UTILS_TEXTURE_SSAO_DEPTH, SsaoNormal = RENDER_UTILS_TEXTURE_SSAO_NORMAL, diff --git a/libraries/render-utils/src/render-utils/fxaa_blend.slp b/libraries/render-utils/src/render-utils/aa_blend.slp similarity index 100% rename from libraries/render-utils/src/render-utils/fxaa_blend.slp rename to libraries/render-utils/src/render-utils/aa_blend.slp diff --git a/libraries/render-utils/src/render-utils/grid.slp b/libraries/render-utils/src/render-utils/grid.slp index 5cf10ff674..e5119c55e4 100644 --- a/libraries/render-utils/src/render-utils/grid.slp +++ b/libraries/render-utils/src/render-utils/grid.slp @@ -1,2 +1 @@ -VERTEX standardTransformPNTC DEFINES translucent:f forward:f \ No newline at end of file diff --git a/libraries/render-utils/src/render-utils/velocityBuffer_cameraMotion.slp b/libraries/render-utils/src/render-utils/velocityBuffer_cameraMotion.slp deleted file mode 100644 index d4d8ec4b01..0000000000 --- a/libraries/render-utils/src/render-utils/velocityBuffer_cameraMotion.slp +++ /dev/null @@ -1 +0,0 @@ -VERTEX gpu::vertex::DrawViewportQuadTransformTexcoord diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index c5bed1ecab..7b7820194b 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -21,7 +21,9 @@ <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> +<@endif@> +<@if HIFI_USE_FORWARD@> layout(location=0) out vec4 _fragColor0; <@else@> <@include DeferredBufferWrite.slh@> @@ -35,6 +37,9 @@ <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; <@endif@> +<@if not HIFI_USE_FORWARD@> + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) in vec4 _prevPositionCS; +<@endif@> layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; #define _texCoord0 _texCoord01.xy @@ -51,27 +56,35 @@ void main() { } <@endif@> + vec3 normal = normalize(_normalWS); + <@if HIFI_USE_UNLIT@> - <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> - _fragColor0 = vec4(color.rgb * isUnlitEnabled(), color.a); + vec4 outColor = vec4(color.rgb * isUnlitEnabled(), color.a); + + <@if HIFI_USE_FORWARD@> + _fragColor0 = outColor; + <@elif HIFI_USE_TRANSLUCENT@> + packDeferredFragmentTranslucent(_prevPositionCS, normal, outColor.a, outColor.rgb, DEFAULT_ROUGHNESS); <@else@> packDeferredFragmentUnlit( - normalize(_normalWS), - color.a, - color.rgb); + _prevPositionCS, + normal, + outColor.a, + outColor.rgb); <@endif@> <@else@> <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> TransformCamera cam = getTransformCamera(); vec3 fragPosition = _positionES.xyz; + vec4 outColor; <@if HIFI_USE_TRANSLUCENT@> - _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + outColor = vec4(evalGlobalLightingAlphaBlended( cam._viewInverse, 1.0, DEFAULT_OCCLUSION, fragPosition, - normalize(_normalWS), + normal, color.rgb, DEFAULT_FRESNEL, DEFAULT_METALLIC, @@ -79,21 +92,28 @@ void main() { DEFAULT_ROUGHNESS, color.a), color.a); <@else@> - _fragColor0 = vec4(evalSkyboxGlobalColor( + outColor = vec4(evalSkyboxGlobalColor( cam._viewInverse, 1.0, DEFAULT_OCCLUSION, fragPosition, - normalize(_normalWS), + normal, color.rgb, DEFAULT_FRESNEL, DEFAULT_METALLIC, DEFAULT_ROUGHNESS), color.a); <@endif@> + + <@if HIFI_USE_FORWARD@> + _fragColor0 = outColor; + <@else@> + packDeferredFragmentTranslucent(_prevPositionCS, normal, outColor.a, outColor.rgb, DEFAULT_ROUGHNESS); + <@endif@> <@else@> packDeferredFragment( - normalize(_normalWS), + _prevPositionCS, + normal, color.a, color.rgb, DEFAULT_ROUGHNESS, diff --git a/libraries/render-utils/src/sdf_text3D.slh b/libraries/render-utils/src/sdf_text3D.slh index 76ace99182..cbcfd4826c 100644 --- a/libraries/render-utils/src/sdf_text3D.slh +++ b/libraries/render-utils/src/sdf_text3D.slh @@ -35,6 +35,7 @@ LAYOUT(binding=0) uniform textParamsBuffer { #define TAA_TEXTURE_LOD_BIAS -3.0 const float interiorCutoff = 0.5; +const float smoothStrength = 4.0; const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS); vec4 evalSDF(vec2 texCoord, vec4 glyphBounds) { @@ -73,18 +74,16 @@ vec4 evalSDF(vec2 texCoord, vec4 glyphBounds) { } vec4 evalSDFSuperSampled(vec2 texCoord, vec4 glyphBounds) { - vec2 dxTexCoord = dFdx(texCoord) * 0.5 * taaBias; - vec2 dyTexCoord = dFdy(texCoord) * 0.5 * taaBias; + vec4 color = evalSDF(texCoord, glyphBounds); - // Perform 4x supersampling for anisotropic filtering - vec4 color; - color = evalSDF(texCoord, glyphBounds); - color += evalSDF(texCoord + dxTexCoord, glyphBounds); - color += evalSDF(texCoord + dyTexCoord, glyphBounds); - color += evalSDF(texCoord + dxTexCoord + dyTexCoord, glyphBounds); - color *= 0.25; + // Rely on TAA for anti-aliasing but smooth transition when minification + // to help filtering + float uvFootprint = length(fwidth(texCoord) * smoothStrength); + float smoothStart = max(0.0, 0.5 - uvFootprint); + float smoothEnd = min(1.0, 0.5 + uvFootprint); + float alpha = max(smoothstep(smoothStart, smoothEnd, color.a), step(interiorCutoff, color.a)); - return vec4(color.rgb, step(interiorCutoff, color.a)); + return vec4(color.rgb, alpha); } <@endfunc@> diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv index 9ac3b871f9..e110b7bd9e 100644 --- a/libraries/render-utils/src/sdf_text3D.slv +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -22,6 +22,9 @@ <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; <@endif@> +<@if not HIFI_USE_FORWARD@> + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) out vec4 _prevPositionCS; +<@endif@> layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; layout(location=RENDER_UTILS_ATTR_FADE1) flat out vec4 _glyphBounds; // we're reusing the fade texcoord locations here @@ -41,10 +44,12 @@ void main() { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); -<@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> +<@if HIFI_USE_FORWARD@> <$transformModelToEyeAndClipPos(cam, obj, position, _positionES, gl_Position)$> +<@elif HIFI_USE_TRANSLUCENT@> + <$transformModelToEyeClipPosAndPrevClipPos(cam, obj, position, _positionES, gl_Position, _prevPositionCS)$> <@else@> - <$transformModelToClipPos(cam, obj, position, gl_Position)$> + <$transformModelToClipPosAndPrevClipPos(cam, obj, position, gl_Position, _prevPositionCS)$> <@endif@> const vec3 normal = vec3(0, 0, 1); diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index fabe85cb4f..610ac094e5 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -5,6 +5,7 @@ // // Created by Andrzej Kapolka on 9/15/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -21,26 +22,21 @@ <$declareStandardCameraTransform()$> <@else@> <@include LightingModel.slh@> + <@endif@> <@endif@> +<@if HIFI_USE_FORWARD@> layout(location=0) out vec4 _fragColor0; +<@else@> + <@include DeferredBufferWrite.slh@> <@endif@> <@if not HIFI_USE_UNLIT@> + <@include GlobalLight.slh@> <@if HIFI_USE_TRANSLUCENT@> - <@include GlobalLight.slh@> <$declareEvalGlobalLightingAlphaBlended()$> <@elif HIFI_USE_FORWARD@> - <@include GlobalLight.slh@> <$declareEvalSkyboxGlobalColor(_SCRIBE_NULL, HIFI_USE_FORWARD)$> - <@else@> - <@include DeferredBufferWrite.slh@> - <@endif@> -<@else@> - <@if not HIFI_USE_FORWARD@> - <@if not HIFI_USE_TRANSLUCENT@> - <@include DeferredBufferWrite.slh@> - <@endif@> <@endif@> <@endif@> @@ -49,11 +45,14 @@ <$declareFadeFragmentInstanced()$> <@endif@> -<@if not HIFI_USE_UNLIT@> - <@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@> +<@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@> + <@if not HIFI_USE_UNLIT@> layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; <@endif@> <@endif@> +<@if not HIFI_USE_FORWARD@> + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) in vec4 _prevPositionCS; +<@endif@> <@if HIFI_USE_FADE@> layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; <@endif@> @@ -90,12 +89,13 @@ void main(void) { <@if not HIFI_USE_UNLIT@> <@if HIFI_USE_TRANSLUCENT@> - _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + vec3 normal = evalFrontOrBackFaceNormal(normalize(_normalWS)); + vec4 color = vec4(evalGlobalLightingAlphaBlended( cam._viewInverse, 1.0, DEFAULT_OCCLUSION, fragPosition, - evalFrontOrBackFaceNormal(normalize(_normalWS)), + normal, texel.rgb, fresnel, metallic, @@ -106,6 +106,12 @@ void main(void) { , DEFAULT_ROUGHNESS, texel.a), texel.a); + + <@if HIFI_USE_FORWARD@> + _fragColor0 = color; + <@else@> + packDeferredFragmentTranslucent(_prevPositionCS, normal, color.a, color.rgb, DEFAULT_ROUGHNESS); + <@endif@> <@elif HIFI_USE_FORWARD@> _fragColor0 = vec4(evalSkyboxGlobalColor( cam._viewInverse, @@ -120,6 +126,7 @@ void main(void) { texel.a); <@else@> packDeferredFragment( + _prevPositionCS, evalFrontOrBackFaceNormal(normalize(_normalWS)), 1.0, texel.rgb, @@ -134,21 +141,25 @@ void main(void) { DEFAULT_SCATTERING); <@endif@> <@else@> - <@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@> - _fragColor0 = isUnlitEnabled() * vec4(texel.rgb + vec3 color = isUnlitEnabled() * (texel.rgb <@if HIFI_USE_FADE@> + fadeEmissive <@endif@> - , texel.a); + ); + <@if HIFI_USE_FORWARD@> + _fragColor0 = vec4(color, texel.a); + <@elif HIFI_USE_TRANSLUCENT@> + packDeferredFragmentTranslucentUnlit( + _prevPositionCS, + evalFrontOrBackFaceNormal(normalize(_normalWS)), + texel.a, + color); <@else@> packDeferredFragmentUnlit( + _prevPositionCS, evalFrontOrBackFaceNormal(normalize(_normalWS)), 1.0, - texel.rgb - <@if HIFI_USE_FADE@> - + fadeEmissive - <@endif@> - ); + color); <@endif@> <@endif@> } diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index e47af5d23f..42aebf13a1 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -5,6 +5,7 @@ // // Created by Andrzej Kapolka on 9/15/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -24,11 +25,14 @@ layout(location=RENDER_UTILS_ATTR_POSITION_WS) out vec4 _positionWS; <@endif@> -<@if not HIFI_USE_UNLIT@> - <@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@> +<@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@> + <@if not HIFI_USE_UNLIT@> layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; <@endif@> <@endif@> +<@if not HIFI_USE_FORWARD@> + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) out vec4 _prevPositionCS; +<@endif@> layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; @@ -39,12 +43,13 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); -<@if not HIFI_USE_UNLIT@> - <@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@> - <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> - <@else@> - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> +<@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@> + <@if not HIFI_USE_UNLIT@> + <$transformModelToEyeWorldAlignedPos(cam, obj, inPosition, _positionES)$> <@endif@> +<@endif@> +<@if not HIFI_USE_FORWARD@> + <$transformModelToClipPosAndPrevClipPos(cam, obj, inPosition, gl_Position, _prevPositionCS)$> <@else@> <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> <@endif@> diff --git a/libraries/render-utils/src/simple_procedural.slf b/libraries/render-utils/src/simple_procedural.slf index cc8edbb415..d0cf9a53b2 100644 --- a/libraries/render-utils/src/simple_procedural.slf +++ b/libraries/render-utils/src/simple_procedural.slf @@ -10,15 +10,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@if not HIFI_USE_TRANSLUCENT@> - <@include DeferredBufferWrite.slh@> -<@else@> +<@include DeferredBufferWrite.slh@> + +<@if HIFI_USE_TRANSLUCENT@> <@include DefaultMaterials.slh@> <@include GlobalLight.slh@> <$declareEvalGlobalLightingAlphaBlended()$> - - layout(location=0) out vec4 _fragColor0; <@endif@> <@include gpu/Transform.slh@> @@ -29,6 +27,7 @@ layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _positionMS; layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) in vec4 _prevPositionCS; layout(location=RENDER_UTILS_ATTR_NORMAL_MS) in vec3 _normalMS; layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; @@ -147,6 +146,7 @@ void main(void) { <@if not HIFI_USE_TRANSLUCENT@> if (emissiveAmount > 0.0) { packDeferredFragmentLightmap( + _prevPositionCS, normal, 1.0, diffuse, @@ -155,6 +155,7 @@ void main(void) { emissive); } else { packDeferredFragment( + _prevPositionCS, normal, 1.0, diffuse, @@ -165,10 +166,11 @@ void main(void) { scattering); } <@else@> + vec4 color; if (emissiveAmount > 0.0) { - _fragColor0 = vec4(diffuse, alpha); + color = vec4(diffuse, alpha); } else { - _fragColor0 = vec4(evalGlobalLightingAlphaBlended( + color = vec4(evalGlobalLightingAlphaBlended( cam._viewInverse, 1.0, occlusion, @@ -181,5 +183,6 @@ void main(void) { roughness, alpha), alpha); } + packDeferredFragmentTranslucent(_prevPositionCS, normal, color.a, color.rgb, roughness); <@endif@> } diff --git a/libraries/render-utils/src/simple_procedural.slv b/libraries/render-utils/src/simple_procedural.slv index 70bce451d3..7c7544fe3d 100644 --- a/libraries/render-utils/src/simple_procedural.slv +++ b/libraries/render-utils/src/simple_procedural.slv @@ -29,6 +29,7 @@ layout(location=RENDER_UTILS_ATTR_POSITION_MS) out vec4 _positionMS; layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; +layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) out vec4 _prevPositionCS; layout(location=RENDER_UTILS_ATTR_NORMAL_MS) out vec3 _normalMS; layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; @@ -84,6 +85,6 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, positionMS, _positionES, gl_Position)$> + <$transformModelToEyeClipPosAndPrevClipPos(cam, obj, positionMS, _positionES, gl_Position, _prevPositionCS)$> <$transformModelToWorldDir(cam, obj, normalMS, _normalWS)$> } \ No newline at end of file diff --git a/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf b/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf index dd9b98b5e5..47bb66b5e3 100644 --- a/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf +++ b/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf @@ -108,7 +108,6 @@ void main(void) { // The position of the pixel fragment in Eye space then in world space vec3 eyePos = evalEyePositionFromZeye(stereoSide.x, Zeye, texcoordPos); - // vec3 worldPos = (frameTransform._viewInverse * vec4(eyePos, 1.0)).xyz; /* if (texcoordPos.y > 0.5) { outFragColor = vec4(fract(10.0 * worldPos.xyz), 1.0); @@ -138,9 +137,10 @@ void main(void) { // Eval px, py, pz world positions of the basis centered on the world pos of the fragment float axeLength = nearPlaneScale; - vec3 ax = (frameTransform._view[0].xyz * axeLength); - vec3 ay = (frameTransform._view[1].xyz * axeLength); - vec3 az = (frameTransform._view[2].xyz * axeLength); + mat4 view = getView(stereoSide.x); + vec3 ax = (view[0].xyz * axeLength); + vec3 ay = (view[1].xyz * axeLength); + vec3 az = (view[2].xyz * axeLength); vec4 px = vec4(eyePos + ax, 0.0); vec4 py = vec4(eyePos + ay, 0.0); @@ -184,7 +184,7 @@ void main(void) { vec2 nclipPos = (texcoordPos - 0.5) * 2.0; - //vec4 clipPos = frameTransform._projection[stereoSide.x] * vec4(eyePos, 1.0); + //vec4 clipPos = getProjection(stereoSide.x) * vec4(eyePos, 1.0); vec4 clipPos = getProjectionMono() * vec4(eyePos, 1.0); nclipPos = clipPos.xy / clipPos.w; diff --git a/libraries/render-utils/src/taa.slf b/libraries/render-utils/src/taa.slf index 25320179f5..8a4b4aff65 100644 --- a/libraries/render-utils/src/taa.slf +++ b/libraries/render-utils/src/taa.slf @@ -1,12 +1,11 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// taa.frag -// fragment shader +// <$_SCRIBE_FILENAME$> +// Generated on <$_SCRIBE_DATE$> // // Created by Sam Gateau on 8/14/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -28,18 +27,24 @@ void main() { return; } + vec4 sourceColor; + vec4 historyColor; + vec2 fragVel = taa_fetchVelocityMapBest(fragUV).xy; + bool needsTAA = taa_fetchSourceAndHistory(fragUV, fragVel, sourceColor, historyColor); + vec4 nextColor = sourceColor; + if (needsTAA) { + // clamp history to neighbourhood of current sample + historyColor.rgb = mix(historyColor.rgb, taa_evalConstrainColor(sourceColor.rgb, fragUV, fragVel, historyColor.rgb).rgb, float(taa_constrainColor())); - vec3 sourceColor; - vec3 historyColor; - vec2 prevFragUV = taa_fetchSourceAndHistory(fragUV, fragVel, sourceColor, historyColor); + if (taa_feedbackColor()) { + nextColor = taa_evalFeedbackColor(sourceColor, historyColor, params.blend); + } else { + nextColor = mix(historyColor, sourceColor, params.blend); + } - vec3 nextColor = sourceColor; + nextColor.rgb = taa_resolveColor(mix(sourceColor.rgb, nextColor.rgb, nextColor.a)); + } - // clamp history to neighbourhood of current sample - historyColor = mix(historyColor, taa_evalConstrainColor(sourceColor, fragUV, fragVel, historyColor), float(taa_constrainColor())); - - nextColor = mix(mix(historyColor, sourceColor, params.blend), taa_evalFeedbackColor(sourceColor, historyColor, params.blend), float(taa_feedbackColor())); - - outFragColor = vec4(taa_resolveColor(nextColor), 1.0); + outFragColor = nextColor; } diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh index ed9162516e..a082ec2531 100644 --- a/libraries/render-utils/src/taa.slh +++ b/libraries/render-utils/src/taa.slh @@ -5,6 +5,7 @@ // // Created by Sam Gateau on 8/17/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -21,6 +22,7 @@ LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_SOURCE) uniform sampler2D sourceMap; LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_VELOCITY) uniform sampler2D velocityMap; LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_DEPTH) uniform sampler2D depthMap; LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_NEXT) uniform sampler2D nextMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_INTENSITY) uniform sampler2D intensityMap; struct TAAParams { @@ -51,10 +53,18 @@ bool taa_showClosestFragment() { return GET_BIT(params.flags.x, 3); } +bool taa_bicubicHistoryFetch() { + return GET_BIT(params.flags.y, 0); +} + bool taa_constrainColor() { return GET_BIT(params.flags.y, 1); } +bool taa_sharpenOutput() { + return GET_BIT(params.flags.y, 2); +} + bool taa_feedbackColor() { return GET_BIT(params.flags.y, 4); } @@ -76,12 +86,23 @@ vec2 taa_getRegionFXAA() { } #define USE_YCOCG 1 -vec4 taa_fetchColor(sampler2D map, vec2 uv) { - vec4 c = texture(map, uv); +vec2 taa_getImageSize() { + vec2 imageSize = getWidthHeight(0); + imageSize.x *= 1.0 + float(isStereo()); + return imageSize; +} + +vec2 taa_getTexelSize() { + vec2 texelSize = getInvWidthHeight(); + texelSize.x *= 1.0 - 0.5 * float(isStereo()); + return texelSize; +} + +vec4 taa_transformColor(vec4 c) { // Apply rapid pseudo tonemapping as TAA is applied to a tonemapped image, using luminance as weight, as proposed in // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf - float lum = dot(vec3(0.3,0.5,0.2),c.rgb); - c.rgb = c.rgb / (1.0+lum); + float lum = dot(vec3(0.3, 0.5, 0.2), c.rgb); + c.rgb = c.rgb / (1.0 + lum); #if USE_YCOCG return vec4(color_LinearToYCoCg(c.rgb), c.a); #else @@ -89,23 +110,76 @@ vec4 taa_fetchColor(sampler2D map, vec2 uv) { #endif } +vec4 taa_fetchColor(sampler2D map, vec2 uv) { + vec4 c = texture(map, uv); + return taa_transformColor(c); +} + vec3 taa_resolveColor(vec3 color) { #if USE_YCOCG color = max(vec3(0), color_YCoCgToUnclampedLinear(color)); #endif // Apply rapid inverse tonemapping, using luminance as weight, as proposed in // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf - float lum = dot(vec3(0.3,0.5,0.2),color.rgb); - color = color / (1.0-lum); + float lum = dot(vec3(0.3, 0.5, 0.2),color.rgb); + color = color / (1.0 - lum); return color; } vec4 taa_fetchSourceMap(vec2 uv) { - return taa_fetchColor(sourceMap, uv); + vec4 source = taa_fetchColor(sourceMap, uv); + // Store AA intensity in alpha + source.a = texture(intensityMap, uv).r; + return source; } vec4 taa_fetchHistoryMap(vec2 uv) { - return taa_fetchColor(historyMap, uv); + vec4 result; + + if (taa_bicubicHistoryFetch()) { + // Perform a Catmull-Rom interpolation for a (possibly) sharper result. + // Optimized 9-tap fetch based on http://vec3.ca/bicubic-filtering-in-fewer-taps/ + vec2 samplePixelPos = uv * taa_getImageSize() - 0.5; + vec2 samplePixelIntPos = floor(samplePixelPos); + + vec2 samplePixelFracPos = samplePixelPos - samplePixelIntPos; + vec2 samplePixelFracPos2 = samplePixelFracPos * samplePixelFracPos; + + // Catmull-Rom interpolation weights + vec2 w0 = samplePixelFracPos * (samplePixelFracPos * (1.0 - 0.5*samplePixelFracPos) - 0.5); + vec2 w1 = 1.0 + samplePixelFracPos2 * (1.5*samplePixelFracPos - 2.5); + vec2 w2 = samplePixelFracPos * (samplePixelFracPos * (2.0 - 1.5*samplePixelFracPos) + 0.5); + vec2 w3 = samplePixelFracPos2 * (0.5 * samplePixelFracPos - 0.5); + + vec2 w12 = w1 + w2; + vec2 offset12 = w2 / (w1 + w2); + + vec2 sampleUV0 = samplePixelIntPos - vec2(1.0); + vec2 sampleUV3 = samplePixelIntPos + vec2(2.0); + vec2 sampleUV12 = samplePixelIntPos + offset12; + + vec2 texelSize = taa_getTexelSize(); + sampleUV0 = (sampleUV0 + 0.5) * texelSize; + sampleUV3 = (sampleUV3 + 0.5) * texelSize; + sampleUV12 = (sampleUV12 + 0.5) * texelSize; + + result = texture(historyMap, vec2(sampleUV0.x, sampleUV0.y)) * w0.x * w0.y; + result += texture(historyMap, vec2(sampleUV12.x, sampleUV0.y)) * w12.x * w0.y; + result += texture(historyMap, vec2(sampleUV3.x, sampleUV0.y)) * w3.x * w0.y; + + result += texture(historyMap, vec2(sampleUV0.x, sampleUV12.y)) * w0.x * w12.y; + result += texture(historyMap, vec2(sampleUV12.x, sampleUV12.y)) * w12.x * w12.y; + result += texture(historyMap, vec2(sampleUV3.x, sampleUV12.y)) * w3.x * w12.y; + + result += texture(historyMap, vec2(sampleUV0.x, sampleUV3.y)) * w0.x * w3.y; + result += texture(historyMap, vec2(sampleUV12.x, sampleUV3.y)) * w12.x * w3.y; + result += texture(historyMap, vec2(sampleUV3.x, sampleUV3.y)) * w3.x * w3.y; + + result.a = clamp(result.a, 0.0, 1.0); + } else { + result = texture(historyMap, uv); + } + return taa_transformColor(result); } vec4 taa_fetchNextMap(vec2 uv) { @@ -123,35 +197,24 @@ float taa_fetchDepth(vec2 uv) { #define ZCMP_GT(a, b) float(a > b) -vec2 taa_getImageSize() { - vec2 imageSize = getWidthHeight(0); - imageSize.x *= 1.0 + float(isStereo()); - return imageSize; -} - -vec2 taa_getTexelSize() { - vec2 texelSize = getInvWidthHeight(); - texelSize.x *= 1.0 - 0.5 * float(isStereo()); - return texelSize; -} - vec3 taa_findClosestFragment3x3(vec2 uv) { - vec2 dd = abs(taa_getTexelSize()); + vec2 dd = taa_getTexelSize(); vec2 du = vec2(dd.x, 0.0); vec2 dv = vec2(0.0, dd.y); + vec2 dm = vec2(-dd.x, dd.y); - vec3 dtl = vec3(-1, -1, taa_fetchDepth(uv - dv - du)); + vec3 dtl = vec3(-1, -1, taa_fetchDepth(uv - dd)); vec3 dtc = vec3( 0, -1, taa_fetchDepth(uv - dv)); - vec3 dtr = vec3( 1, -1, taa_fetchDepth(uv - dv + du)); + vec3 dtr = vec3( 1, -1, taa_fetchDepth(uv - dm)); vec3 dml = vec3(-1, 0, taa_fetchDepth(uv - du)); vec3 dmc = vec3( 0, 0, taa_fetchDepth(uv)); vec3 dmr = vec3( 1, 0, taa_fetchDepth(uv + du)); - vec3 dbl = vec3(-1, 1, taa_fetchDepth(uv + dv - du)); + vec3 dbl = vec3(-1, 1, taa_fetchDepth(uv + dm)); vec3 dbc = vec3( 0, 1, taa_fetchDepth(uv + dv)); - vec3 dbr = vec3( 1, 1, taa_fetchDepth(uv + dv + du)); + vec3 dbr = vec3( 1, 1, taa_fetchDepth(uv + dd)); vec3 dmin = dtl; dmin = mix(dmin, dtc, ZCMP_GT(dmin.z, dtc.z)); @@ -169,21 +232,22 @@ vec3 taa_findClosestFragment3x3(vec2 uv) } vec2 taa_fetchVelocityMapBest(vec2 uv) { - vec2 dd = abs(taa_getTexelSize()); + vec2 dd = taa_getTexelSize(); vec2 du = vec2(dd.x, 0.0); vec2 dv = vec2(0.0, dd.y); + vec2 dm = vec2(dd.x, -dd.y); - vec2 dtl = taa_fetchVelocityMap(uv - dv - du); + vec2 dtl = taa_fetchVelocityMap(uv - dd); vec2 dtc = taa_fetchVelocityMap(uv - dv); - vec2 dtr = taa_fetchVelocityMap(uv - dv + du); + vec2 dtr = taa_fetchVelocityMap(uv - dm); vec2 dml = taa_fetchVelocityMap(uv - du); vec2 dmc = taa_fetchVelocityMap(uv); vec2 dmr = taa_fetchVelocityMap(uv + du); - vec2 dbl = taa_fetchVelocityMap(uv + dv - du); + vec2 dbl = taa_fetchVelocityMap(uv + dm); vec2 dbc = taa_fetchVelocityMap(uv + dv); - vec2 dbr = taa_fetchVelocityMap(uv + dv + du); + vec2 dbr = taa_fetchVelocityMap(uv + dd); vec3 best = vec3(dtl, dot(dtl, dtl)); @@ -223,8 +287,8 @@ vec2 taa_fromFragUVToEyeUVAndSide(vec2 fragUV, out int stereoSide) { vec2 taa_fromEyeUVToFragUV(vec2 eyeUV, int stereoSide) { vec2 fragUV = eyeUV; float check = float(isStereo()); + fragUV.x += check * float(stereoSide); fragUV.x *= 1.0 - 0.5 * check; - fragUV.x += check * float(stereoSide) * 0.5; return fragUV; } @@ -235,14 +299,22 @@ vec2 taa_computePrevFragAndEyeUV(vec2 fragUV, vec2 fragVelocity, out vec2 prevEy return taa_fromEyeUVToFragUV(prevEyeUV, stereoSide); } -vec2 taa_fetchSourceAndHistory(vec2 fragUV, vec2 fragVelocity, out vec3 sourceColor, out vec3 historyColor) { - vec2 prevEyeUV; - vec2 prevFragUV = taa_computePrevFragAndEyeUV(fragUV, fragVelocity, prevEyeUV); - sourceColor = taa_fetchSourceMap(fragUV).xyz; +bool taa_fetchSourceAndHistory(vec2 fragUV, vec2 fragVelocity, out vec4 sourceColor, out vec4 historyColor) { + sourceColor.rgb = texture(sourceMap, fragUV).rgb; + // Store AA intensity in alpha + sourceColor.a = texture(intensityMap, fragUV).r; - historyColor = mix(sourceColor, taa_fetchHistoryMap(prevFragUV).xyz, float(!(any(lessThan(prevEyeUV, vec2(0.0))) || any(greaterThan(prevEyeUV, vec2(1.0)))))); - - return prevFragUV; + // If velocity is 0 then don't fetch history, just return the source. This means there is no jitter on this pixel + if (any(notEqual(fragVelocity, vec2(0.0)))) { + vec2 prevEyeUV; + vec2 prevFragUV = taa_computePrevFragAndEyeUV(fragUV, fragVelocity, prevEyeUV); + sourceColor = taa_transformColor(sourceColor); + historyColor = mix(sourceColor, taa_fetchHistoryMap(prevFragUV), float(!(any(lessThan(prevEyeUV, vec2(0.0))) || any(greaterThan(prevEyeUV, vec2(1.0)))))); + return true; + } else { + historyColor = sourceColor; + return false; + } } float Luminance(vec3 rgb) { @@ -253,12 +325,13 @@ float Luminance(vec3 rgb) { mat3 taa_evalNeighbourColorVariance(vec3 sourceColor, vec2 fragUV, vec2 fragVelocity) { vec2 texelSize = taa_getTexelSize(); - - vec2 du = vec2(texelSize.x, 0.0); - vec2 dv = vec2(0.0, texelSize.y); + vec2 dd = texelSize; + vec2 du = vec2(dd.x, 0.0); + vec2 dv = vec2(0.0, dd.y); + vec2 dm = vec2(dd.x, -dd.y); - vec3 sampleColor = taa_fetchSourceMap(fragUV - dv - du).rgb; + vec3 sampleColor = taa_fetchSourceMap(fragUV - dd).rgb; vec3 sumSamples = sampleColor; vec3 sumSamples2 = sampleColor * sampleColor; @@ -266,7 +339,7 @@ mat3 taa_evalNeighbourColorVariance(vec3 sourceColor, vec2 fragUV, vec2 fragVelo sumSamples += sampleColor; sumSamples2 += sampleColor * sampleColor; - sampleColor = taa_fetchSourceMap(fragUV - dv + du).rgb; + sampleColor = taa_fetchSourceMap(fragUV - dm).rgb; sumSamples += sampleColor; sumSamples2 += sampleColor * sampleColor; @@ -282,27 +355,27 @@ mat3 taa_evalNeighbourColorVariance(vec3 sourceColor, vec2 fragUV, vec2 fragVelo sumSamples += sampleColor; sumSamples2 += sampleColor * sampleColor; - sampleColor = taa_fetchSourceMap(fragUV + dv - du).rgb; + sampleColor = taa_fetchSourceMap(fragUV + dm).rgb; sumSamples += sampleColor; sumSamples2 += sampleColor * sampleColor; sampleColor = taa_fetchSourceMap(fragUV + dv).rgb; sumSamples += sampleColor; sumSamples2 += sampleColor * sampleColor; - - sampleColor = taa_fetchSourceMap(fragUV + dv + du).rgb; + + sampleColor = taa_fetchSourceMap(fragUV + dd).rgb; sumSamples += sampleColor; sumSamples2 += sampleColor * sampleColor; - + vec3 mu = sumSamples / vec3(9.0); vec3 sigma = sqrt(max(sumSamples2 / vec3(9.0) - mu * mu, vec3(0.0))); - - float gamma = params.covarianceGamma; - vec3 cmin = mu - gamma * sigma; - vec3 cmax = mu + gamma * sigma; - return mat3(cmin, cmax, mu); + sigma *= params.covarianceGamma; + vec3 cmin = mu - sigma; + vec3 cmax = mu + sigma; + + return mat3(cmin, cmax, mu); } mat3 taa_evalNeighbourColorRegion(vec3 sourceColor, vec2 fragUV, vec2 fragVelocity, float fragZe) { @@ -310,73 +383,73 @@ mat3 taa_evalNeighbourColorRegion(vec3 sourceColor, vec2 fragUV, vec2 fragVeloci vec2 texelSize = taa_getTexelSize(); vec3 cmin, cmax, cavg; - #if MINMAX_3X3_ROUNDED - vec2 du = vec2(texelSize.x, 0.0); - vec2 dv = vec2(0.0, texelSize.y); +#if MINMAX_3X3_ROUNDED + vec2 du = vec2(texelSize.x, 0.0); + vec2 dv = vec2(0.0, texelSize.y); - vec3 ctl = taa_fetchSourceMap(fragUV - dv - du).rgb; - vec3 ctc = taa_fetchSourceMap(fragUV - dv).rgb; - vec3 ctr = taa_fetchSourceMap(fragUV - dv + du).rgb; - vec3 cml = taa_fetchSourceMap(fragUV - du).rgb; - vec3 cmc = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sample isn't it ? - vec3 cmr = taa_fetchSourceMap(fragUV + du).rgb; - vec3 cbl = taa_fetchSourceMap(fragUV + dv - du).rgb; - vec3 cbc = taa_fetchSourceMap(fragUV + dv).rgb; - vec3 cbr = taa_fetchSourceMap(fragUV + dv + du).rgb; + vec3 ctl = taa_fetchSourceMap(fragUV - dv - du).rgb; + vec3 ctc = taa_fetchSourceMap(fragUV - dv).rgb; + vec3 ctr = taa_fetchSourceMap(fragUV - dv + du).rgb; + vec3 cml = taa_fetchSourceMap(fragUV - du).rgb; + vec3 cmc = sourceColor; //taa_fetchSourceMap(fragUV).rgb; // could resuse the same osurce sample isn't it ? + vec3 cmr = taa_fetchSourceMap(fragUV + du).rgb; + vec3 cbl = taa_fetchSourceMap(fragUV + dv - du).rgb; + vec3 cbc = taa_fetchSourceMap(fragUV + dv).rgb; + vec3 cbr = taa_fetchSourceMap(fragUV + dv + du).rgb; - cmin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr)))))))); - cmax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr)))))))); + cmin = min(ctl, min(ctc, min(ctr, min(cml, min(cmc, min(cmr, min(cbl, min(cbc, cbr)))))))); + cmax = max(ctl, max(ctc, max(ctr, max(cml, max(cmc, max(cmr, max(cbl, max(cbc, cbr)))))))); - #if MINMAX_3X3_ROUNDED || USE_YCOCG || USE_CLIPPING - cavg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0; - #elif - cavg = (cmin + cmax ) * 0.5; - #endif - - #if MINMAX_3X3_ROUNDED - vec3 cmin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc)))); - vec3 cmax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc)))); - vec3 cavg5 = (ctc + cml + cmc + cmr + cbc) / 5.0; - cmin = 0.5 * (cmin + cmin5); - cmax = 0.5 * (cmax + cmax5); - cavg = 0.5 * (cavg + cavg5); - #endif - #else - const float _SubpixelThreshold = 0.5; - const float _GatherBase = 0.5; - const float _GatherSubpixelMotion = 0.1666; - - vec2 texel_vel = fragVelocity * imageSize; - float texel_vel_mag = length(texel_vel) * -fragZe; - float k_subpixel_motion = clamp(_SubpixelThreshold / (0.0001 + texel_vel_mag), 0.0, 1.0); - float k_min_max_support = _GatherBase + _GatherSubpixelMotion * k_subpixel_motion; - - vec2 ss_offset01 = k_min_max_support * vec2(-texelSize.x, texelSize.y); - vec2 ss_offset11 = k_min_max_support * vec2(texelSize.x, texelSize.y); - vec3 c00 = taa_fetchSourceMap(fragUV - ss_offset11).rgb; - vec3 c10 = taa_fetchSourceMap(fragUV - ss_offset01).rgb; - vec3 c01 = taa_fetchSourceMap(fragUV + ss_offset01).rgb; - vec3 c11 = taa_fetchSourceMap(fragUV + ss_offset11).rgb; - - cmin = min(c00, min(c10, min(c01, c11))); - cmax = max(c00, max(c10, max(c01, c11))); + #if MINMAX_3X3_ROUNDED || USE_YCOCG || USE_CLIPPING + cavg = (ctl + ctc + ctr + cml + cmc + cmr + cbl + cbc + cbr) / 9.0; + #elif cavg = (cmin + cmax ) * 0.5; - - #if USE_YCOCG || USE_CLIPPING - cavg = (c00 + c10 + c01 + c11) / 4.0; - #elif - cavg = (cmin + cmax ) * 0.5; - #endif #endif - // shrink chroma min-max - #if USE_YCOCG - vec2 chroma_extent = vec2(0.25 * 0.5 * (cmax.r - cmin.r)); - vec2 chroma_center = sourceColor.gb; - cmin.yz = chroma_center - chroma_extent; - cmax.yz = chroma_center + chroma_extent; - cavg.yz = chroma_center; + #if MINMAX_3X3_ROUNDED + vec3 cmin5 = min(ctc, min(cml, min(cmc, min(cmr, cbc)))); + vec3 cmax5 = max(ctc, max(cml, max(cmc, max(cmr, cbc)))); + vec3 cavg5 = (ctc + cml + cmc + cmr + cbc) / 5.0; + cmin = 0.5 * (cmin + cmin5); + cmax = 0.5 * (cmax + cmax5); + cavg = 0.5 * (cavg + cavg5); #endif +#else + const float _SubpixelThreshold = 0.5; + const float _GatherBase = 0.5; + const float _GatherSubpixelMotion = 0.1666; + + vec2 texel_vel = fragVelocity * imageSize; + float texel_vel_mag = length(texel_vel) * -fragZe; + float k_subpixel_motion = clamp(_SubpixelThreshold / (0.0001 + texel_vel_mag), 0.0, 1.0); + float k_min_max_support = _GatherBase + _GatherSubpixelMotion * k_subpixel_motion; + + vec2 ss_offset01 = k_min_max_support * vec2(-texelSize.x, texelSize.y); + vec2 ss_offset11 = k_min_max_support * vec2(texelSize.x, texelSize.y); + vec3 c00 = taa_fetchSourceMap(fragUV - ss_offset11).rgb; + vec3 c10 = taa_fetchSourceMap(fragUV - ss_offset01).rgb; + vec3 c01 = taa_fetchSourceMap(fragUV + ss_offset01).rgb; + vec3 c11 = taa_fetchSourceMap(fragUV + ss_offset11).rgb; + + cmin = min(c00, min(c10, min(c01, c11))); + cmax = max(c00, max(c10, max(c01, c11))); + cavg = (cmin + cmax ) * 0.5; + + #if USE_YCOCG || USE_CLIPPING + cavg = (c00 + c10 + c01 + c11) / 4.0; + #elif + cavg = (cmin + cmax ) * 0.5; + #endif +#endif + + // shrink chroma min-max +#if USE_YCOCG + vec2 chroma_extent = vec2(0.25 * 0.5 * (cmax.r - cmin.r)); + vec2 chroma_center = sourceColor.gb; + cmin.yz = chroma_center - chroma_extent; + cmax.yz = chroma_center + chroma_extent; + cavg.yz = chroma_center; +#endif return mat3(cmin, cmax, cavg); } @@ -412,24 +485,23 @@ vec3 taa_evalConstrainColor(vec3 sourceColor, vec2 sourceUV, vec2 sourceVel, vec return taa_clampColor(colorMinMaxAvg[0], colorMinMaxAvg[1], sourceColor, candidateColor); } -vec3 taa_evalFeedbackColor(vec3 sourceColor, vec3 historyColor, float blendFactor) { +vec4 taa_evalFeedbackColor(vec4 sourceColor, vec4 historyColor, float blendFactor) { const float _FeedbackMin = 0.1; const float _FeedbackMax = 0.9; // feedback weight from unbiased luminance diff (t.lottes) - #if USE_YCOCG - float lum0 = sourceColor.r; - float lum1 = historyColor.r; - #else - float lum0 = Luminance(sourceColor.rgb); - float lum1 = Luminance(historyColor.rgb); - #endif +#if USE_YCOCG + float lum0 = sourceColor.r; + float lum1 = historyColor.r; +#else + float lum0 = Luminance(sourceColor.rgb); + float lum1 = Luminance(historyColor.rgb); +#endif float unbiased_diff = abs(lum0 - lum1) / max(lum0, max(lum1, 0.2)); float unbiased_weight = 1.0 - unbiased_diff; float unbiased_weight_sqr = unbiased_weight * unbiased_weight; float k_feedback = mix(_FeedbackMin, _FeedbackMax, unbiased_weight_sqr); - - vec3 nextColor = mix(historyColor, sourceColor, k_feedback * blendFactor).xyz; + vec4 nextColor = mix(historyColor, sourceColor, k_feedback * blendFactor); return nextColor; } @@ -447,7 +519,7 @@ vec3 taa_getVelocityColorAboveThreshold(float velocityPixLength) { vec3 taa_evalFXAA(vec2 fragUV) { - // vec2 texelSize = getInvWidthHeight(); + // vec2 texelSize = getInvWidthHeight(); vec2 texelSize = taa_getTexelSize(); // filter width limit for dependent "two-tap" texture samples diff --git a/libraries/render-utils/src/taa_blend.slf b/libraries/render-utils/src/taa_blend.slf index 0999b2482f..0779109cd9 100644 --- a/libraries/render-utils/src/taa_blend.slf +++ b/libraries/render-utils/src/taa_blend.slf @@ -21,7 +21,6 @@ void main(void) { vec3 nextColor = texture(nextMap, varTexCoord0).xyz; outFragColor = vec4(nextColor, 1.0); - // Pixel being shaded vec3 sourceColor = texture(sourceMap, varTexCoord0).xyz; diff --git a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf deleted file mode 100644 index 0ec63a7b1d..0000000000 --- a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf +++ /dev/null @@ -1,43 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// velocityBuffer_cameraMotion.frag -// -// Created by Sam Gateau on 6/3/16. -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include DeferredTransform.slh@> -<$declareDeferredFrameTransform()$> - -layout(location=0) in vec2 varTexCoord0; -layout(location=0) out vec4 outFragColor; - -LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_DEPTH) uniform sampler2D depthMap; - - -void main(void) { - // Pixel being shaded - ivec2 pixelPos; - vec2 texcoordPos; - ivec4 stereoSide; - ivec2 framePixelPos = getPixelPosTexcoordPosAndSide(gl_FragCoord.xy, pixelPos, texcoordPos, stereoSide); - - float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; - - // The position of the pixel fragment in Eye space then in world space - vec3 eyePos = evalUnjitteredEyePositionFromZdb(stereoSide.x, Zdb, texcoordPos); - vec3 worldPos = (getViewInverse() * vec4(eyePos, 1.0)).xyz; - - vec3 prevEyePos = (getPreviousView() * vec4(worldPos, 1.0)).xyz; - vec4 prevClipPos = (getUnjitteredProjection(stereoSide.x) * vec4(prevEyePos, 1.0)); - vec2 prevUV = 0.5 * (prevClipPos.xy / prevClipPos.w) + vec2(0.5); - - //vec2 imageSize = getWidthHeight(0); - vec2 imageSize = vec2(1.0, 1.0); - outFragColor = vec4( ((texcoordPos - prevUV) * imageSize), 0.0, 0.0); -} diff --git a/libraries/render-utils/src/web_browser.slf b/libraries/render-utils/src/web_browser.slf index f746916d3d..2dc71f12dd 100644 --- a/libraries/render-utils/src/web_browser.slf +++ b/libraries/render-utils/src/web_browser.slf @@ -35,6 +35,7 @@ LAYOUT(binding=0) uniform sampler2D webTexture; layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; <@else@> layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) in vec4 _prevPositionCS; <@endif@> layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; @@ -69,6 +70,6 @@ void main(void) { } } <@else@> - packDeferredFragmentUnlit(normalize(_normalWS), 1.0, texel.rgb); + packDeferredFragmentUnlit(_prevPositionCS, normalize(_normalWS), 1.0, texel.rgb); <@endif@> } diff --git a/libraries/render-utils/src/web_browser.slv b/libraries/render-utils/src/web_browser.slv index 07b4d7d3d7..0d7a82da8a 100644 --- a/libraries/render-utils/src/web_browser.slv +++ b/libraries/render-utils/src/web_browser.slv @@ -5,6 +5,7 @@ // // Created by Andrzej Kapolka on 9/15/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -22,6 +23,7 @@ layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; <@else@> layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) out vec4 _prevPositionCS; <@endif@> layout(location=RENDER_UTILS_ATTR_COLOR) out vec4 _color; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; @@ -33,9 +35,9 @@ void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <@if HIFI_USE_FORWARD@> - <$transformModelToWorldAndEyeAndClipPos(cam, obj, inPosition, _positionWS, _positionES, gl_Position)$> + <$transformModelToWorldEyeAndClipPos(cam, obj, inPosition, _positionWS, _positionES, gl_Position)$> <@else@> - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToClipPosAndPrevClipPos(cam, obj, inPosition, gl_Position, _prevPositionCS)$> <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normalWS)$> <@endif@> } \ No newline at end of file diff --git a/libraries/render-utils/src/zone_drawAmbient.slf b/libraries/render-utils/src/zone_drawAmbient.slf index d780fd0de2..1792407f3e 100644 --- a/libraries/render-utils/src/zone_drawAmbient.slf +++ b/libraries/render-utils/src/zone_drawAmbient.slf @@ -29,7 +29,7 @@ void main(void) { vec3 spherePos = normalize(vec3(sphereUV, sqrt(1.0 - sphereR2))); - vec3 fragNormal = vec3(getViewInverse() * vec4(spherePos, 0.0)); + vec3 fragNormal = vec3(getViewInverse(getStereoSideFromFragCoord()) * vec4(spherePos, 0.0)); LightAmbient lightAmbient = getLightAmbient(); diff --git a/libraries/render-utils/src/zone_drawKeyLight.slf b/libraries/render-utils/src/zone_drawKeyLight.slf index 7174914ed8..ddd1b1e9c9 100644 --- a/libraries/render-utils/src/zone_drawKeyLight.slf +++ b/libraries/render-utils/src/zone_drawKeyLight.slf @@ -33,8 +33,9 @@ void main(void) { const float INOUT_RATIO = 0.4; const float SUN_THRESHOLD = 0.99; + mat4 viewInverse = getViewInverse(getStereoSideFromFragCoord()); vec3 outSpherePos = normalize(vec3(sphereUV, -sqrt(1.0 - sphereR2))); - vec3 outNormal = vec3(getViewInverse() * vec4(outSpherePos, 0.0)); + vec3 outNormal = vec3(viewInverse * vec4(outSpherePos, 0.0)); float val = step(SUN_THRESHOLD, dot(-lightDirection, outNormal)); @@ -43,7 +44,7 @@ void main(void) { if (sphereR2 < INOUT_RATIO * INOUT_RATIO * SCOPE_RADIUS2) { vec2 inSphereUV = sphereUV / INOUT_RATIO; vec3 inSpherePos = normalize(vec3(inSphereUV, sqrt(1.0 - dot(inSphereUV.xy, inSphereUV.xy)))); - vec3 inNormal = vec3(getViewInverse() * vec4(inSpherePos, 0.0)); + vec3 inNormal = vec3(viewInverse * vec4(inSpherePos, 0.0)); vec3 marbleColor = max(lightIrradiance * vec3(dot(-lightDirection, inNormal)), vec3(0.01)); color += marbleColor; diff --git a/libraries/render-utils/src/zone_drawSkybox.slf b/libraries/render-utils/src/zone_drawSkybox.slf index 743b48d0bf..d6d4bedeb4 100644 --- a/libraries/render-utils/src/zone_drawSkybox.slf +++ b/libraries/render-utils/src/zone_drawSkybox.slf @@ -30,7 +30,7 @@ void main(void) { vec3 spherePos = normalize(vec3(sphereUV, -sqrt(1.0 - sphereR2))); - vec3 direction = vec3(getViewInverse() * vec4(spherePos, 0.0)); + vec3 direction = vec3(getViewInverse(getStereoSideFromFragCoord()) * vec4(spherePos, 0.0)); vec3 color = skybox.color.rgb; diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 44d54c3c28..145c6c681b 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -73,14 +73,8 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS std::static_pointer_cast(renderContext->jobConfig)->numFreeCells = (int)scene->getSpatialTree().getNumFreeCells(); gpu::doInBatch("DrawSceneOctree::run", args->_context, [&](gpu::Batch& batch) { - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); batch.setViewportTransform(args->_viewport); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat, true); + batch.setSavedViewProjectionTransform(_transformSlot); batch.setModelTransform(Transform()); // bind the one gpu::Pipeline we need @@ -177,14 +171,8 @@ void DrawItemSelection::run(const RenderContextPointer& renderContext, const Ite } gpu::doInBatch("DrawItemSelection::run", args->_context, [&](gpu::Batch& batch) { - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); batch.setViewportTransform(args->_viewport); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat, true); + batch.setSavedViewProjectionTransform(_transformSlot); batch.setModelTransform(Transform()); // bind the one gpu::Pipeline we need diff --git a/libraries/render/src/render/DrawSceneOctree.h b/libraries/render/src/render/DrawSceneOctree.h index 0c7441404a..eddac42d4d 100644 --- a/libraries/render/src/render/DrawSceneOctree.h +++ b/libraries/render/src/render/DrawSceneOctree.h @@ -63,7 +63,7 @@ namespace render { using Config = DrawSceneOctreeConfig; using JobModel = Job::ModelI; - DrawSceneOctree() {} + DrawSceneOctree(uint transformSlot) : _transformSlot(transformSlot) {} void configure(const Config& config); void run(const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& selection); @@ -71,6 +71,9 @@ namespace render { const gpu::PipelinePointer getDrawCellBoundsPipeline(); const gpu::PipelinePointer getDrawLODReticlePipeline(); const gpu::PipelinePointer getDrawItemBoundPipeline(); + + private: + uint _transformSlot; }; @@ -120,12 +123,15 @@ namespace render { using Config = DrawItemSelectionConfig; using JobModel = Job::ModelI; - DrawItemSelection() {} + DrawItemSelection(uint transformSlot) : _transformSlot(transformSlot) {} void configure(const Config& config); void run(const RenderContextPointer& renderContext, const ItemSpatialTree::ItemSelection& selection); const gpu::PipelinePointer getDrawItemBoundPipeline(); + + private: + uint _transformSlot; }; } diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index d010929e7a..b3a6ed658f 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -84,7 +84,7 @@ void DrawStatus::configure(const Config& config) { _showFade = config.showFade; } -void DrawStatus::run(const RenderContextPointer& renderContext, const Input& input) { +void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inItems) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; @@ -92,9 +92,6 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp const int NUM_STATUS_VEC4_PER_ITEM = 2; const int VEC4_LENGTH = 4; - const auto& inItems = input.get0(); - const auto jitter = input.get1(); - // First thing, we collect the bound and the status for all the items we want to render int nbItems = 0; render::ItemBounds itemBounds; @@ -208,15 +205,8 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp // Alright, something to render let's do it gpu::doInBatch("DrawStatus::run", args->_context, [&](gpu::Batch& batch) { - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); batch.setViewportTransform(args->_viewport); - - batch.setProjectionTransform(projMat); - batch.setProjectionJitter(jitter.x, jitter.y); - batch.setViewTransform(viewMat, true); + batch.setSavedViewProjectionTransform(_transformSlot); batch.setModelTransform(Transform()); // bind the one gpu::Pipeline we need diff --git a/libraries/render/src/render/DrawStatus.h b/libraries/render/src/render/DrawStatus.h index 8f61af1f95..b217eb54fd 100644 --- a/libraries/render/src/render/DrawStatus.h +++ b/libraries/render/src/render/DrawStatus.h @@ -43,11 +43,11 @@ namespace render { class DrawStatus { public: using Config = DrawStatusConfig; - using Input = VaryingSet2; + using Input = ItemBounds; using JobModel = Job::ModelI; DrawStatus() {} - DrawStatus(const gpu::TexturePointer statusIconMap) { setStatusIconMap(statusIconMap); } + DrawStatus(const gpu::TexturePointer statusIconMap, uint transformSlot) : _transformSlot(transformSlot) { setStatusIconMap(statusIconMap); } void configure(const Config& config); void run(const RenderContextPointer& renderContext, const Input& input); @@ -71,6 +71,8 @@ namespace render { gpu::BufferPointer _instanceBuffer; gpu::Stream::FormatPointer _vertexFormat; gpu::TexturePointer _statusIconMap; + + uint _transformSlot; }; } diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 68a87ca53c..2b5f4c8846 100644 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -194,12 +194,7 @@ void DrawBounds::run(const RenderContextPointer& renderContext, args->_batch = &batch; // Setup projection - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); + batch.setSavedViewProjectionTransform(_transformSlot); batch.setModelTransform(Transform()); // Bind program diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index 1ef4b8caf1..f948dce1b2 100644 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -58,6 +58,8 @@ public: using Inputs = render::ItemBounds; using JobModel = render::Job::ModelI; + DrawBounds(uint transformSlot) : _transformSlot(transformSlot) {} + void configure(const Config& configuration) {} void run(const render::RenderContextPointer& renderContext, const Inputs& items); @@ -67,6 +69,7 @@ private: gpu::PipelinePointer _boundsPipeline; gpu::BufferPointer _drawBuffer; gpu::BufferPointer _paramsBuffer; + uint _transformSlot; }; class DrawQuadVolumeConfig : public render::JobConfig { diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h index f1d59727d3..6de0727a3a 100644 --- a/libraries/render/src/render/Engine.h +++ b/libraries/render/src/render/Engine.h @@ -85,6 +85,11 @@ namespace render { class RenderEngine : public Engine { public: + enum TransformSlot { + TS_MAIN_VIEW = 0, + TS_BACKGROUND_VIEW + }; + RenderEngine(); ~RenderEngine() = default; diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 048e08e959..467abd9f9a 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -101,6 +101,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->lightBufferUnit = reflection.validUniformBuffer(graphics::slot::buffer::Light); locations->lightAmbientBufferUnit = reflection.validUniformBuffer(graphics::slot::buffer::AmbientLight); locations->lightAmbientMapUnit = reflection.validTexture(graphics::slot::texture::Skybox); + locations->deferredFrameTransformBufferUnit = reflection.validUniformBuffer(render_utils::slot::buffer::DeferredFrameTransform); locations->fadeMaskTextureUnit = reflection.validTexture(render_utils::slot::texture::FadeMask); locations->fadeParameterBufferUnit = reflection.validUniformBuffer(render_utils::slot::buffer::FadeParameters); locations->fadeObjectParameterBufferUnit = reflection.validUniformBuffer(render_utils::slot::buffer::FadeObjectParameters); diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index 8e3505b2e5..231f4ecfe7 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -285,6 +285,7 @@ public: bool lightBufferUnit{ false }; bool lightAmbientBufferUnit{ false }; bool lightAmbientMapUnit{ false }; + bool deferredFrameTransformBufferUnit{ false }; bool fadeMaskTextureUnit{ false }; bool fadeParameterBufferUnit{ false }; bool fadeObjectParameterBufferUnit{ false }; diff --git a/libraries/render/src/render/drawItemStatus.slv b/libraries/render/src/render/drawItemStatus.slv index 7aac26fe2e..a70112ffe6 100644 --- a/libraries/render/src/render/drawItemStatus.slv +++ b/libraries/render/src/render/drawItemStatus.slv @@ -7,6 +7,7 @@ // // Created by Sam Gateau on 6/30/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -128,8 +129,7 @@ void main(void) { // Final position in pixel space vec2 quadPixelPos = offset.xy + quadPos.xy * 0.5 * iconScale; - vec4 viewport; - <$transformCameraViewport(cam, viewport)$>; + vec4 viewport = cam._viewport; vec2 pixelToClip = vec2(2.0 / viewport.z, 2.0 / viewport.w); gl_Position = anchorPoint + (anchorPoint.w * vec4(quadPixelPos * pixelToClip, 0.0, 0.0)); diff --git a/scripts/developer/utilities/render/luci/Antialiasing.qml b/scripts/developer/utilities/render/luci/Antialiasing.qml index 2a52dfed46..10627fcab4 100644 --- a/scripts/developer/utilities/render/luci/Antialiasing.qml +++ b/scripts/developer/utilities/render/luci/Antialiasing.qml @@ -3,6 +3,7 @@ // // Created by Sam Gateau on 8/14/2017 // Copyright 2016 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html @@ -38,7 +39,7 @@ Column{ Prop.PropEnum { label: "Deferred AA Method" - object: Render.getConfig("RenderMainView.Antialiasing") + object: Render.getConfig("RenderMainView.AntialiasingSetup") property: "mode" enums: [ "Off", @@ -49,20 +50,20 @@ Column{ Prop.PropEnum { id: jitter label: "Jitter" - object: Render.getConfig("RenderMainView.JitterCam") + object: Render.getConfig("RenderMainView.AntialiasingSetup") property: "state" enums: [ "Off", - "On", "Paused", + "On", ] } Separator {} Prop.PropScalar { - visible: (Render.getConfig("RenderMainView.JitterCam").state == 2) + visible: (Render.getConfig("RenderMainView.AntialiasingSetup").state == 1) label: "Sample Index" - object: Render.getConfig("RenderMainView.JitterCam") + object: Render.getConfig("RenderMainView.AntialiasingSetup") property: "index" // min: -1 // max: 32 @@ -70,18 +71,27 @@ Column{ integral: true } Row { - visible: (Render.getConfig("RenderMainView.JitterCam").state == 2) + visible: (Render.getConfig("RenderMainView.AntialiasingSetup").state == 1) spacing: 10 HifiControls.Button { text: "<" - onClicked: { Render.getConfig("RenderMainView.JitterCam").prev(); } + onClicked: { Render.getConfig("RenderMainView.AntialiasingSetup").prev(); } } HifiControls.Button { text: ">" - onClicked: { Render.getConfig("RenderMainView.JitterCam").next(); } + onClicked: { Render.getConfig("RenderMainView.AntialiasingSetup").next(); } } } + ConfigSlider { + label: qsTr("Jitter scale") + integral: false + config: Render.getConfig("RenderMainView.AntialiasingSetup") + property: "scale" + max: 2.0 + min: 0.25 + height: 38 + } Separator {} Prop.PropBool { label: "Constrain color" @@ -100,7 +110,12 @@ Column{ label: "Feedback history color" object: Render.getConfig("RenderMainView.Antialiasing") property: "feedbackColor" - } + } + Prop.PropBool { + label: "History bicubic fetch" + object: Render.getConfig("RenderMainView.Antialiasing") + property: "bicubicHistoryFetch" + } Prop.PropScalar { label: "Source blend" object: Render.getConfig("RenderMainView.Antialiasing") diff --git a/scripts/developer/utilities/render/luci/Framebuffer.qml b/scripts/developer/utilities/render/luci/Framebuffer.qml index b7a992c589..c1603d646b 100644 --- a/scripts/developer/utilities/render/luci/Framebuffer.qml +++ b/scripts/developer/utilities/render/luci/Framebuffer.qml @@ -61,6 +61,7 @@ Column { "Ambient Occlusion Blurred", "Ambient Occlusion Normal", "Velocity", + "Antialiasing Intensity", "Custom", ] diff --git a/tests-manual/gpu/src/TestWindow.cpp b/tests-manual/gpu/src/TestWindow.cpp index f667f20f2b..af8c6af243 100644 --- a/tests-manual/gpu/src/TestWindow.cpp +++ b/tests-manual/gpu/src/TestWindow.cpp @@ -28,7 +28,7 @@ extern void initDeferredPipelines(render::ShapePlumber& plumber, const render::S extern void initStencilPipeline(gpu::PipelinePointer& pipeline); #endif -TestWindow::TestWindow() { +TestWindow::TestWindow() : _generateDeferredFrameTransform(render::RenderEngine::TS_MAIN_VIEW) { setSurfaceType(QSurface::OpenGLSurface); @@ -98,7 +98,7 @@ void TestWindow::beginFrame() { _preparePrimaryFramebuffer.run(_renderContext, primaryFramebuffer); DeferredFrameTransformPointer frameTransform; - _generateDeferredFrameTransform.run(_renderContext, glm::vec2(0.0f, 0.0f), frameTransform); + _generateDeferredFrameTransform.run(_renderContext, frameTransform); LightingModelPointer lightingModel; _generateLightingModel.run(_renderContext, lightingModel); diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp index 0089c1577b..d74551a9bc 100644 --- a/tools/gpu-frame-player/src/RenderThread.cpp +++ b/tools/gpu-frame-player/src/RenderThread.cpp @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2018/10/21 // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -119,11 +120,10 @@ void RenderThread::renderFrame(gpu::FramePointer& frame) { #ifdef USE_GL _context.makeCurrent(); #endif - if (_correction != glm::mat4()) { + { std::unique_lock lock(_frameLock); if (_correction != glm::mat4()) { - _backend->setCameraCorrection(_correction, _activeFrame->view); - //_prevRenderView = _correction * _activeFrame->view; + _backend->updatePresentFrame(_correction); } } _backend->recycle(); From d5e251d8da0168bfcf69da6e239075655b711a8e Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Mon, 26 Feb 2024 14:58:15 -0800 Subject: [PATCH 02/35] minor cleanup, copyright --- .../scripting/RenderScriptingInterface.cpp | 2 +- .../src/scripting/RenderScriptingInterface.h | 2 +- .../src/RenderableGizmoEntityItem.cpp | 2 +- .../src/RenderableGridEntityItem.cpp | 1 + .../src/RenderableImageEntityItem.cpp | 1 + .../src/RenderableLineEntityItem.cpp | 2 +- .../src/RenderableModelEntityItem.cpp | 2 +- .../RenderableParticleEffectEntityItem.cpp | 8 ++- .../src/RenderablePolyLineEntityItem.cpp | 2 +- .../src/RenderablePolyVoxEntityItem.cpp | 1 - .../src/RenderableShapeEntityItem.cpp | 12 ++-- .../src/RenderableTextEntityItem.cpp | 17 +++--- .../src/RenderableTextEntityItem.h | 1 - .../src/RenderableWebEntityItem.cpp | 1 + .../entities-renderer/src/paintStroke.slf | 2 +- .../entities-renderer/src/paintStroke.slv | 2 +- libraries/entities-renderer/src/polyvox.slf | 3 +- libraries/entities-renderer/src/polyvox.slv | 1 + .../src/textured_particle.slf | 3 + .../src/textured_particle.slv | 4 +- .../gpu-gl-common/src/gpu/gl/GLBackend.cpp | 8 +-- .../gpu-gl-common/src/gpu/gl/GLBackend.h | 8 +-- .../src/gpu/gl/GLBackendPipeline.cpp | 1 + .../src/gpu/gl/GLBackendTransform.cpp | 6 +- .../gpu-gl-common/src/gpu/gl/GLPipeline.cpp | 1 + .../gpu-gl-common/src/gpu/gl/GLPipeline.h | 1 + libraries/gpu-gl/src/gpu/gl41/GL41Backend.h | 1 + .../src/gpu/gl41/GL41BackendTransform.cpp | 1 + libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 1 + .../src/gpu/gl45/GL45BackendTexture.cpp | 1 + .../src/gpu/gl45/GL45BackendTransform.cpp | 1 + libraries/gpu/src/gpu/Backend.cpp | 4 +- libraries/gpu/src/gpu/Backend.h | 2 +- libraries/gpu/src/gpu/Batch.cpp | 5 +- libraries/gpu/src/gpu/Batch.h | 1 + libraries/gpu/src/gpu/Context.cpp | 1 + libraries/gpu/src/gpu/Context.h | 1 + libraries/gpu/src/gpu/DrawUnitQuad.slv | 7 ++- libraries/gpu/src/gpu/FrameIOKeys.h | 2 +- libraries/gpu/src/gpu/FrameReader.cpp | 2 +- libraries/gpu/src/gpu/FrameWriter.cpp | 2 +- libraries/gpu/src/gpu/Query.h | 1 + libraries/gpu/src/gpu/Transform.slh | 6 +- .../gpu/src/gpu/TransformCamera_shared.slh | 2 +- .../gpu/src/gpu/TransformObject_shared.slh | 4 +- libraries/graphics/src/graphics/Haze.slh | 1 + libraries/graphics/src/graphics/Skybox.cpp | 1 + libraries/graphics/src/graphics/Skybox.h | 1 + libraries/graphics/src/graphics/skybox.slf | 2 +- libraries/graphics/src/graphics/skybox.slh | 10 +-- libraries/graphics/src/graphics/skybox.slv | 5 +- .../src/procedural/ProceduralSkybox.cpp | 1 + .../src/procedural/ProceduralSkybox.h | 1 + .../src/procedural/proceduralSkybox.slf | 2 +- .../render-utils/src/AntialiasingEffect.cpp | 50 +++++---------- .../render-utils/src/AntialiasingEffect.h | 61 +++++++++++++------ .../render-utils/src/BackgroundStage.cpp | 2 +- libraries/render-utils/src/BloomEffect.cpp | 1 + libraries/render-utils/src/BloomThreshold.slf | 1 + .../src/CauterizedMeshPartPayload.cpp | 3 +- .../render-utils/src/DebugDeferredBuffer.cpp | 2 +- .../render-utils/src/DebugDeferredBuffer.h | 1 + .../render-utils/src/DeferredBufferRead.slh | 1 + .../render-utils/src/DeferredBufferWrite.slh | 10 +-- .../src/DeferredBufferWrite_shared.slh | 4 +- .../src/DeferredFrameTransform.cpp | 1 + .../render-utils/src/DeferredFrameTransform.h | 6 +- .../render-utils/src/DeferredFramebuffer.cpp | 1 + .../render-utils/src/DeferredFramebuffer.h | 1 + .../src/DeferredLightingEffect.cpp | 1 + .../render-utils/src/DeferredTransform.slh | 2 +- .../src/DeferredTransform_shared.slh | 4 +- libraries/render-utils/src/GeometryCache.cpp | 1 + libraries/render-utils/src/HighlightEffect.h | 6 +- .../render-utils/src/Highlight_aabox.slv | 1 + libraries/render-utils/src/LightClusters.cpp | 1 + .../render-utils/src/MeshPartPayload.cpp | 5 +- libraries/render-utils/src/MeshPartPayload.h | 2 +- .../render-utils/src/RenderCommonTask.cpp | 2 +- libraries/render-utils/src/RenderCommonTask.h | 1 + .../render-utils/src/RenderDeferredTask.cpp | 2 +- .../render-utils/src/RenderDeferredTask.h | 3 +- .../render-utils/src/RenderForwardTask.cpp | 1 + .../render-utils/src/RenderForwardTask.h | 1 + .../render-utils/src/RenderHUDLayerTask.cpp | 2 +- .../render-utils/src/RenderHUDLayerTask.h | 2 +- libraries/render-utils/src/RenderViewTask.cpp | 1 + libraries/render-utils/src/RenderViewTask.h | 1 + .../render-utils/src/StencilMaskPass.cpp | 1 + libraries/render-utils/src/StencilMaskPass.h | 3 +- libraries/render-utils/src/VelocityWrite.slh | 2 +- libraries/render-utils/src/ZoneRenderer.cpp | 1 + libraries/render-utils/src/aa_blend.slf | 5 +- .../src/deferred_light_limited.slv | 1 + .../src/directional_skybox_light.slf | 1 + .../render-utils/src/drawWorkloadProxy.slf | 1 + .../render-utils/src/drawWorkloadProxy.slv | 2 +- .../render-utils/src/drawWorkloadView.slf | 1 + .../render-utils/src/drawWorkloadView.slv | 1 + libraries/render-utils/src/grid.slf | 2 +- libraries/render-utils/src/grid.slv | 4 +- .../src/lightClusters_drawClusterContent.slf | 1 + .../lightClusters_drawClusterFromDepth.slf | 1 + .../src/local_lights_drawOutline.slf | 1 + .../render-utils/src/local_lights_shading.slf | 1 + libraries/render-utils/src/model.slf | 21 ++++--- libraries/render-utils/src/parabola.slf | 1 + libraries/render-utils/src/parabola.slv | 1 + .../src/render-utils/ShaderConstants.h | 1 + libraries/render-utils/src/sdf_text3D.slf | 3 + libraries/render-utils/src/sdf_text3D.slh | 1 + libraries/render-utils/src/sdf_text3D.slv | 2 + libraries/render-utils/src/simple.slf | 2 +- libraries/render-utils/src/simple.slv | 2 +- .../render-utils/src/simple_procedural.slf | 1 + .../render-utils/src/simple_procedural.slv | 1 + .../src/surfaceGeometry_makeCurvature.slf | 1 + libraries/render-utils/src/taa.slf | 2 +- libraries/render-utils/src/taa.slh | 4 +- libraries/render-utils/src/taa_blend.slf | 1 + libraries/render-utils/src/web_browser.slf | 1 + libraries/render-utils/src/web_browser.slv | 2 +- .../render-utils/src/zone_drawAmbient.slf | 1 + .../render-utils/src/zone_drawKeyLight.slf | 1 + .../render-utils/src/zone_drawSkybox.slf | 1 + .../render/src/render/DrawSceneOctree.cpp | 1 + libraries/render/src/render/DrawSceneOctree.h | 3 +- libraries/render/src/render/DrawStatus.cpp | 1 + libraries/render/src/render/DrawTask.cpp | 3 +- libraries/render/src/render/DrawTask.h | 3 +- libraries/render/src/render/Engine.h | 1 + libraries/render/src/render/ShapePipeline.cpp | 1 + .../render/src/render/drawItemStatus.slv | 2 +- .../utilities/render/luci/Antialiasing.qml | 2 +- .../utilities/render/luci/Framebuffer.qml | 1 + tools/gpu-frame-player/src/RenderThread.cpp | 2 +- 136 files changed, 266 insertions(+), 180 deletions(-) diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index 50bdb5557d..cecfbc63a2 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -219,7 +219,7 @@ void RenderScriptingInterface::forceAntialiasingMode(AntialiasingSetupConfig::Mo setAntialiasingModeForView( mode, secondViewJitterCamConfig, secondViewAntialiasingConfig); } - _antialiasingModeSetting.set(_antialiasingMode); + _antialiasingModeSetting.set((int)_antialiasingMode); }); } diff --git a/interface/src/scripting/RenderScriptingInterface.h b/interface/src/scripting/RenderScriptingInterface.h index dd95cca568..126fc67f94 100644 --- a/interface/src/scripting/RenderScriptingInterface.h +++ b/interface/src/scripting/RenderScriptingInterface.h @@ -246,7 +246,7 @@ private: Setting::Handle _shadowsEnabledSetting { "shadowsEnabled", true }; Setting::Handle _ambientOcclusionEnabledSetting { "ambientOcclusionEnabled", false }; //Setting::Handle _antialiasingModeSetting { "antialiasingMode", AntialiasingConfig::Mode::TAA }; - Setting::Handle _antialiasingModeSetting { "antialiasingMode", AntialiasingSetupConfig::Mode::NONE }; + Setting::Handle _antialiasingModeSetting { "antialiasingMode", (int)AntialiasingSetupConfig::Mode::NONE }; Setting::Handle _viewportResolutionScaleSetting { "viewportResolutionScale", 1.0f }; Setting::Handle _fullScreenScreenSetting { "fullScreenScreen", "" }; diff --git a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp index 2b7d45dc4c..e8ac59898a 100644 --- a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp @@ -1,7 +1,7 @@ // // Created by Sam Gondelman on 1/22/19 // Copyright 2019 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp index 76ae8760be..d535c5f846 100644 --- a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp @@ -1,6 +1,7 @@ // // Created by Sam Gondelman on 11/29/18 // Copyright 2018 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp index 217c64f1fb..209a551b4a 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp @@ -1,6 +1,7 @@ // // Created by Sam Gondelman on 11/29/18 // Copyright 2018 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index 95fe17c986..5752e6eb65 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -4,7 +4,7 @@ // // Created by Seth Alves on 5/11/15. // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index f5e7550a27..c30cf05c8f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -4,7 +4,7 @@ // // Created by Brad Hefta-Gaub on 8/6/14. // Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index accaae0a1c..2276d52111 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -3,7 +3,7 @@ // interface/src // // Created by Jason Rickwald on 3/2/15. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -32,6 +32,7 @@ static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, co state->setDepthTest(true, false, gpu::LESS_EQUAL); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + // TODO: handle opaque PrepareStencil::testMaskResetNoAA(*state); auto program = gpu::Shader::createProgram(shader::entities_renderer::program::textured_particle); @@ -456,7 +457,10 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { color.finish = EntityRenderer::calculatePulseColor(_particleProperties.getColorFinish(), _pulseProperties, _created); color.spread = EntityRenderer::calculatePulseColor(_particleProperties.getColorSpread(), _pulseProperties, _created); - batch.setModelTransform(transform); // particles are currently always transparent so we don't worry about TAA right now + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } batch.setUniformBuffer(0, _uniformBuffer); batch.setInputFormat(_vertexFormat); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 1e7e3f688b..2d9b025cbf 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -4,7 +4,7 @@ // // Created by Eric Levin on 8/10/15 // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index c7581f3ec1..4210031482 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -4,7 +4,6 @@ // // Created by Seth Alves on 5/19/15. // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. // Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 7f73902eca..1f0865eddc 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -1,7 +1,7 @@ // // Created by Bradley Austin Davis on 2016/05/09 // Copyright 2013 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -112,13 +112,8 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { auto geometryCache = DependencyManager::get(); GeometryCache::Shape geometryShape = geometryCache->getShapeForEntityShape(_shape); Transform transform; - Transform prevTransform; withReadLock([&] { transform = _renderTransform; - prevTransform = _prevRenderTransform; - if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { - _prevRenderTransform = _renderTransform; - } }); bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES; @@ -126,7 +121,10 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition(), _shape < entity::Shape::Cube || _shape > entity::Shape::Icosahedron)); - batch.setModelTransform(transform, prevTransform); + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } Pipeline pipelineType = getPipelineType(materials); if (pipelineType == Pipeline::PROCEDURAL) { diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 7769e1ef68..3fd9bfc6c8 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -4,8 +4,7 @@ // // Created by Brad Hefta-Gaub on 8/6/14. // Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. -// Copyright 2023 Overte e.V. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -160,19 +159,17 @@ void TextEntityRenderer::doRender(RenderArgs* args) { bool transparent; Transform transform; - Transform prevTransform; withReadLock([&] { transparent = isTransparent(); transform = _renderTransform; - prevTransform = _prevRenderTransform; - transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, - args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); - if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { - _prevRenderTransform = transform; - } }); - batch.setModelTransform(transform, prevTransform); + transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), _billboardMode, + args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE ? BillboardModeHelpers::getPrimaryViewFrustumPosition() : args->getViewFrustum().getPosition())); + batch.setModelTransform(transform, _prevRenderTransform); + if (args->_renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || args->_renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { + _prevRenderTransform = transform; + } Pipeline pipelineType = getPipelineType(materials); if (pipelineType == Pipeline::PROCEDURAL) { diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index df256a5cd0..738d93704f 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -4,7 +4,6 @@ // // Created by Brad Hefta-Gaub on 8/6/14. // Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. // Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index cd2f8b25f8..a6db84e056 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -2,6 +2,7 @@ // Created by Bradley Austin Davis on 2015/05/12 // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/entities-renderer/src/paintStroke.slf b/libraries/entities-renderer/src/paintStroke.slf index 4b73d50bd7..2d4d8e4d26 100644 --- a/libraries/entities-renderer/src/paintStroke.slf +++ b/libraries/entities-renderer/src/paintStroke.slf @@ -5,7 +5,7 @@ // // Created by Eric Levin on 8/10/2015 // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/entities-renderer/src/paintStroke.slv b/libraries/entities-renderer/src/paintStroke.slv index 23315cbc2d..bdf31012f0 100644 --- a/libraries/entities-renderer/src/paintStroke.slv +++ b/libraries/entities-renderer/src/paintStroke.slv @@ -5,7 +5,7 @@ // // Created by Eric Levin on 7/20/15. // Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index dd735ecf16..f5251fe1f8 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -5,6 +5,7 @@ // // Created by Seth Alves on 2015-8-3 // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -37,7 +38,7 @@ <@if HIFI_USE_FORWARD@> layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; <@else@> - layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) in vec4 _prevPositionCS; + layout(location=RENDER_UTILS_ATTR_PREV_POSITION_CS) in vec4 _prevPositionCS; <@endif@> layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec3 _positionMS; layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/entities-renderer/src/polyvox.slv b/libraries/entities-renderer/src/polyvox.slv index 42ee508bfd..794d56645d 100644 --- a/libraries/entities-renderer/src/polyvox.slv +++ b/libraries/entities-renderer/src/polyvox.slv @@ -4,6 +4,7 @@ // Generated on <$_SCRIBE_DATE$> // // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/entities-renderer/src/textured_particle.slf b/libraries/entities-renderer/src/textured_particle.slf index 7dadb6fc49..2b8e189832 100644 --- a/libraries/entities-renderer/src/textured_particle.slf +++ b/libraries/entities-renderer/src/textured_particle.slf @@ -5,6 +5,7 @@ // textured_particle.frag // // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -19,4 +20,6 @@ layout(location=0) out vec4 outFragColor; void main(void) { outFragColor = texture(colorMap, varTexcoord.xy) * varColor; + + // TODO: handle opaque particles } diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv index e66b7d0302..eb0054630c 100644 --- a/libraries/entities-renderer/src/textured_particle.slv +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -5,7 +5,7 @@ // texture_particle.vert // // Copyright 2015 High Fidelity, Inc. -// Copyright 2023 Overte e.V. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -174,4 +174,6 @@ void main(void) { vec4 quadPos = radius * vec4(UNIT_QUAD[twoTriID], 0.0); vec4 eyePos = anchorPoint + rotation * quadPos; <$transformEyeToClipPos(cam, eyePos, gl_Position)$> + + // TODO: _prevPositionCS } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index e80279fa87..6e4a9f66c5 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -4,7 +4,7 @@ // // Created by Sam Gateau on 10/27/2014. // Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -325,6 +325,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) { case Batch::COMMAND_drawIndexedInstanced: case Batch::COMMAND_multiDrawIndirect: case Batch::COMMAND_multiDrawIndexedIndirect: + case Batch::COMMAND_copySavedViewProjectionTransformToBuffer: // We need to store this transform state in the transform buffer preUpdateTransform(); break; @@ -336,11 +337,6 @@ void GLBackend::renderPassTransfer(const Batch& batch) { _stereo._contextDisable = false; break; - case Batch::COMMAND_copySavedViewProjectionTransformToBuffer: - // We need to store this transform state in the transform buffer - preUpdateTransform(); - break; - case Batch::COMMAND_setFramebuffer: case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setViewTransform: diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 8d07b069d6..4ff3b959b3 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -4,7 +4,7 @@ // // Created by Sam Gateau on 10/27/2014. // Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -308,7 +308,7 @@ protected: virtual bool supportsBindless() const { return false; } static const size_t INVALID_OFFSET = (size_t)-1; - static const uint INVALID_SAVED_CAMERA_SLOT = (uint)-1; + static const uint INVALID_SAVED_CAMERA_SLOT = (uint)-1; bool _inRenderTransferPass { false }; int _currentDraw { -1 }; @@ -424,7 +424,7 @@ protected: using CameraBufferElement = TransformCamera; #endif using TransformCameras = std::vector; - + struct ViewProjectionState { Transform _view; Transform _correctedView; @@ -448,7 +448,7 @@ protected: TransformCamera _camera; TransformCameras _cameras; - std::array _savedTransforms; + std::array _savedTransforms; mutable std::map _drawCallInfoOffsets; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index 45db4ba5fb..5c2219e720 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 3/8/2015. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp index 0f07fde876..db228281e3 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp @@ -4,7 +4,7 @@ // // Created by Sam Gateau on 3/8/2015. // Copyright 2014 High Fidelity, Inc. -// Copyright 2023 Overte e.V. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -19,7 +19,7 @@ void GLBackend::do_setModelTransform(const Batch& batch, size_t paramOffset) { } void GLBackend::do_setViewTransform(const Batch& batch, size_t paramOffset) { - _transform._viewProjectionState._view = batch._transforms.get(batch._params[paramOffset]._uint); + _transform._viewProjectionState._view = batch._transforms.get(batch._params[paramOffset]._uint); // View history is only supported with saved transforms and if setViewTransform is called (and not setSavedViewProjectionTransform) // then, in consequence, the view will NOT be corrected in the present thread. In which case // the previousCorrectedView should be the same as the view. @@ -119,7 +119,7 @@ void GLBackend::syncTransformStateCache() { Mat4 modelView; auto modelViewInv = glm::inverse(modelView); - _transform._viewProjectionState._view.evalFromRawMatrix(modelViewInv); + _transform._viewProjectionState._view.evalFromRawMatrix(modelViewInv); glDisableVertexAttribArray(gpu::Stream::DRAW_CALL_INFO); _transform._enabledDrawcallInfoBuffer = false; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp index d4786a5a8f..0d7b28e223 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2016/05/15 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h index 1634483323..b58ca3be6d 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.h @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2016/05/15 // Copyright 2013-2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index 49ef58ab00..967d94a687 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 10/27/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp index 6d7e108194..e49405d3cf 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTransform.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 3/8/2015. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index c8b0a84605..e0b921237e 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 10/27/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index f1fa282720..3d30ebf03e 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 1/19/2015. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp index 644ec6ae8e..0fd328f353 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTransform.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 3/8/2015. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu/src/gpu/Backend.cpp b/libraries/gpu/src/gpu/Backend.cpp index c344b4c535..7859a86824 100644 --- a/libraries/gpu/src/gpu/Backend.cpp +++ b/libraries/gpu/src/gpu/Backend.cpp @@ -4,7 +4,7 @@ // // Created by Olivier Prat on 05/25/2018. // Copyright 2018 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -120,6 +120,8 @@ const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived(const viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f); _previousProjectionViewUntranslated = previousProjection * viewUntranslated; + //_previousProjectionViewUntranslated = _projection * viewUntranslated; + _stereoInfo = Vec4(0.0f); return *this; diff --git a/libraries/gpu/src/gpu/Backend.h b/libraries/gpu/src/gpu/Backend.h index 901780829c..0b180671d7 100644 --- a/libraries/gpu/src/gpu/Backend.h +++ b/libraries/gpu/src/gpu/Backend.h @@ -4,7 +4,7 @@ // // Created by Olivier Prat on 05/18/2018. // Copyright 2018 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index bc53bf9c8e..59eff31ce8 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 10/14/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -104,7 +105,7 @@ void Batch::clear() { _drawcallUniformReset = 0; _enableStereo = true; _enableSkybox = false; - _mustUpdatePreviousModels = true; + _mustUpdatePreviousModels = true; } size_t Batch::cacheData(size_t size, const void* data) { @@ -273,7 +274,7 @@ void Batch::pushProjectionJitterEnabled(bool isProjectionEnabled) { _params.emplace_back(isProjectionEnabled & 1); } -void Batch::popProjectionJitterEnabled() { +void Batch::popProjectionJitterEnabled() { pushProjectionJitterEnabled(_isJitterOnProjectionEnabled); } diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index df9161bdda..18de16d903 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 10/14/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index 7afd4374fe..cb85923033 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 10/27/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 4e5cab17a8..439aeec6bf 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 10/27/2014. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu/src/gpu/DrawUnitQuad.slv b/libraries/gpu/src/gpu/DrawUnitQuad.slv index 909ab81655..b87b9d0e8d 100644 --- a/libraries/gpu/src/gpu/DrawUnitQuad.slv +++ b/libraries/gpu/src/gpu/DrawUnitQuad.slv @@ -1,13 +1,14 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> -// <$_SCRIBE_FILENAME$> +// <$_SCRIBE_FILENAME$> // Generated on <$_SCRIBE_DATE$> -// Draw the unit quad [-1,-1 -> 1,1]. -// Not transform used. +// Draw the unit quad [-1,-1 -> 1,1]. +// No transform used. // Simply draw a Triangle_strip of 2 triangles, no input buffers or index buffer needed // // Created by Olivier Prat on 10/22/2018 // Copyright 2018 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu/src/gpu/FrameIOKeys.h b/libraries/gpu/src/gpu/FrameIOKeys.h index 6cc4a6bd8c..4e9833fd46 100644 --- a/libraries/gpu/src/gpu/FrameIOKeys.h +++ b/libraries/gpu/src/gpu/FrameIOKeys.h @@ -1,7 +1,7 @@ // // Created by Bradley Austin Davis on 2018/10/14 // Copyright 2013-2018 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu/src/gpu/FrameReader.cpp b/libraries/gpu/src/gpu/FrameReader.cpp index 79858e2888..d156720a1f 100644 --- a/libraries/gpu/src/gpu/FrameReader.cpp +++ b/libraries/gpu/src/gpu/FrameReader.cpp @@ -1,7 +1,7 @@ // // Created by Bradley Austin Davis on 2018/10/14 // Copyright 2013-2018 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu/src/gpu/FrameWriter.cpp b/libraries/gpu/src/gpu/FrameWriter.cpp index eb53041280..f3e632bcad 100644 --- a/libraries/gpu/src/gpu/FrameWriter.cpp +++ b/libraries/gpu/src/gpu/FrameWriter.cpp @@ -1,7 +1,7 @@ // // Created by Bradley Austin Davis on 2018/10/14 // Copyright 2013-2018 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu/src/gpu/Query.h b/libraries/gpu/src/gpu/Query.h index acec5500b2..e327e14cc0 100644 --- a/libraries/gpu/src/gpu/Query.h +++ b/libraries/gpu/src/gpu/Query.h @@ -4,6 +4,7 @@ // // Created by Niraj Venkat on 7/7/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index 5653d67a26..4d5bbd6dfa 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -3,7 +3,7 @@ // // Created by Sam Gateau on 2/10/15. // Copyright 2013 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -229,7 +229,7 @@ TransformObject getTransformObject() { <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; <$transformStereoClipSpace($clipPos$)$> - <$prevClipPos$> = <$cameraTransform$>._previousProjectionViewUntranslated * prevEyeWAPos; + <$prevClipPos$> = <$cameraTransform$>._previousProjectionViewUntranslated * prevEyeWAPos; // Prev clip pos is in mono clip space } <@endfunc@> @@ -363,7 +363,7 @@ TransformObject getTransformObject() { <@endfunc@> <@func transformEyeToPrevClipPos(cameraTransform, eyePos, prevClipPos)@> - { // transformEyeToClipPos + { // transformEyeToPrevClipPos vec4 worldPos = <$cameraTransform$>._viewInverse * vec4(<$eyePos$>.xyz, 1.0); <$prevClipPos$> = <$cameraTransform$>._previousProjectionViewUntranslated * worldPos; // Prev clip pos is in mono clip space diff --git a/libraries/gpu/src/gpu/TransformCamera_shared.slh b/libraries/gpu/src/gpu/TransformCamera_shared.slh index a7d8f58590..86a3b06db2 100644 --- a/libraries/gpu/src/gpu/TransformCamera_shared.slh +++ b/libraries/gpu/src/gpu/TransformCamera_shared.slh @@ -6,7 +6,7 @@ #else # define TC_MAT4 mat4 # define TC_VEC4 vec4 -# define TC_MUTABLE +# define TC_MUTABLE #endif struct _TransformCamera { diff --git a/libraries/gpu/src/gpu/TransformObject_shared.slh b/libraries/gpu/src/gpu/TransformObject_shared.slh index dce87c9e0b..edb7ca4a5b 100644 --- a/libraries/gpu/src/gpu/TransformObject_shared.slh +++ b/libraries/gpu/src/gpu/TransformObject_shared.slh @@ -13,7 +13,7 @@ struct TransformObject { }; // <@if 1@> - // Trigger Scribe include - // <@endif@> + // Trigger Scribe include + // <@endif@> // diff --git a/libraries/graphics/src/graphics/Haze.slh b/libraries/graphics/src/graphics/Haze.slh index 2a98177e83..e10f1b1449 100644 --- a/libraries/graphics/src/graphics/Haze.slh +++ b/libraries/graphics/src/graphics/Haze.slh @@ -3,6 +3,7 @@ // // Created by Nissim Hadar on 9/13/2017 // Copyright 2013 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/graphics/src/graphics/Skybox.cpp b/libraries/graphics/src/graphics/Skybox.cpp index e0134c337b..b9232418fc 100644 --- a/libraries/graphics/src/graphics/Skybox.cpp +++ b/libraries/graphics/src/graphics/Skybox.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 5/4/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/graphics/src/graphics/Skybox.h b/libraries/graphics/src/graphics/Skybox.h index 94b12cf1ab..6dc788c48c 100644 --- a/libraries/graphics/src/graphics/Skybox.h +++ b/libraries/graphics/src/graphics/Skybox.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 5/4/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/graphics/src/graphics/skybox.slf b/libraries/graphics/src/graphics/skybox.slf index 394af6f260..eb34488d50 100755 --- a/libraries/graphics/src/graphics/skybox.slf +++ b/libraries/graphics/src/graphics/skybox.slf @@ -5,7 +5,7 @@ // // Created by Sam Gateau on 5/5/2015. // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/graphics/src/graphics/skybox.slh b/libraries/graphics/src/graphics/skybox.slh index e84f770c7d..6bb8570aad 100644 --- a/libraries/graphics/src/graphics/skybox.slh +++ b/libraries/graphics/src/graphics/skybox.slh @@ -3,7 +3,7 @@ // libraries/graphics/src // // Created by HifiExperiments on 8/5/2020. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -46,11 +46,11 @@ vec2 packVelocity(vec4 prevPositionCS) { } // Must match layout in DeferredBufferWrite.slh, but only velocity and lighting are used -layout(location = 0) out vec4 _albedoMetallic; // albedo / metallic -layout(location = 1) out vec4 _normalRoughness; // normal / roughness +layout(location = 0) out vec4 _albedoMetallic; // albedo / metallic +layout(location = 1) out vec4 _normalRoughness; // normal / roughness layout(location = 2) out vec4 _scatteringEmissiveOcclusion; // scattering / emissive / occlusion -layout(location = 3) out vec4 _velocity; // velocity -layout(location = 4) out vec4 _lighting; // emissive +layout(location = 3) out vec4 _velocity; // velocity +layout(location = 4) out vec4 _lighting; // emissive void packDeferredFragmentSky(vec4 prevPositionCS, vec3 color, vec3 normal) { _albedoMetallic = vec4(color, 0.6f); diff --git a/libraries/graphics/src/graphics/skybox.slv b/libraries/graphics/src/graphics/skybox.slv index 991f0c938f..5c60cf73a9 100755 --- a/libraries/graphics/src/graphics/skybox.slv +++ b/libraries/graphics/src/graphics/skybox.slv @@ -5,7 +5,7 @@ // // Created by Sam Gateau on 5/5/2015. // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -36,9 +36,10 @@ void main(void) { <$transformEyeToWorldDir(cam, eyeDir, _normal)$> <@if not HIFI_USE_FORWARD@> + // FIXME: this is probably wrong _prevPositionCS = cam._previousProjectionViewUntranslated * (cam._viewInverse * (cam._projectionInverse * vec4(clipDir, 1.0))); <@endif@> - + // Position is supposed to come in clip space gl_Position = vec4(clipDir, 1.0); diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index f96dd7f4ed..fb3223d73a 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 9/21/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.h b/libraries/procedural/src/procedural/ProceduralSkybox.h index 122eeafbf9..5aef9171d9 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.h +++ b/libraries/procedural/src/procedural/ProceduralSkybox.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 9/21/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/procedural/src/procedural/proceduralSkybox.slf b/libraries/procedural/src/procedural/proceduralSkybox.slf index b6c68faa5c..3070d5d643 100644 --- a/libraries/procedural/src/procedural/proceduralSkybox.slf +++ b/libraries/procedural/src/procedural/proceduralSkybox.slf @@ -6,7 +6,7 @@ // // Created by Sam Gateau on 5/5/2015. // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 444431fabf..01b17e5a9d 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -4,7 +4,7 @@ // // Created by Raffi Bedikian on 8/30/15 // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -45,18 +45,18 @@ void AntialiasingSetupConfig::setIndex(int current) { emit dirty(); } -void AntialiasingSetupConfig::setState(int state) { - _state = (state) % 3; +void AntialiasingSetupConfig::setState(State state) { + _state = (State)((int)state % (int)State::STATE_COUNT); switch (_state) { - case 0: { + case State::NONE: { none(); break; } - case 1: { + case State::PAUSE: { pause(); break; } - case 2: + case State::PLAY: default: { play(); break; @@ -65,26 +65,6 @@ void AntialiasingSetupConfig::setState(int state) { emit dirty(); } -int AntialiasingSetupConfig::cycleStopPauseRun() { - _state = (_state + 1) % 3; - switch (_state) { - case 0: { - return none(); - break; - } - case 1: { - return pause(); - break; - } - case 2: - default: { - return play(); - break; - } - } - return _state; -} - int AntialiasingSetupConfig::prev() { setIndex(_index - 1); return _index; @@ -95,32 +75,32 @@ int AntialiasingSetupConfig::next() { return _index; } -int AntialiasingSetupConfig::none() { - _state = 0; +AntialiasingSetupConfig::State AntialiasingSetupConfig::none() { + _state = State::NONE; stop = true; freeze = false; setIndex(-1); return _state; } -int AntialiasingSetupConfig::pause() { - _state = 1; +AntialiasingSetupConfig::State AntialiasingSetupConfig::pause() { + _state = State::PAUSE; stop = false; freeze = true; setIndex(0); return _state; } -int AntialiasingSetupConfig::play() { - _state = 2; +AntialiasingSetupConfig::State AntialiasingSetupConfig::play() { + _state = State::PLAY; stop = false; freeze = false; setIndex(0); return _state; } -void AntialiasingSetupConfig::setAAMode(int mode) { - this->mode = glm::clamp(mode, 0, (int)AntialiasingSetupConfig::MODE_COUNT); +void AntialiasingSetupConfig::setAAMode(Mode mode) { + this->mode = (Mode)glm::clamp((int)mode, 0, (int)AntialiasingSetupConfig::Mode::MODE_COUNT); emit dirty(); } @@ -172,7 +152,7 @@ Antialiasing::~Antialiasing() { _antialiasingBuffers.clear(); } -const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { +const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { if (!_antialiasingPipeline) { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::taa); gpu::StatePointer state = std::make_shared(); diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index c2c4f53057..87b90cba02 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -4,7 +4,6 @@ // // Created by Raffi Bedikian on 8/30/15 // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. // Copyright 2022-2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. @@ -28,8 +27,8 @@ class AntialiasingSetupConfig : public render::Job::Config { Q_PROPERTY(bool freeze MEMBER freeze NOTIFY dirty) Q_PROPERTY(bool stop MEMBER stop NOTIFY dirty) Q_PROPERTY(int index READ getIndex NOTIFY dirty) - Q_PROPERTY(int state READ getState WRITE setState NOTIFY dirty) - Q_PROPERTY(int mode READ getAAMode WRITE setAAMode NOTIFY dirty) + Q_PROPERTY(State state READ getState WRITE setState NOTIFY dirty) + Q_PROPERTY(Mode mode READ getAAMode WRITE setAAMode NOTIFY dirty) public: AntialiasingSetupConfig() : render::Job::Config(true) {} @@ -43,12 +42,12 @@ public: * * * - * + * * *
0NONEAntialiasing is disabled.
1TAATemporal Antialiasing.
2FXAAFXAA.
3MODE_COUNTInducates number of antialiasing modes
3MODE_COUNTIndicates number of antialiasing modes
* @typedef {number} AntialiasingMode */ - enum Mode { + enum class Mode { NONE = 0, TAA, FXAA, @@ -56,33 +55,55 @@ public: }; Q_ENUM(Mode) // Stored as signed int. + /*@jsdoc + *TAA Antialiasing state. + * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0NONETAA is disabled.
1PAUSETAA jitter is paused.
2PLAYTAA jitter is playing.
3STATE_COUNTIndicates number of antialiasing states
+ * @typedef {number} AntialiasingState + */ + enum class State + { + NONE = 0, + PAUSE, + PLAY, + STATE_COUNT + }; + Q_ENUM(State) + float scale { 0.75f }; bool stop { false }; bool freeze { false }; - int mode { TAA }; - - void setIndex(int current); - void setState(int state); + Mode mode { Mode::TAA }; public slots: - int cycleStopPauseRun(); int prev(); int next(); - int none(); - int pause(); - int play(); + State none(); + State pause(); + State play(); int getIndex() const { return _index; } - int getState() const { return _state; } + void setIndex(int current); - void setAAMode(int mode); - int getAAMode() const { return mode; } + State getState() const { return _state; } + void setState(State state); + + Mode getAAMode() const { return mode; } + void setAAMode(Mode mode); signals: void dirty(); private: - int _state { 0 }; + State _state { State::PLAY }; int _index { 0 }; }; @@ -91,7 +112,7 @@ class AntialiasingSetup { public: using Config = AntialiasingSetupConfig; - using Output = int; + using Output = AntialiasingSetupConfig::Mode; using JobModel = render::Job::ModelO; AntialiasingSetup(); @@ -106,7 +127,7 @@ private: int _freezedSampleIndex { 0 }; bool _isStopped { false }; bool _isFrozen { false }; - int _mode { AntialiasingSetupConfig::Mode::TAA }; + AntialiasingSetupConfig::Mode _mode{ AntialiasingSetupConfig::Mode::TAA }; }; @@ -203,7 +224,7 @@ using TAAParamsBuffer = gpu::StructBuffer; class Antialiasing { public: - using Inputs = render::VaryingSet4; + using Inputs = render::VaryingSet4; using Outputs = gpu::TexturePointer; using Config = AntialiasingConfig; using JobModel = render::Job::ModelIO; diff --git a/libraries/render-utils/src/BackgroundStage.cpp b/libraries/render-utils/src/BackgroundStage.cpp index 38fe0aea3d..89c73a9422 100644 --- a/libraries/render-utils/src/BackgroundStage.cpp +++ b/libraries/render-utils/src/BackgroundStage.cpp @@ -3,7 +3,7 @@ // // Created by Sam Gateau on 5/9/2017. // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index 1aae75e97d..e454a489fa 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -4,6 +4,7 @@ // // Created by Olivier Prat on 09/25/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/BloomThreshold.slf b/libraries/render-utils/src/BloomThreshold.slf index 1a12f070ba..d8e4153198 100644 --- a/libraries/render-utils/src/BloomThreshold.slf +++ b/libraries/render-utils/src/BloomThreshold.slf @@ -5,6 +5,7 @@ // // Created by Olivier Prat on 09/26/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index 2238b4be7f..89365e4099 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -4,8 +4,7 @@ // // Created by Andrew Meadows 2017.01.17 // Copyright 2017 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. -// Copyright 2023 Overte e.V. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index c0ad71fcfb..f1d6f74df6 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -4,7 +4,7 @@ // // Created by Clement on 12/3/15. // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index 4e4104f221..bdc97ec104 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -4,6 +4,7 @@ // // Created by Clement on 12/3/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index 3198594825..8d30bd6b18 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 5/4/16. // Copyright 2013 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/DeferredBufferWrite.slh b/libraries/render-utils/src/DeferredBufferWrite.slh index 99e62153fa..9213bc2fcd 100644 --- a/libraries/render-utils/src/DeferredBufferWrite.slh +++ b/libraries/render-utils/src/DeferredBufferWrite.slh @@ -4,7 +4,7 @@ // // Created by Sam Gateau on 1/12/15. // Copyright 2013 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -16,11 +16,11 @@ <@include DeferredBufferWrite_shared.slh@> // Must match layout in skybox.slh -layout(location = DEFERRED_COLOR_SLOT) out vec4 _albedoMetallic; // albedo / metallic -layout(location = DEFERRED_NORMAL_SLOT) out vec4 _normalRoughness; // normal / roughness +layout(location = DEFERRED_COLOR_SLOT) out vec4 _albedoMetallic; // albedo / metallic +layout(location = DEFERRED_NORMAL_SLOT) out vec4 _normalRoughness; // normal / roughness layout(location = DEFERRED_SPECULAR_SLOT) out vec4 _scatteringEmissiveOcclusion; // scattering / emissive / occlusion -layout(location = DEFERRED_VELOCITY_SLOT) out vec4 _velocity; // velocity -layout(location = DEFERRED_LIGHTING_SLOT) out vec4 _lighting; // emissive +layout(location = DEFERRED_VELOCITY_SLOT) out vec4 _velocity; // velocity +layout(location = DEFERRED_LIGHTING_SLOT) out vec4 _lighting; // emissive // the alpha threshold const float alphaThreshold = 0.5; diff --git a/libraries/render-utils/src/DeferredBufferWrite_shared.slh b/libraries/render-utils/src/DeferredBufferWrite_shared.slh index a1954d1062..f26bc3ac49 100644 --- a/libraries/render-utils/src/DeferredBufferWrite_shared.slh +++ b/libraries/render-utils/src/DeferredBufferWrite_shared.slh @@ -7,6 +7,6 @@ #define DEFERRED_LIGHTING_SLOT 4 // <@if 1@> - // Trigger Scribe include - // <@endif@> + // Trigger Scribe include + // <@endif@> // diff --git a/libraries/render-utils/src/DeferredFrameTransform.cpp b/libraries/render-utils/src/DeferredFrameTransform.cpp index 5c379a0324..5e6826ae26 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.cpp +++ b/libraries/render-utils/src/DeferredFrameTransform.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau 6/3/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/DeferredFrameTransform.h b/libraries/render-utils/src/DeferredFrameTransform.h index a0f7879199..67179c36d0 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.h +++ b/libraries/render-utils/src/DeferredFrameTransform.h @@ -4,7 +4,7 @@ // // Created by Sam Gateau 6/3/2016. // Copyright 2016 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -33,10 +33,10 @@ public: protected: - // Class describing the uniform buffer with the transform info common to the AO shaders // It s changing every frame -#include "DeferredTransform_shared.slh" +#include "DeferredTransform_shared.slh" + class FrameTransform : public _DeferredFrameTransform { public: FrameTransform() { infos.stereoInfo = glm::vec4(0.0f); } diff --git a/libraries/render-utils/src/DeferredFramebuffer.cpp b/libraries/render-utils/src/DeferredFramebuffer.cpp index c99073a18a..e1104d2cbb 100644 --- a/libraries/render-utils/src/DeferredFramebuffer.cpp +++ b/libraries/render-utils/src/DeferredFramebuffer.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau 7/11/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/DeferredFramebuffer.h b/libraries/render-utils/src/DeferredFramebuffer.h index ed107b677f..272ba42e44 100644 --- a/libraries/render-utils/src/DeferredFramebuffer.h +++ b/libraries/render-utils/src/DeferredFramebuffer.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau 7/11/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index aa2b0a8e7c..8f7ab0cb02 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -4,6 +4,7 @@ // // Created by Andrzej Kapolka on 9/11/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index b354e27f92..19c6f67973 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -4,7 +4,7 @@ // // Created by Sam Gateau on 6/2/16. // Copyright 2016 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/DeferredTransform_shared.slh b/libraries/render-utils/src/DeferredTransform_shared.slh index 2973b627ce..2c3441ffed 100644 --- a/libraries/render-utils/src/DeferredTransform_shared.slh +++ b/libraries/render-utils/src/DeferredTransform_shared.slh @@ -28,6 +28,6 @@ struct _DeferredFrameTransform { }; // <@if 1@> - // Trigger Scribe include - // <@endif@> + // Trigger Scribe include + // <@endif@> // diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index c1cbdbe5e4..f1a99197c6 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -4,6 +4,7 @@ // // Created by Andrzej Kapolka on 6/21/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h index dc95d96394..d5f4a844e7 100644 --- a/libraries/render-utils/src/HighlightEffect.h +++ b/libraries/render-utils/src/HighlightEffect.h @@ -114,7 +114,8 @@ private: class DrawHighlightMask { public: - using Inputs = render::VaryingSet2; using Outputs = glm::ivec4; + using Inputs = render::VaryingSet2; + using Outputs = glm::ivec4; using JobModel = render::Job::ModelIO; DrawHighlightMask(unsigned int highlightIndex, render::ShapePlumberPointer shapePlumber, HighlightSharedParametersPointer parameters, uint transformSlot); @@ -174,7 +175,8 @@ signals: class DebugHighlight { public: - using Inputs = render::VaryingSet2; using Config = DebugHighlightConfig; + using Inputs = render::VaryingSet2; + using Config = DebugHighlightConfig; using JobModel = render::Job::ModelI; DebugHighlight(uint transformSlot); diff --git a/libraries/render-utils/src/Highlight_aabox.slv b/libraries/render-utils/src/Highlight_aabox.slv index e75694d910..6d7e5d5bc2 100644 --- a/libraries/render-utils/src/Highlight_aabox.slv +++ b/libraries/render-utils/src/Highlight_aabox.slv @@ -7,6 +7,7 @@ // // Created by Olivier Prat on 11/02/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/LightClusters.cpp b/libraries/render-utils/src/LightClusters.cpp index a1bd4001fc..301e2dd85a 100644 --- a/libraries/render-utils/src/LightClusters.cpp +++ b/libraries/render-utils/src/LightClusters.cpp @@ -3,6 +3,7 @@ // // Created by Sam Gateau on 9/7/2016. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index c3d40c4954..61e3ea94d1 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -193,12 +193,9 @@ void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const Transform& tra if (_clusterBuffer) { batch.setUniformBuffer(graphics::slot::buffer::Skinning, _clusterBuffer); } - // TODO: I'm not sure of this - //batch.setModelTransform(transform, _previousModelTransform); + batch.setModelTransform(transform, _previousRenderTransform); if (renderMode == Args::RenderMode::DEFAULT_RENDER_MODE || renderMode == Args::RenderMode::MIRROR_RENDER_MODE) { - // TODO: I'm not sure of this - //_prevRenderTransform = _drawTransform; _previousRenderTransform = transform; } } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 9aad004002..3c0d756756 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -70,7 +70,7 @@ public: static bool enableMaterialProceduralShaders; -protected: +protected: mutable Transform _previousRenderTransform; private: diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index cf20bff06b..358d4d9c11 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -1,7 +1,7 @@ // // Created by Bradley Austin Davis on 2018/01/09 // Copyright 2013-2018 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h index e8603c003f..36d8899edb 100644 --- a/libraries/render-utils/src/RenderCommonTask.h +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2018/01/09 // Copyright 2013-2018 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index cfee877bbd..bbd42596a1 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -5,7 +5,7 @@ // // Created by Sam Gateau on 5/29/15. // Copyright 2016 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index ce4348f257..40962c13ac 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -3,7 +3,8 @@ // render-utils/src/ // // Created by Sam Gateau on 5/29/15. -// Copyright 20154 High Fidelity, Inc. +// Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index fe774a100e..22bab2700f 100644 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -5,6 +5,7 @@ // // Created by Zach Pomerantz on 12/13/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/RenderForwardTask.h b/libraries/render-utils/src/RenderForwardTask.h index c3deb002b6..de13be79e0 100644 --- a/libraries/render-utils/src/RenderForwardTask.h +++ b/libraries/render-utils/src/RenderForwardTask.h @@ -4,6 +4,7 @@ // // Created by Zach Pomerantz on 12/13/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/RenderHUDLayerTask.cpp b/libraries/render-utils/src/RenderHUDLayerTask.cpp index b3720a4cf7..e43eb8810d 100644 --- a/libraries/render-utils/src/RenderHUDLayerTask.cpp +++ b/libraries/render-utils/src/RenderHUDLayerTask.cpp @@ -1,7 +1,7 @@ // // Created by Sam Gateau on 2019/06/14 // Copyright 2013-2019 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/RenderHUDLayerTask.h b/libraries/render-utils/src/RenderHUDLayerTask.h index 5e70e73584..de7bb63670 100644 --- a/libraries/render-utils/src/RenderHUDLayerTask.h +++ b/libraries/render-utils/src/RenderHUDLayerTask.h @@ -1,7 +1,7 @@ // // Created by Sam Gateau on 2019/06/14 // Copyright 2013-2019 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 6dbad76e80..1b27cfcee3 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 5/25/2017. // Copyright 2017 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/RenderViewTask.h b/libraries/render-utils/src/RenderViewTask.h index 8911431c88..c00bcdd96c 100644 --- a/libraries/render-utils/src/RenderViewTask.h +++ b/libraries/render-utils/src/RenderViewTask.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 5/25/2017. // Copyright 2017 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index 76ee597592..7d86dc4d6e 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 5/31/17. // Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/StencilMaskPass.h b/libraries/render-utils/src/StencilMaskPass.h index 5599d792ba..527e426be3 100644 --- a/libraries/render-utils/src/StencilMaskPass.h +++ b/libraries/render-utils/src/StencilMaskPass.h @@ -3,7 +3,8 @@ // render-utils/src/ // // Created by Sam Gateau on 5/31/17. -// Copyright 20154 High Fidelity, Inc. +// Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/VelocityWrite.slh b/libraries/render-utils/src/VelocityWrite.slh index 0ef0242787..79f52bb687 100644 --- a/libraries/render-utils/src/VelocityWrite.slh +++ b/libraries/render-utils/src/VelocityWrite.slh @@ -4,7 +4,7 @@ // // Created by Olivier Prat on 10/19/18. // Copyright 2018 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 5e8a8df9d3..b8c282cad1 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 4/4/2017. // Copyright 2017 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/aa_blend.slf b/libraries/render-utils/src/aa_blend.slf index 8391bba221..c42c6373cc 100644 --- a/libraries/render-utils/src/aa_blend.slf +++ b/libraries/render-utils/src/aa_blend.slf @@ -1,10 +1,11 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> -// <$_SCRIBE_FILENAME$> +// <$_SCRIBE_FILENAME$> // Generated on <$_SCRIBE_DATE$> // // Created by Raffi Bedikian on 8/30/15 // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -47,6 +48,6 @@ void main(void) { vec4 maxColor = pixels[4] + vec4(0.5); outFragColor = clamp(pixels[4] + sharpenedPixel * params.sharpenIntensity.x, minColor, maxColor); } else { - outFragColor = texelFetch(colorTexture, ivec2(gl_FragCoord.xy), 0); + outFragColor = texelFetch(colorTexture, ivec2(gl_FragCoord.xy), 0); } } diff --git a/libraries/render-utils/src/deferred_light_limited.slv b/libraries/render-utils/src/deferred_light_limited.slv index 5757e99f78..f99a43771b 100644 --- a/libraries/render-utils/src/deferred_light_limited.slv +++ b/libraries/render-utils/src/deferred_light_limited.slv @@ -7,6 +7,7 @@ // // Created by Sam Gateau on 6/16/16. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/directional_skybox_light.slf b/libraries/render-utils/src/directional_skybox_light.slf index 77935c3e19..c02c29681c 100644 --- a/libraries/render-utils/src/directional_skybox_light.slf +++ b/libraries/render-utils/src/directional_skybox_light.slf @@ -5,6 +5,7 @@ // // Created by Sam Gateau on 5/8/2015. // Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/drawWorkloadProxy.slf b/libraries/render-utils/src/drawWorkloadProxy.slf index 107d1a6c4b..9a05498441 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slf +++ b/libraries/render-utils/src/drawWorkloadProxy.slf @@ -6,6 +6,7 @@ // // Created by Sam Gateau on 6/29/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/drawWorkloadProxy.slv b/libraries/render-utils/src/drawWorkloadProxy.slv index a6e5ab7022..c74d42e8cc 100644 --- a/libraries/render-utils/src/drawWorkloadProxy.slv +++ b/libraries/render-utils/src/drawWorkloadProxy.slv @@ -7,7 +7,7 @@ // // Created by Sam Gateau on 6/29/2015. // Copyright 2015 High Fidelity, Inc. -// Copyright 2023 Overte e.V. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/drawWorkloadView.slf b/libraries/render-utils/src/drawWorkloadView.slf index c838a2f1c8..fb9c430490 100644 --- a/libraries/render-utils/src/drawWorkloadView.slf +++ b/libraries/render-utils/src/drawWorkloadView.slf @@ -7,6 +7,7 @@ // // Created by Sam Gateau on 6/29/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/drawWorkloadView.slv b/libraries/render-utils/src/drawWorkloadView.slv index fe7c3f6692..6320d9ea83 100644 --- a/libraries/render-utils/src/drawWorkloadView.slv +++ b/libraries/render-utils/src/drawWorkloadView.slv @@ -7,6 +7,7 @@ // // Created by Sam Gateau on 6/29/2015. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/grid.slf b/libraries/render-utils/src/grid.slf index ff98e2b56d..fa510b9204 100644 --- a/libraries/render-utils/src/grid.slf +++ b/libraries/render-utils/src/grid.slf @@ -5,7 +5,7 @@ // // Created by Zach Pomerantz on 2/16/2016. // Copyright 2016 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/grid.slv b/libraries/render-utils/src/grid.slv index 1e71893159..cd6143fa50 100644 --- a/libraries/render-utils/src/grid.slv +++ b/libraries/render-utils/src/grid.slv @@ -4,7 +4,7 @@ // Generated on <$_SCRIBE_DATE$> // // Created by HifiExperiments on 7/24/2020 -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -26,7 +26,7 @@ layout(location=GPU_ATTR_COLOR) out vec4 varColor; void main(void) { varTexCoord0 = inTexCoord0.st; varColor = color_sRGBAToLinear(inColor); - + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); diff --git a/libraries/render-utils/src/lightClusters_drawClusterContent.slf b/libraries/render-utils/src/lightClusters_drawClusterContent.slf index b3b81544a7..18602954b8 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterContent.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterContent.slf @@ -5,6 +5,7 @@ // // Created by Sam Gateau on 9/8/2016. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf index 5e3714abee..31a8eac667 100644 --- a/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf +++ b/libraries/render-utils/src/lightClusters_drawClusterFromDepth.slf @@ -5,6 +5,7 @@ // // Created by Sam Gateau on 9/8/2016. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/local_lights_drawOutline.slf b/libraries/render-utils/src/local_lights_drawOutline.slf index f9f78aabb5..1b8773dc63 100644 --- a/libraries/render-utils/src/local_lights_drawOutline.slf +++ b/libraries/render-utils/src/local_lights_drawOutline.slf @@ -7,6 +7,7 @@ // // Created by Sam Gateau on 9/6/2016. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/local_lights_shading.slf b/libraries/render-utils/src/local_lights_shading.slf index 9339d6b7ca..166005b7f0 100644 --- a/libraries/render-utils/src/local_lights_shading.slf +++ b/libraries/render-utils/src/local_lights_shading.slf @@ -7,6 +7,7 @@ // // Created by Sam Gateau on 9/6/2016. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/model.slf b/libraries/render-utils/src/model.slf index 8b3f1b2c23..d69f3af4e8 100644 --- a/libraries/render-utils/src/model.slf +++ b/libraries/render-utils/src/model.slf @@ -47,12 +47,6 @@ <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> - - <@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@> - layout(location=0) out vec4 _fragColor0; - <@else@> - <@include DeferredBufferWrite.slh@> - <@endif@> <@elif HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@> <@include DefaultMaterials.slh@> <@include GlobalLight.slh@> @@ -68,7 +62,9 @@ <@endif@> <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> + <@endif@> + <@if HIFI_USE_FORWARD@> layout(location=0) out vec4 _fragColor0; <@else@> <@include DeferredBufferWrite.slh@> @@ -217,14 +213,25 @@ void main(void) { shade, shadingShift, getMaterialShadingToony(mat), getMaterialMatcap(mat), getMaterialParametricRim(mat), getMaterialParametricRimFresnelPower(mat), getMaterialParametricRimLift(mat), rimTex, getMaterialRimLightingMix(mat), matKey), opacity); - <@if HIFI_USE_FORWARD or HIFI_USE_TRANSLUCENT@> + <@if HIFI_USE_FORWARD@> _fragColor0 = isUnlitEnabled() * vec4(color.rgb <@if HIFI_USE_FADE@> + fadeEmissive <@endif@> , color.a); + <@elif HIFI_USE_TRANSLUCENT@> + packDeferredFragmentTranslucent( + _prevPositionCS, + fragNormalWS, + color.a, + color.rgb + <@if HIFI_USE_FADE@> + + fadeEmissive + <@endif@> + , DEFAULT_ROUGHNESS); <@else@> packDeferredFragmentUnlit( + _prevPositionCS, fragNormalWS, 1.0, color.rgb diff --git a/libraries/render-utils/src/parabola.slf b/libraries/render-utils/src/parabola.slf index cd97a79113..dbf7c85404 100644 --- a/libraries/render-utils/src/parabola.slf +++ b/libraries/render-utils/src/parabola.slf @@ -5,6 +5,7 @@ // // Created by Sam Gondelman on 7/18/2018 // Copyright 2018 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/parabola.slv b/libraries/render-utils/src/parabola.slv index 731c14f122..4dade8f091 100644 --- a/libraries/render-utils/src/parabola.slv +++ b/libraries/render-utils/src/parabola.slv @@ -4,6 +4,7 @@ // // Created by Sam Gondelman on 7/18/2018 // Copyright 2018 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/render-utils/ShaderConstants.h b/libraries/render-utils/src/render-utils/ShaderConstants.h index 5d00736a29..e1b78c9cb5 100644 --- a/libraries/render-utils/src/render-utils/ShaderConstants.h +++ b/libraries/render-utils/src/render-utils/ShaderConstants.h @@ -1,6 +1,7 @@ // // // Created by Bradley Austin Davis on 2015-02-04 +// Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. +// // Based on fragment shader code from // https://github.com/paulhoux/Cinder-Samples/blob/master/TextRendering/include/text/Text.cpp // Distributed under the Apache License, Version 2.0. diff --git a/libraries/render-utils/src/sdf_text3D.slh b/libraries/render-utils/src/sdf_text3D.slh index cbcfd4826c..aff3286eb3 100644 --- a/libraries/render-utils/src/sdf_text3D.slh +++ b/libraries/render-utils/src/sdf_text3D.slh @@ -6,6 +6,7 @@ // // Created by Sam Gondelman on 3/15/19 // Copyright 2019 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv index e110b7bd9e..59892c0c8d 100644 --- a/libraries/render-utils/src/sdf_text3D.slv +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -5,6 +5,8 @@ // vertex shader // // Created by Brad Davis on 10/14/13. +// Copyright 2013 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index 610ac094e5..72c8870a74 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -5,7 +5,7 @@ // // Created by Andrzej Kapolka on 9/15/14. // Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index 42aebf13a1..50dfc558de 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -5,7 +5,7 @@ // // Created by Andrzej Kapolka on 9/15/14. // Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/simple_procedural.slf b/libraries/render-utils/src/simple_procedural.slf index d0cf9a53b2..9f9636e34a 100644 --- a/libraries/render-utils/src/simple_procedural.slf +++ b/libraries/render-utils/src/simple_procedural.slf @@ -5,6 +5,7 @@ // // Created by Andrzej Kapolka on 9/15/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/simple_procedural.slv b/libraries/render-utils/src/simple_procedural.slv index 7c7544fe3d..69b7f14e6d 100644 --- a/libraries/render-utils/src/simple_procedural.slv +++ b/libraries/render-utils/src/simple_procedural.slv @@ -5,6 +5,7 @@ // // Created by Andrzej Kapolka on 9/15/14. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf b/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf index 47bb66b5e3..d5eebefec0 100644 --- a/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf +++ b/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf @@ -6,6 +6,7 @@ // // Created by Sam Gateau on 6/3/16. // Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/taa.slf b/libraries/render-utils/src/taa.slf index 8a4b4aff65..cd40c6b46f 100644 --- a/libraries/render-utils/src/taa.slf +++ b/libraries/render-utils/src/taa.slf @@ -5,7 +5,7 @@ // // Created by Sam Gateau on 8/14/2017 // Copyright 2017 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh index a082ec2531..4df30094df 100644 --- a/libraries/render-utils/src/taa.slh +++ b/libraries/render-utils/src/taa.slh @@ -5,7 +5,7 @@ // // Created by Sam Gateau on 8/17/2017 // Copyright 2017 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -301,7 +301,7 @@ vec2 taa_computePrevFragAndEyeUV(vec2 fragUV, vec2 fragVelocity, out vec2 prevEy bool taa_fetchSourceAndHistory(vec2 fragUV, vec2 fragVelocity, out vec4 sourceColor, out vec4 historyColor) { sourceColor.rgb = texture(sourceMap, fragUV).rgb; - // Store AA intensity in alpha + // Store AA intensity in alpha sourceColor.a = texture(intensityMap, fragUV).r; // If velocity is 0 then don't fetch history, just return the source. This means there is no jitter on this pixel diff --git a/libraries/render-utils/src/taa_blend.slf b/libraries/render-utils/src/taa_blend.slf index 0779109cd9..fa0d94dd78 100644 --- a/libraries/render-utils/src/taa_blend.slf +++ b/libraries/render-utils/src/taa_blend.slf @@ -7,6 +7,7 @@ // // Created by Sam Gateau on 8/17/2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/web_browser.slf b/libraries/render-utils/src/web_browser.slf index 2dc71f12dd..4ba7422d24 100644 --- a/libraries/render-utils/src/web_browser.slf +++ b/libraries/render-utils/src/web_browser.slf @@ -5,6 +5,7 @@ // // Created by Anthony Thibault on 7/25/16. // Copyright 2016 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/web_browser.slv b/libraries/render-utils/src/web_browser.slv index 0d7a82da8a..ee6305ab75 100644 --- a/libraries/render-utils/src/web_browser.slv +++ b/libraries/render-utils/src/web_browser.slv @@ -5,7 +5,7 @@ // // Created by Andrzej Kapolka on 9/15/14. // Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/zone_drawAmbient.slf b/libraries/render-utils/src/zone_drawAmbient.slf index 1792407f3e..daf16a7adb 100644 --- a/libraries/render-utils/src/zone_drawAmbient.slf +++ b/libraries/render-utils/src/zone_drawAmbient.slf @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 5/16/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/zone_drawKeyLight.slf b/libraries/render-utils/src/zone_drawKeyLight.slf index ddd1b1e9c9..0cd6bbdbb1 100644 --- a/libraries/render-utils/src/zone_drawKeyLight.slf +++ b/libraries/render-utils/src/zone_drawKeyLight.slf @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 5/16/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render-utils/src/zone_drawSkybox.slf b/libraries/render-utils/src/zone_drawSkybox.slf index d6d4bedeb4..cd9d478ba7 100644 --- a/libraries/render-utils/src/zone_drawSkybox.slf +++ b/libraries/render-utils/src/zone_drawSkybox.slf @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 5/16/17. // Copyright 2017 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 145c6c681b..41472e8b18 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 1/25/16. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render/src/render/DrawSceneOctree.h b/libraries/render/src/render/DrawSceneOctree.h index eddac42d4d..dc9c105d13 100644 --- a/libraries/render/src/render/DrawSceneOctree.h +++ b/libraries/render/src/render/DrawSceneOctree.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 1/25/16. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -71,7 +72,7 @@ namespace render { const gpu::PipelinePointer getDrawCellBoundsPipeline(); const gpu::PipelinePointer getDrawLODReticlePipeline(); const gpu::PipelinePointer getDrawItemBoundPipeline(); - + private: uint _transformSlot; }; diff --git a/libraries/render/src/render/DrawStatus.cpp b/libraries/render/src/render/DrawStatus.cpp index b3a6ed658f..57ed515629 100644 --- a/libraries/render/src/render/DrawStatus.cpp +++ b/libraries/render/src/render/DrawStatus.cpp @@ -4,6 +4,7 @@ // // Created by Niraj Venkat on 6/29/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 2b5f4c8846..1efe33c497 100644 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -3,7 +3,8 @@ // render/src/render // // Created by Sam Gateau on 5/21/15. -// Copyright 20154 High Fidelity, Inc. +// Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index f948dce1b2..8768c56b4b 100644 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -3,7 +3,8 @@ // render/src/render // // Created by Sam Gateau on 5/21/15. -// Copyright 20154 High Fidelity, Inc. +// Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h index 6de0727a3a..c9544a3fa3 100644 --- a/libraries/render/src/render/Engine.h +++ b/libraries/render/src/render/Engine.h @@ -4,6 +4,7 @@ // // Created by Sam Gateau on 3/3/15. // Copyright 2014 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 467abd9f9a..b4c52ae724 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -4,6 +4,7 @@ // // Created by Zach Pomerantz on 12/31/15. // Copyright 2015 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/render/src/render/drawItemStatus.slv b/libraries/render/src/render/drawItemStatus.slv index a70112ffe6..166b1f7894 100644 --- a/libraries/render/src/render/drawItemStatus.slv +++ b/libraries/render/src/render/drawItemStatus.slv @@ -7,7 +7,7 @@ // // Created by Sam Gateau on 6/30/2015. // Copyright 2015 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/scripts/developer/utilities/render/luci/Antialiasing.qml b/scripts/developer/utilities/render/luci/Antialiasing.qml index 10627fcab4..7c174d53c5 100644 --- a/scripts/developer/utilities/render/luci/Antialiasing.qml +++ b/scripts/developer/utilities/render/luci/Antialiasing.qml @@ -3,7 +3,7 @@ // // Created by Sam Gateau on 8/14/2017 // Copyright 2016 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html diff --git a/scripts/developer/utilities/render/luci/Framebuffer.qml b/scripts/developer/utilities/render/luci/Framebuffer.qml index c1603d646b..7847d2523e 100644 --- a/scripts/developer/utilities/render/luci/Framebuffer.qml +++ b/scripts/developer/utilities/render/luci/Framebuffer.qml @@ -3,6 +3,7 @@ // // Created by Sam Gateau on 4/18/2019 // Copyright 2019 High Fidelity, Inc. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp index d74551a9bc..fda551dfa4 100644 --- a/tools/gpu-frame-player/src/RenderThread.cpp +++ b/tools/gpu-frame-player/src/RenderThread.cpp @@ -1,7 +1,7 @@ // // Created by Bradley Austin Davis on 2018/10/21 // Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html From f2ebca99359a10c5a5ec172772bd90eaa2f847c1 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Thu, 29 Feb 2024 17:39:15 -0800 Subject: [PATCH 03/35] more fixes --- libraries/gpu/src/gpu/Backend.cpp | 2 - libraries/graphics/src/graphics/skybox.slf | 4 +- libraries/graphics/src/graphics/skybox.slh | 17 ++--- .../src/procedural/proceduralSkybox.slf | 4 +- .../render-utils/src/DeferredBufferWrite.slh | 1 - .../src/DeferredLightingEffect.cpp | 7 ++- .../render-utils/src/DeferredLightingEffect.h | 6 +- .../render-utils/src/RenderDeferredTask.cpp | 1 - .../render/src/render/DrawSceneOctree.cpp | 62 ++++++++++--------- libraries/render/src/render/DrawSceneOctree.h | 14 +++-- scripts/developer/utilities/render/lod.qml | 9 ++- 11 files changed, 62 insertions(+), 65 deletions(-) diff --git a/libraries/gpu/src/gpu/Backend.cpp b/libraries/gpu/src/gpu/Backend.cpp index 7859a86824..eb02e15676 100644 --- a/libraries/gpu/src/gpu/Backend.cpp +++ b/libraries/gpu/src/gpu/Backend.cpp @@ -120,8 +120,6 @@ const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived(const viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f); _previousProjectionViewUntranslated = previousProjection * viewUntranslated; - //_previousProjectionViewUntranslated = _projection * viewUntranslated; - _stereoInfo = Vec4(0.0f); return *this; diff --git a/libraries/graphics/src/graphics/skybox.slf b/libraries/graphics/src/graphics/skybox.slf index eb34488d50..9bd130a66f 100755 --- a/libraries/graphics/src/graphics/skybox.slf +++ b/libraries/graphics/src/graphics/skybox.slf @@ -10,8 +10,6 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include graphics/ShaderConstants.h@> - <@include skybox.slh@> <@if HIFI_USE_FORWARD@> @@ -63,7 +61,7 @@ void main(void) { _fragColor.rgb = mix(_fragColor.rgb, hazeColor.rgb, hazeColor.a); } <@else@> - packDeferredFragmentSky(_prevPositionCS, color, normal); + packDeferredFragmentSky(_prevPositionCS, color); <@endif@> } diff --git a/libraries/graphics/src/graphics/skybox.slh b/libraries/graphics/src/graphics/skybox.slh index 6bb8570aad..d6dfc8046e 100644 --- a/libraries/graphics/src/graphics/skybox.slh +++ b/libraries/graphics/src/graphics/skybox.slh @@ -11,6 +11,8 @@ <@if not SKYBOX_SLH@> <@def SKYBOX_SLH@> +<@include graphics/ShaderConstants.h@> + <@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> @@ -45,19 +47,12 @@ vec2 packVelocity(vec4 prevPositionCS) { return deltaUV; } -// Must match layout in DeferredBufferWrite.slh, but only velocity and lighting are used -layout(location = 0) out vec4 _albedoMetallic; // albedo / metallic -layout(location = 1) out vec4 _normalRoughness; // normal / roughness -layout(location = 2) out vec4 _scatteringEmissiveOcclusion; // scattering / emissive / occlusion -layout(location = 3) out vec4 _velocity; // velocity -layout(location = 4) out vec4 _lighting; // emissive +layout(location = 0) out vec4 _lighting; // calculated lighting +layout(location = 1) out vec4 _velocity; // velocity -void packDeferredFragmentSky(vec4 prevPositionCS, vec3 color, vec3 normal) { - _albedoMetallic = vec4(color, 0.6f); - _normalRoughness = vec4(packNormal(normal), 1.0f); - _scatteringEmissiveOcclusion = vec4(0.0f); - _velocity = vec4(packVelocity(prevPositionCS), 0.0f, 0.0f); +void packDeferredFragmentSky(vec4 prevPositionCS, vec3 color) { _lighting = vec4(color, 1.0f); + _velocity = vec4(packVelocity(prevPositionCS), 0.0f, 0.0f); } <@endfunc@> diff --git a/libraries/procedural/src/procedural/proceduralSkybox.slf b/libraries/procedural/src/procedural/proceduralSkybox.slf index 3070d5d643..f7b40d22cc 100644 --- a/libraries/procedural/src/procedural/proceduralSkybox.slf +++ b/libraries/procedural/src/procedural/proceduralSkybox.slf @@ -11,8 +11,6 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include graphics/ShaderConstants.h@> - <@include graphics/skybox.slh@> <$declarePackDeferredFragmentSky()$> @@ -37,5 +35,5 @@ void main(void) { // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline color = pow(color, vec3(2.2)); - packDeferredFragmentSky(_prevPositionCS, color, _normal); + packDeferredFragmentSky(_prevPositionCS, color); } diff --git a/libraries/render-utils/src/DeferredBufferWrite.slh b/libraries/render-utils/src/DeferredBufferWrite.slh index 9213bc2fcd..ff965d9eb9 100644 --- a/libraries/render-utils/src/DeferredBufferWrite.slh +++ b/libraries/render-utils/src/DeferredBufferWrite.slh @@ -15,7 +15,6 @@ <@include DeferredBuffer.slh@> <@include DeferredBufferWrite_shared.slh@> -// Must match layout in skybox.slh layout(location = DEFERRED_COLOR_SLOT) out vec4 _albedoMetallic; // albedo / metallic layout(location = DEFERRED_NORMAL_SLOT) out vec4 _normalRoughness; // normal / roughness layout(location = DEFERRED_SPECULAR_SLOT) out vec4 _scatteringEmissiveOcclusion; // scattering / emissive / occlusion diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 8f7ab0cb02..d9914c63a0 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -287,7 +287,6 @@ void PrepareDeferred::run(const RenderContextPointer& renderContext, const Input outputs.edit0() = _deferredFramebuffer; outputs.edit1() = _deferredFramebuffer->getLightingFramebuffer(); - outputs.edit2() = _deferredFramebuffer->getLightingWithVelocityFramebuffer(); gpu::doInBatch("PrepareDeferred::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); @@ -507,7 +506,7 @@ void RenderDeferredLocals::run(const render::RenderContextPointer& renderContext } } -void RenderDeferredCleanup::run(const render::RenderContextPointer& renderContext) { +void RenderDeferredCleanup::run(const render::RenderContextPointer& renderContext, const DeferredFramebufferPointer& deferredFramebuffer) { auto args = renderContext->args; auto& batch = (*args->_batch); { @@ -532,6 +531,8 @@ void RenderDeferredCleanup::run(const render::RenderContextPointer& renderContex batch.setUniformBuffer(ru::Buffer::LightClusterGrid, nullptr); batch.setUniformBuffer(ru::Buffer::LightClusterContent, nullptr); + // Restore the lighting with velocity framebuffer so that following stages, like drawing the background, can get motion vectors. + batch.setFramebuffer(deferredFramebuffer->getLightingWithVelocityFramebuffer()); } } @@ -572,7 +573,7 @@ void RenderDeferred::run(const RenderContextPointer& renderContext, const Inputs lightsJob.run(renderContext, deferredTransform, deferredFramebuffer, lightingModel, surfaceGeometryFramebuffer, lightClusters); - cleanupJob.run(renderContext); + cleanupJob.run(renderContext, deferredFramebuffer); _gpuTimer->end(batch); }); diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index a83be6e5d2..b90f3309dd 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -78,7 +78,7 @@ class PrepareDeferred { public: // Inputs: primaryFramebuffer and lightingModel using Inputs = render::VaryingSet2 ; - using Outputs = render::VaryingSet3; + using Outputs = render::VaryingSet2; using JobModel = render::Job::ModelIO; @@ -122,8 +122,8 @@ public: class RenderDeferredCleanup { public: using JobModel = render::Job::Model; - - void run(const render::RenderContextPointer& renderContext); + + void run(const render::RenderContextPointer& renderContext, const DeferredFramebufferPointer& deferredFramebuffer); }; using RenderDeferredConfig = render::GPUJobConfig; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index bbd42596a1..0e0244a91a 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -153,7 +153,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto prepareDeferredOutputs = task.addJob("PrepareDeferred", prepareDeferredInputs); const auto deferredFramebuffer = prepareDeferredOutputs.getN(0); const auto lightingFramebuffer = prepareDeferredOutputs.getN(1); - const auto lightingWithVelocityFramebuffer = prepareDeferredOutputs.getN(2); // draw a stencil mask in hidden regions of the framebuffer. task.addJob("PrepareStencil", scaledPrimaryFramebuffer); diff --git a/libraries/render/src/render/DrawSceneOctree.cpp b/libraries/render/src/render/DrawSceneOctree.cpp index 41472e8b18..16b034b6e9 100644 --- a/libraries/render/src/render/DrawSceneOctree.cpp +++ b/libraries/render/src/render/DrawSceneOctree.cpp @@ -61,6 +61,7 @@ const gpu::PipelinePointer DrawSceneOctree::getDrawLODReticlePipeline() { void DrawSceneOctree::configure(const Config& config) { _showVisibleCells = config.showVisibleCells; _showEmptyCells = config.showEmptyCells; + _showLODReticle = config.showLODReticle; } @@ -76,45 +77,48 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS gpu::doInBatch("DrawSceneOctree::run", args->_context, [&](gpu::Batch& batch) { batch.setViewportTransform(args->_viewport); batch.setSavedViewProjectionTransform(_transformSlot); - batch.setModelTransform(Transform()); - // bind the one gpu::Pipeline we need - batch.setPipeline(getDrawCellBoundsPipeline()); - batch.setInputFormat(_cellBoundsFormat); + if (_showEmptyCells || _showVisibleCells) { + batch.setModelTransform(Transform()); - std::vector cellBounds; - auto drawCellBounds = [this, &cellBounds, &scene](const std::vector& cells) { - cellBounds.reserve(cellBounds.size() + cells.size()); - for (const auto& cellID : cells) { - auto cell = scene->getSpatialTree().getConcreteCell(cellID); - auto cellLoc = cell.getlocation(); - glm::ivec4 cellLocation(cellLoc.pos.x, cellLoc.pos.y, cellLoc.pos.z, cellLoc.depth); + // bind the one gpu::Pipeline we need + batch.setPipeline(getDrawCellBoundsPipeline()); + batch.setInputFormat(_cellBoundsFormat); - bool empty = cell.isBrickEmpty() || !cell.hasBrick(); - if (empty) { - if (!_showEmptyCells) { + std::vector cellBounds; + auto drawCellBounds = [this, &cellBounds, &scene](const std::vector& cells) { + cellBounds.reserve(cellBounds.size() + cells.size()); + for (const auto& cellID : cells) { + auto cell = scene->getSpatialTree().getConcreteCell(cellID); + auto cellLoc = cell.getlocation(); + glm::ivec4 cellLocation(cellLoc.pos.x, cellLoc.pos.y, cellLoc.pos.z, cellLoc.depth); + + bool empty = cell.isBrickEmpty() || !cell.hasBrick(); + if (empty) { + if (!_showEmptyCells) { + continue; + } + cellLocation.w *= -1.0; + } else if (!_showVisibleCells) { continue; } - cellLocation.w *= -1.0; - } else if (!empty && !_showVisibleCells) { - continue; + cellBounds.push_back(cellLocation); } - cellBounds.push_back(cellLocation); - } - }; + }; - drawCellBounds(inSelection.cellSelection.insideCells); - drawCellBounds(inSelection.cellSelection.partialCells); - auto size = cellBounds.size() * sizeof(ivec4); - if (size > _cellBoundsBuffer->getSize()) { - _cellBoundsBuffer->resize(size); + drawCellBounds(inSelection.cellSelection.insideCells); + drawCellBounds(inSelection.cellSelection.partialCells); + auto size = cellBounds.size() * sizeof(ivec4); + if (size > _cellBoundsBuffer->getSize()) { + _cellBoundsBuffer->resize(size); + } + _cellBoundsBuffer->setSubData(0, cellBounds); + batch.setInputBuffer(0, _cellBoundsBuffer, 0, sizeof(ivec4)); + batch.drawInstanced((uint32_t)cellBounds.size(), gpu::LINES, 24); } - _cellBoundsBuffer->setSubData(0, cellBounds); - batch.setInputBuffer(0, _cellBoundsBuffer, 0, sizeof(ivec4)); - batch.drawInstanced((uint32_t)cellBounds.size(), gpu::LINES, 24); // Draw the LOD Reticle - { + if (_showLODReticle) { float angle = glm::degrees(getPerspectiveAccuracyHalfAngle(args->_sizeScale, args->_boundaryLevelAdjust)); Transform crosshairModel; crosshairModel.setTranslation(glm::vec3(0.0, 0.0, -1000.0)); diff --git a/libraries/render/src/render/DrawSceneOctree.h b/libraries/render/src/render/DrawSceneOctree.h index dc9c105d13..8b524ef032 100644 --- a/libraries/render/src/render/DrawSceneOctree.h +++ b/libraries/render/src/render/DrawSceneOctree.h @@ -23,6 +23,7 @@ namespace render { Q_OBJECT Q_PROPERTY(bool showVisibleCells READ getShowVisibleCells WRITE setShowVisibleCells NOTIFY dirty()) Q_PROPERTY(bool showEmptyCells READ getShowEmptyCells WRITE setShowEmptyCells NOTIFY dirty()) + Q_PROPERTY(bool showLODReticle READ getShowLODReticle WRITE setShowLODReticle NOTIFY dirty()) Q_PROPERTY(int numAllocatedCells READ getNumAllocatedCells) Q_PROPERTY(int numFreeCells READ getNumFreeCells) @@ -36,15 +37,18 @@ namespace render { int getNumAllocatedCells() const { return numAllocatedCells; } int getNumFreeCells() const { return numFreeCells; } - bool showVisibleCells{ true }; - bool showEmptyCells{ false }; + bool showVisibleCells { false }; + bool showEmptyCells { false }; + bool showLODReticle { false }; bool getShowVisibleCells() { return showVisibleCells; } bool getShowEmptyCells() { return showEmptyCells; } + bool getShowLODReticle() { return showLODReticle; } public slots: void setShowVisibleCells(bool show) { showVisibleCells = show; emit dirty(); } void setShowEmptyCells(bool show) { showEmptyCells = show; emit dirty(); } + void setShowLODReticle(bool show) { showLODReticle = show; emit dirty(); } signals: void dirty(); @@ -57,8 +61,10 @@ namespace render { gpu::BufferPointer _cellBoundsBuffer; gpu::Stream::FormatPointer _cellBoundsFormat; - bool _showVisibleCells; // initialized by Config - bool _showEmptyCells; // initialized by Config + // initialized by Config + bool _showVisibleCells; + bool _showEmptyCells; + bool _showLODReticle; public: using Config = DrawSceneOctreeConfig; diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 6497fb967e..3d4cafdd38 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -22,8 +22,7 @@ Item { anchors.fill:parent Component.onCompleted: { - Render.getConfig("RenderMainView.DrawSceneOctree").showVisibleCells = false - Render.getConfig("RenderMainView.DrawSceneOctree").showEmptyCells = false + Render.getConfig("RenderMainView.DrawSceneOctree").enabled = true } Component.onDestruction: { @@ -38,9 +37,9 @@ Item { HifiControls.CheckBox { boxSize: 20 - text: "Show LOD Reticule" - checked: Render.getConfig("RenderMainView.DrawSceneOctree").enabled - onCheckedChanged: { Render.getConfig("RenderMainView.DrawSceneOctree").enabled = checked } + text: "Show LOD Reticle" + checked: Render.getConfig("RenderMainView.DrawSceneOctree").showLODReticle + onCheckedChanged: { Render.getConfig("RenderMainView.DrawSceneOctree").showLODReticle = checked } } RichSlider { From c4cf2ba483eeed2dd1e2f9ec88018cd37dcf0f1f Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Wed, 30 Oct 2024 15:59:12 -0700 Subject: [PATCH 04/35] cleanup + fixing mirrors --- libraries/gpu/src/gpu/Batch.cpp | 6 +-- libraries/gpu/src/gpu/Batch.h | 12 +++-- .../render-utils/src/DeferredFrameTransform.h | 4 +- .../render-utils/src/RenderCommonTask.cpp | 54 ++++++++++++++----- libraries/render-utils/src/RenderCommonTask.h | 6 +-- .../render-utils/src/RenderDeferredTask.cpp | 2 +- .../render-utils/src/RenderDeferredTask.h | 2 +- .../render-utils/src/RenderForwardTask.cpp | 2 +- .../render-utils/src/RenderForwardTask.h | 2 +- libraries/render-utils/src/RenderViewTask.cpp | 6 +-- libraries/render-utils/src/RenderViewTask.h | 4 +- libraries/render/src/render/Engine.h | 2 +- 12 files changed, 68 insertions(+), 34 deletions(-) diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index a08fcfc054..0e1da3c4f0 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -302,7 +302,7 @@ void Batch::setDepthRangeTransform(float nearDepth, float farDepth) { _params.emplace_back(nearDepth); } -void Batch::saveViewProjectionTransform(uint32 saveSlot) { +void Batch::saveViewProjectionTransform(uint saveSlot) { ADD_COMMAND(saveViewProjectionTransform); if (saveSlot >= MAX_TRANSFORM_SAVE_SLOT_COUNT) { qCWarning(gpulogging) << "Transform save slot" << saveSlot << "exceeds max save slot count of" << MAX_TRANSFORM_SAVE_SLOT_COUNT; @@ -310,7 +310,7 @@ void Batch::saveViewProjectionTransform(uint32 saveSlot) { _params.emplace_back(saveSlot); } -void Batch::setSavedViewProjectionTransform(uint32 saveSlot) { +void Batch::setSavedViewProjectionTransform(uint saveSlot) { ADD_COMMAND(setSavedViewProjectionTransform); if (saveSlot >= MAX_TRANSFORM_SAVE_SLOT_COUNT) { qCWarning(gpulogging) << "Transform save slot" << saveSlot << "exceeds max save slot count of" @@ -319,7 +319,7 @@ void Batch::setSavedViewProjectionTransform(uint32 saveSlot) { _params.emplace_back(saveSlot); } -void Batch::copySavedViewProjectionTransformToBuffer(uint32 saveSlot, const BufferPointer& buffer, Offset offset) { +void Batch::copySavedViewProjectionTransformToBuffer(uint saveSlot, const BufferPointer& buffer, Offset offset) { ADD_COMMAND(copySavedViewProjectionTransformToBuffer); if (saveSlot >= MAX_TRANSFORM_SAVE_SLOT_COUNT) { qCWarning(gpulogging) << "Transform save slot" << saveSlot << "exceeds max save slot count of" diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 8a27f86ab5..1e8a365b7a 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -44,7 +44,11 @@ public: typedef Stream::Slot Slot; enum { - MAX_TRANSFORM_SAVE_SLOT_COUNT = 6 + // This is tied to RenderMirrorTask::MAX_MIRROR_DEPTH and RenderMirrorTask::MAX_MIRRORS_PER_LEVEL + // We have 1 view at mirror depth 0, 3 more at mirror depth 1, 9 more at mirror depth 2, and 27 more at mirror depth 3 + // For each view, we have one slot for the background and one for the primary view, and that's all repeated for the secondary camera + // So this is 2 slots/view/camera * 2 cameras * (1 + 3 + 9 + 27) views + MAX_TRANSFORM_SAVE_SLOT_COUNT = 160 }; class DrawCallInfo { @@ -192,9 +196,9 @@ public: void setViewportTransform(const Vec4i& viewport); void setDepthRangeTransform(float nearDepth, float farDepth); - void saveViewProjectionTransform(uint32 saveSlot); - void setSavedViewProjectionTransform(uint32 saveSlot); - void copySavedViewProjectionTransformToBuffer(uint32 saveSlot, const BufferPointer& buffer, Offset offset); + void saveViewProjectionTransform(uint saveSlot); + void setSavedViewProjectionTransform(uint saveSlot); + void copySavedViewProjectionTransformToBuffer(uint saveSlot, const BufferPointer& buffer, Offset offset); // Pipeline Stage void setPipeline(const PipelinePointer& pipeline); diff --git a/libraries/render-utils/src/DeferredFrameTransform.h b/libraries/render-utils/src/DeferredFrameTransform.h index 67179c36d0..8b8ce376f9 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.h +++ b/libraries/render-utils/src/DeferredFrameTransform.h @@ -53,12 +53,12 @@ public: using Output = DeferredFrameTransformPointer; using JobModel = render::Job::ModelO; - GenerateDeferredFrameTransform(unsigned int transformSlot) : _transformSlot{ transformSlot } {} + GenerateDeferredFrameTransform(uint transformSlot) : _transformSlot(transformSlot) {} void run(const render::RenderContextPointer& renderContext, Output& frameTransform); private: - unsigned int _transformSlot; + uint _transformSlot; }; #endif // hifi_DeferredFrameTransform_h diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index 2eeb7d3b9e..4406fca4ca 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -52,7 +52,7 @@ void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, co config->setGPUBatchRunTime(timer->getGPUAverage(), timer->getBatchAverage()); } -DrawLayered3D::DrawLayered3D(const render::ShapePlumberPointer& shapePlumber, bool opaque, bool jitter, unsigned int transformSlot) : +DrawLayered3D::DrawLayered3D(const render::ShapePlumberPointer& shapePlumber, bool opaque, bool jitter, uint transformSlot) : _shapePlumber(shapePlumber), _transformSlot(transformSlot), _opaquePass(opaque), @@ -343,7 +343,7 @@ public: using Inputs = SetupMirrorTask::Outputs; using JobModel = render::Job::ModelI; - DrawMirrorTask() { + DrawMirrorTask(uint transformSlot) : _transformSlot(transformSlot) { static std::once_flag once; std::call_once(once, [this] { auto state = std::make_shared(); @@ -379,13 +379,7 @@ public: batch.setViewportTransform(args->_viewport); batch.setStateScissorRect(args->_viewport); - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); + batch.setSavedViewProjectionTransform(_transformSlot); batch.setResourceTexture(gr::Texture::MaterialMirror, args->_blitFramebuffer->getRenderBuffer(0)); @@ -406,20 +400,54 @@ public: private: static ShapePlumberPointer _forwardPipelines; static ShapePlumberPointer _deferredPipelines; + + uint _transformSlot; }; ShapePlumberPointer DrawMirrorTask::_forwardPipelines = std::make_shared(); ShapePlumberPointer DrawMirrorTask::_deferredPipelines = std::make_shared(); void RenderMirrorTask::build(JobModel& task, const render::Varying& inputs, render::Varying& output, size_t mirrorIndex, render::CullFunctor cullFunctor, - uint8_t transformOffset, size_t depth) { + uint transformOffset,size_t depth) { size_t nextDepth = depth + 1; const auto setupOutput = task.addJob("SetupMirror" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), inputs, mirrorIndex, nextDepth); - task.addJob("RenderMirrorView" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), cullFunctor, render::ItemKey::TAG_BITS_1, - render::ItemKey::TAG_BITS_1, (RenderViewTask::TransformOffset) transformOffset, nextDepth); + // Our primary view starts at transformOffset 0, and the secondary camera starts at transformOffset 2 + // Our primary mirror views thus start after the secondary camera, at transformOffset 4, and the secondary + // camera mirror views start after all of the primary camera mirror views, at 4 + NUM_MAIN_MIRROR_SLOTS + static uint NUM_MAIN_MIRROR_SLOTS = 0; + static std::once_flag once; + std::call_once(once, [] { + for (size_t mirrorDepth = 0; mirrorDepth < MAX_MIRROR_DEPTH; mirrorDepth++) { + NUM_MAIN_MIRROR_SLOTS += pow(MAX_MIRRORS_PER_LEVEL, mirrorDepth + 1); + } + NUM_MAIN_MIRROR_SLOTS *= 2; + }); - task.addJob("DrawMirrorTask" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), setupOutput); + uint mirrorOffset; + if (transformOffset == RenderViewTask::TransformOffset::MAIN_VIEW) { + mirrorOffset = RenderViewTask::TransformOffset::FIRST_MIRROR_VIEW - 2; + } else if (transformOffset == RenderViewTask::TransformOffset::SECONDARY_VIEW) { + mirrorOffset = RenderViewTask::TransformOffset::FIRST_MIRROR_VIEW + NUM_MAIN_MIRROR_SLOTS - 2; + } else { + mirrorOffset = transformOffset; + } + + // To calculate our transformSlot, we take the transformSlot of our parent and add numSubSlots (the number of slots + // taken up by a sub-tree starting at this depth) per preceding mirrorIndex + uint numSubSlots = 0; + for (size_t mirrorDepth = depth; mirrorDepth < MAX_MIRROR_DEPTH; mirrorDepth++) { + numSubSlots += pow(MAX_MIRRORS_PER_LEVEL, mirrorDepth + 1 - nextDepth); + } + numSubSlots *= 2; + + mirrorOffset += 2 + numSubSlots * (uint)mirrorIndex; + + task.addJob("RenderMirrorView" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), cullFunctor, render::ItemKey::TAG_BITS_1, + render::ItemKey::TAG_BITS_1, (RenderViewTask::TransformOffset) mirrorOffset, nextDepth); + + task.addJob("DrawMirrorTask" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), setupOutput, + render::RenderEngine::TS_MAIN_VIEW + transformOffset); } void RenderSimulateTask::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h index 57b65f882c..452c967cdb 100644 --- a/libraries/render-utils/src/RenderCommonTask.h +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -69,7 +69,7 @@ public: using Config = DrawLayered3DConfig; using JobModel = render::Job::ModelI; - DrawLayered3D(const render::ShapePlumberPointer& shapePlumber, bool opaque, bool jitter, unsigned int transformSlot); + DrawLayered3D(const render::ShapePlumberPointer& shapePlumber, bool opaque, bool jitter, uint transformSlot); void configure(const Config& config) { _maxDrawn = config.maxDrawn; } void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); @@ -165,9 +165,9 @@ public: RenderMirrorTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& output, size_t mirrorIndex, render::CullFunctor cullFunctor, - uint8_t transformOffset, size_t depth); + void build(JobModel& task, const render::Varying& inputs, render::Varying& output, size_t mirrorIndex, render::CullFunctor cullFunctor, uint transformOffset, size_t depth); + // NOTE: if these change, must also change Batch::MAX_TRANSFORM_SAVE_SLOT_COUNT static const size_t MAX_MIRROR_DEPTH { 3 }; static const size_t MAX_MIRRORS_PER_LEVEL { 3 }; }; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index b758a74d6d..001ff8106d 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -94,7 +94,7 @@ void RenderDeferredTask::configure(const Config& config) { preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale); } -void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset, render::CullFunctor cullFunctor, size_t depth) { +void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint transformOffset, size_t depth) { // Prepare the ShapePipelines ShapePlumberPointer shapePlumberDeferred = std::make_shared(); initDeferredPipelines(*shapePlumberDeferred, FadeEffect::getBatchSetter(), FadeEffect::getItemUniformSetter()); diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 2b52f164a9..5ecca97306 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -145,7 +145,7 @@ public: using JobModel = render::Task::ModelI; void configure(const Config& config); - void build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset, render::CullFunctor cullFunctor, size_t depth); + void build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint transformOffset, size_t depth); }; diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index 745b0148b6..ab4edbd765 100644 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -68,7 +68,7 @@ void RenderForwardTask::configure(const Config& config) { preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale); } -void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset, render::CullFunctor cullFunctor, size_t depth) { +void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint transformOffset, size_t depth) { task.addJob("SetRenderMethodTask", render::Args::FORWARD); // Prepare the ShapePipelines diff --git a/libraries/render-utils/src/RenderForwardTask.h b/libraries/render-utils/src/RenderForwardTask.h index 7cd7f6cd5f..ef7b6f5a92 100644 --- a/libraries/render-utils/src/RenderForwardTask.h +++ b/libraries/render-utils/src/RenderForwardTask.h @@ -37,7 +37,7 @@ public: RenderForwardTask() {} void configure(const Config& config); - void build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset, render::CullFunctor cullFunctor, size_t depth); + void build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint transformOffset, size_t depth); }; diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index ca0bba93e0..5c625df9fc 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -30,14 +30,14 @@ void RenderShadowsAndDeferredTask::build(JobModel& task, const render::Varying& const auto shadowTaskOut = task.addJob("RenderShadowTask", shadowTaskIn, cullFunctor, tagBits, tagMask); const auto renderDeferredInput = RenderDeferredTask::Input(items, lightingModel, lightingStageFramesAndZones, shadowTaskOut).asVarying(); - task.addJob("RenderDeferredTask", renderDeferredInput, transformOffset, cullFunctor, depth); + task.addJob("RenderDeferredTask", renderDeferredInput, cullFunctor, transformOffset, depth); } void DeferredForwardSwitchJob::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask, uint8_t transformOffset, size_t depth) { task.addBranch("RenderShadowsAndDeferredTask", 0, input, cullFunctor, tagBits, tagMask, transformOffset, depth); - task.addBranch("RenderForwardTask", 1, input, transformOffset, cullFunctor, depth); + task.addBranch("RenderForwardTask", 1, input, cullFunctor, transformOffset, depth); } void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask, @@ -55,6 +55,6 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render: task.addJob("DeferredForwardSwitch", deferredForwardIn, cullFunctor, tagBits, tagMask, transformOffset, depth); #else const auto renderInput = RenderForwardTask::Input(items, lightingModel, lightingStageFramesAndZones).asVarying(); - task.addJob("RenderForwardTask", renderInput, transformOffset); + task.addJob("RenderForwardTask", renderInput, cullFunctor, transformOffset, depth); #endif } diff --git a/libraries/render-utils/src/RenderViewTask.h b/libraries/render-utils/src/RenderViewTask.h index 539d9be8fb..956af8d3df 100644 --- a/libraries/render-utils/src/RenderViewTask.h +++ b/libraries/render-utils/src/RenderViewTask.h @@ -48,9 +48,11 @@ public: RenderViewTask() {} + // each view uses 1 transform for the main view, and one for the background, so these need to be increments of 2 enum TransformOffset: uint8_t { MAIN_VIEW = 0, - SECONDARY_VIEW = 2 // each view uses 1 transform for the main view, and one for the background, so these need to be increments of 2 + SECONDARY_VIEW = 2, + FIRST_MIRROR_VIEW = 4 }; void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h index c9544a3fa3..e769297bbf 100644 --- a/libraries/render/src/render/Engine.h +++ b/libraries/render/src/render/Engine.h @@ -86,7 +86,7 @@ namespace render { class RenderEngine : public Engine { public: - enum TransformSlot { + enum TransformSlot : uint8_t { TS_MAIN_VIEW = 0, TS_BACKGROUND_VIEW }; From 9009ba3681d2e4a2b524d070d7b12c65cd8ef1be Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Wed, 30 Oct 2024 16:19:47 -0700 Subject: [PATCH 05/35] wrong config --- interface/src/scripting/RenderScriptingInterface.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index 1e8b1145b0..221a7bf308 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -406,23 +406,19 @@ void RenderScriptingInterface::forceViewportResolutionScale(float scale) { auto renderConfig = qApp->getRenderEngine()->getConfiguration(); assert(renderConfig); auto deferredView = renderConfig->getConfig("RenderMainView.RenderDeferredTask"); - // mainView can be null if we're rendering in forward mode if (deferredView) { deferredView->setProperty("resolutionScale", scale); } auto forwardView = renderConfig->getConfig("RenderMainView.RenderForwardTask"); - // mainView can be null if we're rendering in forward mode if (forwardView) { forwardView->setProperty("resolutionScale", scale); } auto deferredSecondView = renderConfig->getConfig("RenderSecondView.RenderDeferredTask"); - // mainView can be null if we're rendering in forward mode if (deferredSecondView) { deferredSecondView->setProperty("resolutionScale", scale); } - auto forwardSecondView = renderConfig->getConfig("RenderMainView.RenderForwardTask"); - // mainView can be null if we're rendering in forward mode + auto forwardSecondView = renderConfig->getConfig("RenderSecondView.RenderForwardTask"); if (forwardSecondView) { forwardSecondView->setProperty("resolutionScale", scale); } From 8e9fab9f98ffe06452987e554168864c9e380c4f Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sun, 17 Nov 2024 22:26:35 -0800 Subject: [PATCH 06/35] don't update jitter in mirror views --- libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index aea872d15d..931d6ad4e5 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -1004,8 +1004,6 @@ void GLBackend::updatePresentFrame(const Mat4& correction, bool primary) { _transform._presentFrame.correction = correction; _transform._presentFrame.correctionInverse = glm::inverse(correction); - _transform._projectionJitter._currentSampleIndex++; - // Update previous views of saved transforms for (auto& viewProjState : _transform._savedTransforms) { viewProjState._state._previousCorrectedView = viewProjState._state._correctedView; @@ -1013,6 +1011,7 @@ void GLBackend::updatePresentFrame(const Mat4& correction, bool primary) { } if (primary) { + _transform._projectionJitter._currentSampleIndex++; _transform._presentFrame.unflippedCorrection = _transform._presentFrame.correction; quat flippedRotation = glm::quat_cast(_transform._presentFrame.unflippedCorrection); flippedRotation.y *= -1.0f; From 58cfcb18d6832457626be7ae9bd31cb8b2822da5 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Thu, 19 Dec 2024 22:12:32 -0500 Subject: [PATCH 07/35] Add sortOrder. Add sortOrder to bring the app on the first page of the tablet (position 8) --- scripts/communityScripts/armored-chat/armored_chat.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/communityScripts/armored-chat/armored_chat.js b/scripts/communityScripts/armored-chat/armored_chat.js index 779dc3ff54..ae46f4d8f3 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.js +++ b/scripts/communityScripts/armored-chat/armored_chat.js @@ -1,7 +1,7 @@ // // armored_chat.js // -// Created by Armored Dragon, 2024. +// Created by Armored Dragon, May 17th, 2024. // Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. @@ -47,6 +47,7 @@ icon: Script.resolvePath("./img/icon_white.png"), activeIcon: Script.resolvePath("./img/icon_black.png"), text: "CHAT", + sortOrder: 8, isActive: appIsVisible, }); From 18c7320879299962d506b2521f86aa7a1c920c3a Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Fri, 1 Nov 2024 00:54:32 -0700 Subject: [PATCH 08/35] 3rd person camera clipping option --- .../dialogs/graphics/GraphicsSettings.qml | 30 ++++++++++++ interface/src/Application.cpp | 49 +++++++++++++++++-- interface/src/Application.h | 7 +++ interface/src/CameraRootTransformNode.cpp | 48 ++++++++++++++++++ interface/src/CameraRootTransformNode.h | 20 ++++++++ .../src/raypick/PickScriptingInterface.cpp | 4 ++ .../scripting/RenderScriptingInterface.cpp | 7 +++ .../src/scripting/RenderScriptingInterface.h | 18 ++++++- interface/src/ui/PreferencesDialog.cpp | 7 ++- scripts/system/create/edit.js | 5 ++ 10 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 interface/src/CameraRootTransformNode.cpp create mode 100644 interface/src/CameraRootTransformNode.h diff --git a/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml b/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml index a928b1379f..a0804957be 100644 --- a/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml +++ b/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml @@ -658,6 +658,36 @@ Flickable { } } } + Item { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 35 + Layout.topMargin: 16 + + HifiStylesUit.RalewayRegular { + id: enableCameraClippingHeader + text: "3rd Person Camera Clipping" + anchors.left: parent.left + anchors.top: parent.top + width: 200 + height: parent.height + size: 16 + color: "#FFFFFF" + } + + HifiControlsUit.CheckBox { + id: enableCameraClipping + checked: Render.cameraClippingEnabled + boxSize: 16 + spacing: -1 + colorScheme: hifi.colorSchemes.dark + anchors.left: enableCameraClippingHeader.right + anchors.leftMargin: 20 + anchors.top: parent.top + onCheckedChanged: { + Render.cameraClippingEnabled = enableCameraClipping.checked; + } + } + } } ColumnLayout { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index db8a5dc0cf..c5951a2aff 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -241,6 +241,7 @@ #include #include #include +#include #include "ResourceRequestObserver.h" @@ -1004,6 +1005,7 @@ Application::Application( _previousSessionCrashed(false), //setupEssentials(parser, false)), _previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION), _fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES), + _cameraClippingEnabled("cameraClippingEnabled", false), _hmdTabletScale("hmdTabletScale", DEFAULT_HMD_TABLET_SCALE_PERCENT), _desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT), _firstRun(Settings::firstRun, true), @@ -2499,6 +2501,17 @@ void Application::initialize(const QCommandLineParser &parser) { DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); + // Setup the camera clipping ray pick + { + _prevCameraClippingEnabled = _cameraClippingEnabled.get(); + auto cameraRayPick = std::make_shared(Vectors::ZERO, -Vectors::UP, + PickFilter(PickScriptingInterface::getPickEntities() | + PickScriptingInterface::getPickLocalEntities()), + MyAvatar::ZOOM_MAX, 0.0f, _prevCameraClippingEnabled); + cameraRayPick->parentTransform = std::make_shared(); + _cameraClippingRayPickID = DependencyManager::get()->addPick(PickQuery::Ray, cameraRayPick); + } + BillboardModeHelpers::setBillboardRotationOperator([](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos, bool rotate90x) { const glm::quat ROTATE_90X = glm::angleAxis(-(float)M_PI_2, Vectors::RIGHT); @@ -3684,9 +3697,7 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { PROFILE_RANGE(render, __FUNCTION__); PerformanceTimer perfTimer("updateCamera"); - glm::vec3 boomOffset; auto myAvatar = getMyAvatar(); - boomOffset = myAvatar->getModelScale() * myAvatar->getBoomLength() * -IDENTITY_FORWARD; // The render mode is default or mirror if the camera is in mirror mode, assigned further below renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE; @@ -3725,6 +3736,16 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { _myCamera.setOrientation(glm::normalize(glmExtractRotation(worldCameraMat))); _myCamera.setPosition(extractTranslation(worldCameraMat)); } else { + float boomLength = myAvatar->getBoomLength(); + if (getCameraClippingEnabled()) { + auto result = + DependencyManager::get()->getPrevPickResultTyped(_cameraClippingRayPickID); + if (result && result->doesIntersect()) { + const float CAMERA_CLIPPING_EPSILON = 0.1f; + boomLength = std::min(boomLength, result->distance - CAMERA_CLIPPING_EPSILON); + } + } + glm::vec3 boomOffset = myAvatar->getModelScale() * boomLength * -IDENTITY_FORWARD; _thirdPersonHMDCameraBoomValid = false; if (mode == CAMERA_MODE_THIRD_PERSON) { _myCamera.setOrientation(myAvatar->getHead()->getOrientation()); @@ -3802,7 +3823,19 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { _myCamera.update(); } - renderArgs._cameraMode = (int8_t)_myCamera.getMode(); + renderArgs._cameraMode = (int8_t)mode; + + const bool shouldEnableCameraClipping = + (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) && !isHMDMode() && + getCameraClippingEnabled(); + if (_prevCameraClippingEnabled != shouldEnableCameraClipping) { + if (shouldEnableCameraClipping) { + DependencyManager::get()->enablePick(_cameraClippingRayPickID); + } else { + DependencyManager::get()->disablePick(_cameraClippingRayPickID); + } + _prevCameraClippingEnabled = shouldEnableCameraClipping; + } } void Application::runTests() { @@ -3817,6 +3850,16 @@ void Application::setFieldOfView(float fov) { } } +void Application::setCameraClippingEnabled(bool enabled) { + _cameraClippingEnabled.set(enabled); + _prevCameraClippingEnabled = enabled; + if (enabled) { + DependencyManager::get()->enablePick(_cameraClippingRayPickID); + } else { + DependencyManager::get()->disablePick(_cameraClippingRayPickID); + } +} + void Application::setHMDTabletScale(float hmdTabletScale) { _hmdTabletScale.set(hmdTabletScale); } diff --git a/interface/src/Application.h b/interface/src/Application.h index ec575b384d..2c7a97a5eb 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -246,6 +246,9 @@ public: float getFieldOfView() { return _fieldOfView.get(); } void setFieldOfView(float fov); + bool getCameraClippingEnabled() { return _cameraClippingEnabled.get(); } + void setCameraClippingEnabled(bool enabled); + float getHMDTabletScale() { return _hmdTabletScale.get(); } void setHMDTabletScale(float hmdTabletScale); float getDesktopTabletScale() { return _desktopTabletScale.get(); } @@ -719,6 +722,7 @@ private: Setting::Handle _previousScriptLocation; Setting::Handle _fieldOfView; + Setting::Handle _cameraClippingEnabled; Setting::Handle _hmdTabletScale; Setting::Handle _desktopTabletScale; Setting::Handle _firstRun; @@ -893,5 +897,8 @@ private: DiscordPresence* _discordPresence{ nullptr }; bool _profilingInitialized { false }; + + bool _prevCameraClippingEnabled { false }; + unsigned int _cameraClippingRayPickID; }; #endif // hifi_Application_h diff --git a/interface/src/CameraRootTransformNode.cpp b/interface/src/CameraRootTransformNode.cpp new file mode 100644 index 0000000000..596bdab3d3 --- /dev/null +++ b/interface/src/CameraRootTransformNode.cpp @@ -0,0 +1,48 @@ +// +// Created by HifiExperiments on 10/30/2024 +// Copyright 2024 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "CameraRootTransformNode.h" + +#include "Application.h" +#include "DependencyManager.h" +#include "avatar/AvatarManager.h" +#include "avatar/MyAvatar.h" + +Transform CameraRootTransformNode::getTransform() { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + glm::vec3 pos; + glm::quat ori; + + CameraMode mode = qApp->getCamera().getMode(); + if (mode == CAMERA_MODE_FIRST_PERSON || mode == CAMERA_MODE_THIRD_PERSON) { + pos = myAvatar->getDefaultEyePosition(); + ori = myAvatar->getHeadOrientation(); + } else if (mode == CAMERA_MODE_FIRST_PERSON_LOOK_AT) { + pos = myAvatar->getCameraEyesPosition(0.0f); + ori = myAvatar->getLookAtRotation(); + } else { + ori = myAvatar->getLookAtRotation(); + pos = myAvatar->getLookAtPivotPoint(); + + if (mode == CAMERA_MODE_SELFIE) { + ori = ori * glm::angleAxis(PI, ori * Vectors::UP); + } + } + + ori = ori * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT); + + glm::vec3 scale = glm::vec3(myAvatar->scaleForChildren()); + return Transform(ori, scale, pos); +} + +QVariantMap CameraRootTransformNode::toVariantMap() const { + QVariantMap map; + map["joint"] = "CameraRoot"; + return map; +} diff --git a/interface/src/CameraRootTransformNode.h b/interface/src/CameraRootTransformNode.h new file mode 100644 index 0000000000..6a0f58f42e --- /dev/null +++ b/interface/src/CameraRootTransformNode.h @@ -0,0 +1,20 @@ +// +// Created by HifiExperiments on 10/30/2024 +// Copyright 2024 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_CameraRootTransformNode_h +#define hifi_CameraRootTransformNode_h + +#include "TransformNode.h" + +class CameraRootTransformNode : public TransformNode { +public: + CameraRootTransformNode() {} + Transform getTransform() override; + QVariantMap toVariantMap() const override; +}; + +#endif // hifi_CameraRootTransformNode_h diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 5323c52faf..221b0fcb63 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -21,6 +21,7 @@ #include "ParabolaPick.h" #include "CollisionPick.h" +#include "CameraRootTransformNode.h" #include "SpatialParentFinder.h" #include "PickTransformNode.h" #include "MouseTransformNode.h" @@ -537,6 +538,9 @@ void PickScriptingInterface::setParentTransform(std::shared_ptr pick, } else if (joint == "Avatar") { pick->parentTransform = std::make_shared(); return; + } else if (joint == "CameraRoot") { + pick->parentTransform = std::make_shared(); + return; } else { parentUuid = myAvatar->getSessionUUID(); parentJointIndex = myAvatar->getJointIndex(joint); diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index e126a5734e..db3e398547 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -342,6 +342,13 @@ void RenderScriptingInterface::setVerticalFieldOfView(float fieldOfView) { } } +void RenderScriptingInterface::setCameraClippingEnabled(bool enabled) { + if (qApp->getCameraClippingEnabled() != enabled) { + qApp->setCameraClippingEnabled(enabled); + emit settingsChanged(); + } +} + QStringList RenderScriptingInterface::getScreens() const { QStringList screens; diff --git a/interface/src/scripting/RenderScriptingInterface.h b/interface/src/scripting/RenderScriptingInterface.h index 56b474cf31..97d7868a96 100644 --- a/interface/src/scripting/RenderScriptingInterface.h +++ b/interface/src/scripting/RenderScriptingInterface.h @@ -37,6 +37,7 @@ * they're disabled. * @property {integer} antialiasingMode - The active anti-aliasing mode. * @property {number} viewportResolutionScale - The view port resolution scale, > 0.0. + * @property {boolean} cameraClippingEnabled - true if third person camera clipping is enabled, false if it's disabled. */ class RenderScriptingInterface : public QObject { Q_OBJECT @@ -49,6 +50,7 @@ class RenderScriptingInterface : public QObject { Q_PROPERTY(AntialiasingConfig::Mode antialiasingMode READ getAntialiasingMode WRITE setAntialiasingMode NOTIFY settingsChanged) Q_PROPERTY(float viewportResolutionScale READ getViewportResolutionScale WRITE setViewportResolutionScale NOTIFY settingsChanged) Q_PROPERTY(float verticalFieldOfView READ getVerticalFieldOfView WRITE setVerticalFieldOfView NOTIFY settingsChanged) + Q_PROPERTY(bool cameraClippingEnabled READ getCameraClippingEnabled WRITE setCameraClippingEnabled NOTIFY settingsChanged) public: RenderScriptingInterface(); @@ -261,7 +263,21 @@ public slots: * @function Render.setVerticalFieldOfView * @param {number} fieldOfView - The vertical field of view in degrees to set. */ - void setVerticalFieldOfView( float fieldOfView ); + void setVerticalFieldOfView(float fieldOfView); + + /*@jsdoc + * Gets whether or not third person camera clipping is enabled. + * @function Render.getCameraClippingEnabled + * @returns {boolean} true if camera clipping is enabled, false if it's disabled. + */ + bool getCameraClippingEnabled() { return qApp->getCameraClippingEnabled(); } + + /*@jsdoc + * Sets whether or not third person camera clipping is enabled. + * @function Render.setCameraClippingEnabled + * @param {boolean} enabled - true to enable third person camera clipping, false to disable. + */ + void setCameraClippingEnabled(bool enabled); signals: diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 4c87f12998..623aede2fa 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -231,7 +231,7 @@ void setupPreferences() { preferences->addPreference(new CheckPreference(UI_CATEGORY, "Show Graphics icon on tablet and toolbar", getter, setter)); } - static const QString VIEW_CATEGORY{ "View" }; + static const QString VIEW_CATEGORY { "View" }; { auto getter = [myAvatar]()->float { return myAvatar->getRealWorldFieldOfView(); }; auto setter = [myAvatar](float value) { myAvatar->setRealWorldFieldOfView(value); }; @@ -249,6 +249,11 @@ void setupPreferences() { preference->setStep(1); preferences->addPreference(preference); } + { + auto getter = []()->bool { return qApp->getCameraClippingEnabled(); }; + auto setter = [](bool value) { qApp->setCameraClippingEnabled(value); }; + preferences->addPreference(new CheckPreference(VIEW_CATEGORY, "Enable 3rd Person Camera Clipping?", getter, setter)); + } // Snapshots static const QString SNAPSHOTS { "Snapshots" }; diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index 82cab1c76c..24df1c9e0f 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -261,6 +261,8 @@ visible: false }); + var savedClippingEnabled = false; + function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { // Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original // position in the given direction. @@ -1195,6 +1197,7 @@ selectionDisplay.disableTriggerMapping(); tablet.landscape = false; Controller.disableMapping(CONTROLLER_MAPPING_NAME); + Render.cameraClippingEnabled = savedClippingEnabled; } else { if (shouldUseEditTabletApp()) { tablet.loadQMLSource(Script.resolvePath("qml/Edit.qml"), true); @@ -1212,6 +1215,8 @@ print("starting tablet in landscape mode"); tablet.landscape = true; Controller.enableMapping(CONTROLLER_MAPPING_NAME); + savedClippingEnabled = Render.cameraClippingEnabled; + Render.cameraClippingEnabled = false; // Not sure what the following was meant to accomplish, but it currently causes // everybody else to think that Interface has lost focus overall. fogbugzid:558 // Window.setFocus(); From 8e41908e49f845e5937768b9631886e1de37787b Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Sat, 31 Dec 2022 19:32:33 +0100 Subject: [PATCH 09/35] Partial spherical harmonics tests --- tests/ktx/src/KtxTests.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/ktx/src/KtxTests.cpp b/tests/ktx/src/KtxTests.cpp index 25ab15f5db..cac9690b5c 100644 --- a/tests/ktx/src/KtxTests.cpp +++ b/tests/ktx/src/KtxTests.cpp @@ -16,7 +16,9 @@ #include #include #include - +#include "SerDes.h" +#include "KTX.h" +#include "Texture_ktx.cpp" QTEST_GUILESS_MAIN(KtxTests) @@ -31,6 +33,19 @@ QString getRootPath() { return result; } +#if 0 +ktx::Byte* serializeSPH(ktx::Byte* data, const gpu::IrradianceKTXPayload &payload) const { + *(ktx::IrradianceKTXPayload::Version*)data = IrradianceKTXPayload::CURRENT_VERSION; + data += sizeof(ktx::IrradianceKTXPayload::Version); + + memcpy(data, &payload._irradianceSH, sizeof(ktx::SphericalHarmonics)); + data += sizeof(SphericalHarmonics); + + return data + PADDING; +} +#endif + + void KtxTests::initTestCase() { } @@ -147,6 +162,14 @@ void KtxTests::testKtxSerialization() { testTexture->setKtxBacking(TEST_IMAGE_KTX.fileName().toStdString()); } + +void KtxTests::testKtxNewSerializationSphericalHarmonics() { + DataSerializer ser; + + +} + + #if 0 static const QString TEST_FOLDER { "H:/ktx_cacheold" }; From 23a5795ba9ee0c8a737db17eb7f3f6af6e9e22b0 Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Fri, 10 Jun 2022 00:45:12 +0200 Subject: [PATCH 10/35] Initial version --- libraries/gpu/src/gpu/Texture.h | 56 +++- libraries/gpu/src/gpu/Texture_ktx.cpp | 47 +++- libraries/shared/src/SerDes.cpp | 70 +++++ libraries/shared/src/SerDes.h | 382 ++++++++++++++++++++++++++ tests/shared/src/SerializerTests.cpp | 107 ++++++++ tests/shared/src/SerializerTests.h | 29 ++ 6 files changed, 683 insertions(+), 8 deletions(-) create mode 100644 libraries/shared/src/SerDes.cpp create mode 100644 libraries/shared/src/SerDes.h create mode 100644 tests/shared/src/SerializerTests.cpp create mode 100644 tests/shared/src/SerializerTests.h diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 907f9ff392..5143cc1c23 100644 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -24,6 +24,7 @@ #include "Forward.h" #include "Resource.h" #include "Metric.h" +#include "SerDes.h" const int ABSOLUTE_MAX_TEXTURE_NUM_PIXELS = 8192 * 8192; @@ -136,7 +137,7 @@ public: uint8 _wrapModeU = WRAP_REPEAT; uint8 _wrapModeV = WRAP_REPEAT; uint8 _wrapModeW = WRAP_REPEAT; - + uint8 _mipOffset = 0; uint8 _minMip = 0; uint8 _maxMip = MAX_MIP_LEVEL; @@ -156,8 +157,24 @@ public: _minMip == other._minMip && _maxMip == other._maxMip; } + + SerDes &operator<<(SerDes &dsd) { + dsd << _borderColor; + dsd << _maxAnisotropy; + dsd << _filter; + dsd << _comparisonFunc; + dsd << _wrapModeU; + dsd << _wrapModeV; + dsd << _wrapModeW; + dsd << _mipOffset; + dsd << _minMip; + dsd << _maxMip; + return dsd; + } }; + + Sampler() {} Sampler(const Filter filter, const WrapMode wrap = WRAP_REPEAT) : _desc(filter, wrap) {} Sampler(const Desc& desc) : _desc(desc) {} @@ -193,6 +210,33 @@ protected: friend class Deserializer; }; +inline SerDes &operator<<(SerDes &ser, const Sampler::Desc &d) { + ser << d._borderColor; + ser << d._maxAnisotropy; + ser << d._filter; + ser << d._comparisonFunc; + ser << d._wrapModeU; + ser << d._wrapModeV; + ser << d._wrapModeW; + ser << d._mipOffset; + ser << d._minMip; + ser << d._maxMip; + return ser; +} + +inline SerDes &operator>>(SerDes &dsr, Sampler::Desc &d) { + dsr >> d._borderColor; + dsr >> d._maxAnisotropy; + dsr >> d._filter; + dsr >> d._comparisonFunc; + dsr >> d._wrapModeU; + dsr >> d._wrapModeV; + dsr >> d._wrapModeW; + dsr >> d._mipOffset; + dsr >> d._minMip; + dsr >> d._maxMip; + return dsr; +} enum class TextureUsageType : uint8 { RENDERBUFFER, // Used as attachments to a framebuffer RESOURCE, // Resource textures, like materials... subject to memory manipulation @@ -230,7 +274,7 @@ public: NORMAL, // Texture is a normal map ALPHA, // Texture has an alpha channel ALPHA_MASK, // Texture alpha channel is a Mask 0/1 - NUM_FLAGS, + NUM_FLAGS, }; typedef std::bitset Flags; @@ -478,7 +522,7 @@ public: uint16 evalMipDepth(uint16 level) const { return std::max(_depth >> level, 1); } // The true size of an image line or surface depends on the format, tiling and padding rules - // + // // Here are the static function to compute the different sizes from parametered dimensions and format // Tile size must be a power of 2 static uint16 evalTiledPadding(uint16 length, int tile) { int tileMinusOne = (tile - 1); return (tileMinusOne - (length + tileMinusOne) % tile); } @@ -507,7 +551,7 @@ public: uint32 evalMipFaceNumTexels(uint16 level) const { return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level); } uint32 evalMipNumTexels(uint16 level) const { return evalMipFaceNumTexels(level) * getNumFaces(); } - // For convenience assign a source name + // For convenience assign a source name const std::string& source() const { return _source; } void setSource(const std::string& source) { _source = source; } const std::string& sourceHash() const { return _sourceHash; } @@ -633,7 +677,7 @@ protected: uint16 _maxMipLevel { 0 }; uint16 _minMip { 0 }; - + Type _type { TEX_1D }; Usage _usage; @@ -643,7 +687,7 @@ protected: bool _isIrradianceValid = false; bool _defined = false; bool _important = false; - + static TexturePointer create(TextureUsageType usageType, Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips, const Sampler& sampler); Size resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices, uint16 numMips); diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index c4b674a917..748ec1a702 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -18,6 +18,7 @@ #include #include "GPULogging.h" +#include "SerDes.h" using namespace gpu; @@ -42,6 +43,44 @@ struct GPUKTXPayload { TextureUsageType _usageType; glm::ivec2 _originalSize { 0, 0 }; + void serialize2(SerDes &ser) { + ser << CURRENT_VERSION; + ser << _samplerDesc; + + uint32 usageData = _usage._flags.to_ulong(); + ser << usageData; + + ser << (char)_usageType; + ser << _originalSize; + ser.addPadding(PADDING); + } + + bool unserialize2(SerDes &dsr) { + Version version = 0; + uint32 usageData; + uint8_t usagetype = 0; + + dsr >> version; + + if (version > CURRENT_VERSION) { + // If we try to load a version that we don't know how to parse, + // it will render incorrectly + return false; + } + + dsr >> _samplerDesc; + dsr >> usageData; + dsr >> usagetype; + _usageType = (TextureUsageType)usagetype; + + if (version >= 2) { + dsr >> _originalSize; + } + + return true; + } + + Byte* serialize(Byte* data) const { *(Version*)data = CURRENT_VERSION; data += sizeof(Version); @@ -103,7 +142,9 @@ struct GPUKTXPayload { auto found = std::find_if(keyValues.begin(), keyValues.end(), isGPUKTX); if (found != keyValues.end()) { auto value = found->_value; - return payload.unserialize(value.data(), value.size()); + SerDes dsr(value.data(), value.size()); + return payload.unserialize2(dsr); + //return payload.unserialize(value.data(), value.size()); } return false; } @@ -467,7 +508,9 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture, const glm::ivec gpuKeyval._originalSize = originalSize; Byte keyvalPayload[GPUKTXPayload::SIZE]; - gpuKeyval.serialize(keyvalPayload); + SerDes ser(keyvalPayload, sizeof(keyvalPayload)); + + gpuKeyval.serialize2(ser); ktx::KeyValues keyValues; keyValues.emplace_back(GPUKTXPayload::KEY, (uint32)GPUKTXPayload::SIZE, (ktx::Byte*) &keyvalPayload); diff --git a/libraries/shared/src/SerDes.cpp b/libraries/shared/src/SerDes.cpp new file mode 100644 index 0000000000..c64430f201 --- /dev/null +++ b/libraries/shared/src/SerDes.cpp @@ -0,0 +1,70 @@ +// +// SerDes.h +// +// +// Created by Dale Glass on 5/6/2022 +// Copyright 2022 Dale Glass +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "SerDes.h" +const int SerDes::DEFAULT_SIZE; +const char SerDes::PADDING_CHAR; + +QDebug operator<<(QDebug debug, const SerDes &ds) { + debug << "{ capacity =" << ds.capacity() << "; length = " << ds.length() << "; pos = " << ds.pos() << "}"; + debug << "\n"; + + QString literal; + QString hex; + + for(size_t i=0;i(c), 16 ); + if ( hnum.length() == 1 ) { + hnum.prepend("0"); + } + + hex.append(hnum + " "); + + if ( literal.length() == 16 || (i+1 == ds.length()) ) { + while( literal.length() < 16 ) { + literal.append(" "); + hex.append(" "); + } + + debug << literal << " " << hex << "\n"; + literal.clear(); + hex.clear(); + } + } + + return debug; +} + + +void SerDes::changeAllocation(size_t new_size) { + while ( _capacity < new_size) { + _capacity *= 2; + } + + char *new_buf = new char[_capacity]; + assert( *new_buf ); + + memcpy(new_buf, _store, _length); + char *prev_buf = _store; + _store = new_buf; + + delete []prev_buf; +} diff --git a/libraries/shared/src/SerDes.h b/libraries/shared/src/SerDes.h new file mode 100644 index 0000000000..4651ac8353 --- /dev/null +++ b/libraries/shared/src/SerDes.h @@ -0,0 +1,382 @@ +// +// SerDes.h +// +// +// Created by Dale Glass on 5/6/2022 +// Copyright 2022 Dale Glass +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#include +#include +#include +#include +#include + +/** + * @brief Data serializer/deserializer + * + * When encoding, this class takes in data and encodes it into a buffer. No attempt is made to store version numbers, lengths, + * or any other metadata. It's entirely up to the user to use the class in such a way that the process can be + * correctly reversed if variable-length or optional fields are used. + * + * It can operate both on an internal, dynamically-allocated buffer, or an externally provided, fixed-size one. + * + * If an external store is used, the class will refuse to add data once capacity is reached and set the overflow flag. + * + * When decoding, this class operates on a fixed size buffer. If an attempt to read past the end is made, the read fails, + * and the overflow flag is set. + * + * The class was written for the maximum simplicity possible and inline friendliness. + */ +class SerDes { + public: + // This class is aimed at network serialization, so we assume we're going to deal + // with something MTU-sized by default. + static const int DEFAULT_SIZE = 1500; + static const char PADDING_CHAR = 0xAA; + + /** + * @brief Construct a dynamically allocated serializer + * + * If constructed this way, an internal buffer will be dynamically allocated and grown as needed. + * + * The default buffer size is 1500 bytes, based on the assumption that it will be used to construct + * network packets. + */ + SerDes() { + _capacity = DEFAULT_SIZE; + _pos = 0; + _length = 0; + _store = new char[_capacity]; + } + + /** + * @brief Construct a statically allocated serializer + * + * If constructed this way, the external buffer will be used to store data. The class will refuse to + * keep adding data if the maximum length is reached, and set the overflow flag. + * + * The flag can be read with isOverflow() + * + * @param externalStore External data store + * @param storeLength Length of the data store + */ + SerDes(char *externalStore, size_t storeLength) { + _capacity = storeLength; + _length = storeLength; + _pos = 0; + _storeIsExternal = true; + _store = externalStore; + } + + SerDes(uint8_t *externalStore, size_t storeLength) : SerDes((char*)externalStore, storeLength) { + + } + + SerDes(const SerDes &) = delete; + SerDes &operator=(const SerDes &) = delete; + + + + ~SerDes() { + if (!_storeIsExternal) { + delete[] _store; + } + } + + void addPadding(size_t bytes) { + if (!extendBy(bytes)) { + return; + } + + // Fill padding with something recognizable. Will keep valgrind happier. + memset(&_store[_pos], PADDING_CHAR, bytes); + _pos += bytes; + } + + SerDes &operator<<(uint8_t c) { + return *this << int8_t(c); + } + + SerDes &operator<<(int8_t c) { + if (!extendBy(1)) { + return *this; + } + + _store[_pos++] = c; + return *this; + } + + SerDes &operator>>(uint8_t &c) { + return *this >> reinterpret_cast(c); + } + + SerDes &operator>>(int8_t &c) { + if ( _pos < _length ) { + c = _store[_pos++]; + } else { + _overflow = true; + qCritical() << "Deserializer trying to read past end of input, reading 8 bits from position " << _pos << ", length " << _length; + } + + return *this; + } + + /////////////////////////////////////////////////////////// + + SerDes &operator<<(uint16_t val) { + return *this << int16_t(val); + } + + SerDes &operator<<(int16_t val) { + if (!extendBy(sizeof(val))) { + return *this; + } + + memcpy(&_store[_pos], (char*)&val, sizeof(val)); + _pos += sizeof(val); + return *this; + } + + SerDes &operator>>(uint16_t &val) { + return *this >> reinterpret_cast(val); + } + + SerDes &operator>>(int16_t &val) { + if ( _pos + sizeof(val) <= _length ) { + memcpy((char*)&val, &_store[_pos], sizeof(val)); + _pos += sizeof(val); + } else { + _overflow = true; + qCritical() << "Deserializer trying to read past end of input, reading 16 bits from position " << _pos << ", length " << _length; + } + + return *this; + } + + /////////////////////////////////////////////////////////// + + SerDes &operator<<(uint32_t val) { + return *this << int32_t(val); + } + + SerDes &operator<<(int32_t val) { + if (!extendBy(sizeof(val))) { + return *this; + } + + memcpy(&_store[_pos], (char*)&val, sizeof(val)); + _pos += sizeof(val); + return *this; + } + + SerDes &operator>>(uint32_t &val) { + return *this >> reinterpret_cast(val); + } + + SerDes &operator>>(int32_t &val) { + if ( _pos + sizeof(val) <= _length ) { + memcpy((char*)&val, &_store[_pos], sizeof(val)); + _pos += sizeof(val); + } else { + _overflow = true; + qCritical() << "Deserializer trying to read past end of input, reading 32 bits from position " << _pos << ", length " << _length; + } + return *this; + } + + + /////////////////////////////////////////////////////////// + + SerDes &operator<<(glm::vec3 val) { + size_t sz = sizeof(val.x); + if (!extendBy(sz*3)) { + return *this; + } + + memcpy(&_store[_pos ], (char*)&val.x, sz); + memcpy(&_store[_pos + sz ], (char*)&val.y, sz); + memcpy(&_store[_pos + sz*2], (char*)&val.z, sz); + + _pos += sz*3; + return *this; + } + + SerDes &operator>>(glm::vec3 &val) { + size_t sz = sizeof(val.x); + + if ( _pos + sz*3 <= _length ) { + memcpy((char*)&val.x, &_store[_pos ], sz); + memcpy((char*)&val.y, &_store[_pos + sz ], sz); + memcpy((char*)&val.z, &_store[_pos + sz*2], sz); + + _pos += sz*3; + } else { + _overflow = true; + qCritical() << "Deserializer trying to read past end of input, reading glm::vec3 from position " << _pos << ", length " << _length; + } + return *this; + } + + /////////////////////////////////////////////////////////// + + SerDes &operator<<(glm::vec4 val) { + size_t sz = sizeof(val.x); + if (!extendBy(sz*4)) { + return *this; + } + + memcpy(&_store[_pos ], (char*)&val.x, sz); + memcpy(&_store[_pos + sz ], (char*)&val.y, sz); + memcpy(&_store[_pos + sz*2], (char*)&val.z, sz); + memcpy(&_store[_pos + sz*3], (char*)&val.w, sz); + + _pos += sz*3; + return *this; + } + + SerDes &operator>>(glm::vec4 &val) { + size_t sz = sizeof(val.x); + + if ( _pos + sz*4 <= _length ) { + memcpy((char*)&val.x, &_store[_pos ], sz); + memcpy((char*)&val.y, &_store[_pos + sz ], sz); + memcpy((char*)&val.z, &_store[_pos + sz*2], sz); + memcpy((char*)&val.w, &_store[_pos + sz*3], sz); + + _pos += sz*3; + } else { + _overflow = true; + qCritical() << "Deserializer trying to read past end of input, reading glm::vec3 from position " << _pos << ", length " << _length; + } + return *this; + } + + /////////////////////////////////////////////////////////// + + SerDes &operator<<(glm::ivec2 val) { + size_t sz = sizeof(val.x); + if (!extendBy(sz*2)) { + return *this; + } + + memcpy(&_store[_pos ], (char*)&val.x, sz); + memcpy(&_store[_pos + sz ], (char*)&val.y, sz); + + _pos += sz*2; + return *this; + } + + SerDes &operator>>(glm::ivec2 &val) { + size_t sz = sizeof(val.x); + + if ( _pos + sz*2 <= _length ) { + memcpy((char*)&val.x, &_store[_pos ], sz); + memcpy((char*)&val.y, &_store[_pos + sz ], sz); + + _pos += sz*2; + } else { + _overflow = true; + qCritical() << "Deserializer trying to read past end of input, reading glm::ivec2 from position " << _pos << ", length " << _length; + } + return *this; + } + /////////////////////////////////////////////////////////// + + SerDes &operator<<(const char *val) { + size_t len = strlen(val)+1; + extendBy(len); + memcpy(&_store[_pos], val, len); + _pos += len; + return *this; + } + + SerDes &operator<<(const QString &val) { + return *this << val.toUtf8().constData(); + } + + + /////////////////////////////////////////////////////////// + + /** + * @brief Current position in the buffer. Starts at 0. + * + * @return size_t + */ + size_t pos() const { return _pos; } + + /** + * @brief Last position that was written to in the buffer. Starts at 0. + * + * @return size_t + */ + size_t length() const { return _length; } + + /** + * @brief Current capacity of the buffer. + * + * If the buffer is dynamically allocated, it can grow. + * + * If the buffer is static, this is a fixed limit. + * + * @return size_t + */ + size_t capacity() const { return _capacity; } + + /** + * @brief Whether there's any data in the buffer + * + * @return true Something has been written + * @return false The buffer is empty + */ + bool isEmpty() const { return _length == 0; } + + /** + * @brief The buffer size limit has been reached + * + * This can only return true for a statically allocated buffer. + * + * @return true Limit reached + * @return false There is still room + */ + bool isOverflow() const { return _overflow; } + + /** + * @brief Reset the serializer to the start, clear overflow bit. + * + */ + void rewind() { _pos = 0; _overflow = false; } + + friend QDebug operator<<(QDebug debug, const SerDes &ds); + + private: + bool extendBy(size_t bytes) { + //qDebug() << "Extend by" << bytes; + + if ( _capacity < _length + bytes) { + if ( _storeIsExternal ) { + _overflow = true; + return false; + } + + changeAllocation(_length + bytes); + } + + _length += bytes; + return true; + } + + // This is split up here to try to make the class as inline-friendly as possible. + void changeAllocation(size_t new_size); + + char *_store; + bool _storeIsExternal = false; + bool _overflow = false; + size_t _capacity = 0; + size_t _length = 0; + size_t _pos = 0; +}; diff --git a/tests/shared/src/SerializerTests.cpp b/tests/shared/src/SerializerTests.cpp new file mode 100644 index 0000000000..dc5ffa2160 --- /dev/null +++ b/tests/shared/src/SerializerTests.cpp @@ -0,0 +1,107 @@ +// +// SerializerTests.cpp +// +// Copyright 2022 Dale Glass +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include "SerializerTests.h" +#include +#include +#include + +QTEST_GUILESS_MAIN(SerializerTests) + + +void SerializerTests::initTestCase() { +} + +void SerializerTests::testCreate() { + SerDes s; + QCOMPARE(s.length(), 0); + QCOMPARE(s.capacity(), SerDes::DEFAULT_SIZE); + QCOMPARE(s.isEmpty(), true); +} + +void SerializerTests::testAdd() { + SerDes s; + s << (qint8)1; + QCOMPARE(s.length(), 1); + QCOMPARE(s.isEmpty(), false); + + s << (quint8)-1; + QCOMPARE(s.length(), 2); + + s << (qint16)0xaabb; + QCOMPARE(s.length(), 4); + + s << (quint16)-18000; + QCOMPARE(s.length(), 6); + + s << (qint32)0xCCDDEEFF; + QCOMPARE(s.length(), 10); + + s << (quint32)-1818000000; + QCOMPARE(s.length(), 14); + + s << "Hello, world!"; + QCOMPARE(s.length(), 28); + + glm::vec3 v{1.f,2.f,3.f}; + s << v; + QCOMPARE(s.length(), 40); + + + qDebug() << s; +} + +void SerializerTests::testAddAndRead() { + SerDes s; + glm::vec3 v{1.f, 3.1415f, 2.71828f}; + glm::vec3 v2; + + s << (qint8)1; + s << (qint16)0xaabb; + s << (qint32)0xccddeeff; + s << v; + + qint8 i8; + qint16 i16; + qint32 i32; + + s.rewind(); + + s >> i8; + s >> i16; + s >> i32; + s >> v2; + + qDebug() << s; + + QCOMPARE(i8, (qint8)1); + QCOMPARE(i16, (qint16)0xaabb); + QCOMPARE(i32, (qint32)0xccddeeff); + QCOMPARE(v, v2); +} + +void SerializerTests::testReadPastEnd() { + SerDes s; + qint8 i8; + qint16 i16; + s << (qint8)1; + s.rewind(); + s >> i8; + QCOMPARE(s.pos(), 1); + + s.rewind(); + s >> i16; + QCOMPARE(s.pos(), 0); +} + + +void SerializerTests::cleanupTestCase() { +} + diff --git a/tests/shared/src/SerializerTests.h b/tests/shared/src/SerializerTests.h new file mode 100644 index 0000000000..1320d99c57 --- /dev/null +++ b/tests/shared/src/SerializerTests.h @@ -0,0 +1,29 @@ +// +// ResourceTests.h +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef overte_SerializerTests_h +#define overte_SerializerTests_h + +#include +#include + +class SerializerTests : public QObject { + Q_OBJECT +private slots: + void initTestCase(); + void testCreate(); + void testAdd(); + void testAddAndRead(); + void testReadPastEnd(); + void cleanupTestCase(); +private: + +}; + +#endif // overte_SerializerTests_h From 30ced59cec1b6bde8d59b88e87e2748cb7d4777b Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Sat, 11 Jun 2022 21:36:40 +0200 Subject: [PATCH 11/35] Fix vec4 serialization --- libraries/shared/src/SerDes.h | 4 ++-- tests/shared/src/SerializerTests.cpp | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/libraries/shared/src/SerDes.h b/libraries/shared/src/SerDes.h index 4651ac8353..b608d452ab 100644 --- a/libraries/shared/src/SerDes.h +++ b/libraries/shared/src/SerDes.h @@ -235,7 +235,7 @@ class SerDes { memcpy(&_store[_pos + sz*2], (char*)&val.z, sz); memcpy(&_store[_pos + sz*3], (char*)&val.w, sz); - _pos += sz*3; + _pos += sz*4; return *this; } @@ -248,7 +248,7 @@ class SerDes { memcpy((char*)&val.z, &_store[_pos + sz*2], sz); memcpy((char*)&val.w, &_store[_pos + sz*3], sz); - _pos += sz*3; + _pos += sz*4; } else { _overflow = true; qCritical() << "Deserializer trying to read past end of input, reading glm::vec3 from position " << _pos << ", length " << _length; diff --git a/tests/shared/src/SerializerTests.cpp b/tests/shared/src/SerializerTests.cpp index dc5ffa2160..bc50554d96 100644 --- a/tests/shared/src/SerializerTests.cpp +++ b/tests/shared/src/SerializerTests.cpp @@ -60,13 +60,19 @@ void SerializerTests::testAdd() { void SerializerTests::testAddAndRead() { SerDes s; - glm::vec3 v{1.f, 3.1415f, 2.71828f}; - glm::vec3 v2; + glm::vec3 v3_a{1.f, 3.1415f, 2.71828f}; + glm::vec3 v3_b; + glm::vec4 v4_a{3.1415f, 2.71828f, 1.4142f, 1.6180f}; + glm::vec4 v4_b; + glm::ivec2 iv2_a{10, 24}; + glm::ivec2 iv2_b; s << (qint8)1; s << (qint16)0xaabb; s << (qint32)0xccddeeff; - s << v; + s << v3_a; + s << v4_a; + s << iv2_a; qint8 i8; qint16 i16; @@ -77,14 +83,18 @@ void SerializerTests::testAddAndRead() { s >> i8; s >> i16; s >> i32; - s >> v2; + s >> v3_b; + s >> v4_b; + s >> iv2_b; qDebug() << s; QCOMPARE(i8, (qint8)1); QCOMPARE(i16, (qint16)0xaabb); QCOMPARE(i32, (qint32)0xccddeeff); - QCOMPARE(v, v2); + QCOMPARE(v3_a, v3_b); + QCOMPARE(v4_a, v4_b); + QCOMPARE(iv2_a, iv2_b); } void SerializerTests::testReadPastEnd() { From 9c97928751f958a1424ff95e98dd26c1859cafe4 Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Sun, 12 Jun 2022 01:24:55 +0200 Subject: [PATCH 12/35] Add benchmarks --- tests/shared/src/SerializerTests.cpp | 73 ++++++++++++++++++++++++++++ tests/shared/src/SerializerTests.h | 3 ++ 2 files changed, 76 insertions(+) diff --git a/tests/shared/src/SerializerTests.cpp b/tests/shared/src/SerializerTests.cpp index bc50554d96..a1b3a37544 100644 --- a/tests/shared/src/SerializerTests.cpp +++ b/tests/shared/src/SerializerTests.cpp @@ -111,6 +111,79 @@ void SerializerTests::testReadPastEnd() { QCOMPARE(s.pos(), 0); } +void SerializerTests::benchmarkEncodingDynamicAlloc() { + QBENCHMARK { + SerDes s; + glm::vec3 v3_a{1.f, 3.1415f, 2.71828f}; + glm::vec3 v3_b; + glm::vec4 v4_a{3.1415f, 2.71828f, 1.4142f, 1.6180f}; + glm::vec4 v4_b; + glm::ivec2 iv2_a{10, 24}; + glm::ivec2 iv2_b; + + s << (qint8)1; + s << (qint16)0xaabb; + s << (qint32)0xccddeeff; + s << v3_a; + s << v4_a; + s << iv2_a; + } +} + +void SerializerTests::benchmarkEncodingStaticAlloc() { + char buf[1024]; + + QBENCHMARK { + SerDes s(buf, sizeof(buf)); + glm::vec3 v3_a{1.f, 3.1415f, 2.71828f}; + glm::vec3 v3_b; + glm::vec4 v4_a{3.1415f, 2.71828f, 1.4142f, 1.6180f}; + glm::vec4 v4_b; + glm::ivec2 iv2_a{10, 24}; + glm::ivec2 iv2_b; + + s << (qint8)1; + s << (qint16)0xaabb; + s << (qint32)0xccddeeff; + s << v3_a; + s << v4_a; + s << iv2_a; + } +} + + +void SerializerTests::benchmarkDecoding() { + SerDes s; + qint8 q8 = 1; + qint16 q16 = 0xaabb; + qint32 q32 = 0xccddeeff; + + glm::vec3 v3_a{1.f, 3.1415f, 2.71828f}; + glm::vec3 v3_b; + glm::vec4 v4_a{3.1415f, 2.71828f, 1.4142f, 1.6180f}; + glm::vec4 v4_b; + glm::ivec2 iv2_a{10, 24}; + glm::ivec2 iv2_b; + + s << q8; + s << q16; + s << q32; + s << v3_a; + s << v4_a; + s << iv2_a; + + + QBENCHMARK { + s.rewind(); + s >> q8; + s >> q16; + s >> q32; + s >> v3_a; + s >> v4_a; + s >> iv2_a; + } +} + void SerializerTests::cleanupTestCase() { } diff --git a/tests/shared/src/SerializerTests.h b/tests/shared/src/SerializerTests.h index 1320d99c57..3a3a3217d2 100644 --- a/tests/shared/src/SerializerTests.h +++ b/tests/shared/src/SerializerTests.h @@ -21,6 +21,9 @@ private slots: void testAdd(); void testAddAndRead(); void testReadPastEnd(); + void benchmarkEncodingDynamicAlloc(); + void benchmarkEncodingStaticAlloc(); + void benchmarkDecoding(); void cleanupTestCase(); private: From f6c203b54bad948b8068297666e2fe93831f46f1 Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Sun, 12 Jun 2022 01:25:38 +0200 Subject: [PATCH 13/35] Add documentation --- libraries/shared/src/SerDes.h | 239 +++++++++++++++++++++++++++++++++- 1 file changed, 232 insertions(+), 7 deletions(-) diff --git a/libraries/shared/src/SerDes.h b/libraries/shared/src/SerDes.h index b608d452ab..fe445fe854 100644 --- a/libraries/shared/src/SerDes.h +++ b/libraries/shared/src/SerDes.h @@ -24,19 +24,79 @@ * correctly reversed if variable-length or optional fields are used. * * It can operate both on an internal, dynamically-allocated buffer, or an externally provided, fixed-size one. - * * If an external store is used, the class will refuse to add data once capacity is reached and set the overflow flag. - * * When decoding, this class operates on a fixed size buffer. If an attempt to read past the end is made, the read fails, * and the overflow flag is set. * * The class was written for the maximum simplicity possible and inline friendliness. + * + * Example of encoding: + * + * @code {.cpp} + * uint8_t version = 1; + * uint16_t count = 1; + * glm::vec3 pos{1.5, 2.0, 9.0}; + * + * SerDes ser; + * ser << version; + * ser << count; + * ser << pos; + * + * // Serialized data is in ser.buffer(), ser.length() long. + * @endcode + * + * Example of decoding: + * + * @code {.cpp} + * // Incoming data has been placed in: + * // char buffer[1024]; + * + * uint8_t version; + * uint16_t count; + * glm::vec3 pos; + * + * SerDes des(buffer, sizeof(buffer)); + * des >> version; + * des >> count; + * des >> pos; + * @endcode + * + * This object should be modified directly to add support for any primitive and common datatypes in the code. To support serializing/deserializing + * classes and structures, implement a `operator<<` and `operator>>` functions for that object, eg: + * + * @code {.cpp} + * SerDes &operator<<(SerDes &ser, const Object &o) { + * ser << o._borderColor; + * ser << o._maxAnisotropy; + * ser << o._filter; + * return ser; + * } + * + * SerDes &operator>>(SerDes &des, Object &o) { + * des >> o._borderColor; + * des >> o._maxAnisotropy; + * des >> o._filter; + * return des; + * } + * + * @endcode + * */ class SerDes { public: - // This class is aimed at network serialization, so we assume we're going to deal - // with something MTU-sized by default. + /** + * @brief Default size for a dynamically allocated buffer. + * + * Since this is mostly intended to be used for networking, we default to the largest probable MTU here. + */ static const int DEFAULT_SIZE = 1500; + + /** + * @brief Character to use for padding. + * + * Padding should be ignored, so it doesn't matter what we go with here, but it can be useful to set it + * to something that would be distinctive in a dump. + */ static const char PADDING_CHAR = 0xAA; /** @@ -44,8 +104,7 @@ class SerDes { * * If constructed this way, an internal buffer will be dynamically allocated and grown as needed. * - * The default buffer size is 1500 bytes, based on the assumption that it will be used to construct - * network packets. + * The buffer is SerDes::DEFAULT_SIZE bytes by default, and doubles in size every time the limit is reached. */ SerDes() { _capacity = DEFAULT_SIZE; @@ -58,7 +117,8 @@ class SerDes { * @brief Construct a statically allocated serializer * * If constructed this way, the external buffer will be used to store data. The class will refuse to - * keep adding data if the maximum length is reached, and set the overflow flag. + * keep adding data if the maximum length is reached, write a critical message to the log, and set + * the overflow flag. * * The flag can be read with isOverflow() * @@ -73,6 +133,17 @@ class SerDes { _store = externalStore; } + /** + * @brief Construct a statically allocated serializer + * + * If constructed this way, the external buffer will be used to store data. The class will refuse to + * keep adding data if the maximum length is reached, and set the overflow flag. + * + * The flag can be read with isOverflow() + * + * @param externalStore External data store + * @param storeLength Length of the data store + */ SerDes(uint8_t *externalStore, size_t storeLength) : SerDes((char*)externalStore, storeLength) { } @@ -88,6 +159,15 @@ class SerDes { } } + /** + * @brief Adds padding to the output + * + * The bytes will be set to SerDes::PADDING_CHAR, which is a constant in the source code. + * Since padding isn't supposed to be read, it can be any value and is intended to + * be set to something that can be easily recognized in a dump. + * + * @param bytes Number of bytes to add + */ void addPadding(size_t bytes) { if (!extendBy(bytes)) { return; @@ -98,10 +178,22 @@ class SerDes { _pos += bytes; } + /** + * @brief Add an uint8_t to the output + * + * @param c Character to add + * @return SerDes& This object + */ SerDes &operator<<(uint8_t c) { return *this << int8_t(c); } + /** + * @brief Add an int8_t to the output + * + * @param c Character to add + * @return SerDes& This object + */ SerDes &operator<<(int8_t c) { if (!extendBy(1)) { return *this; @@ -111,10 +203,22 @@ class SerDes { return *this; } + /** + * @brief Read an uint8_t from the buffer + * + * @param c Character to read + * @return SerDes& This object + */ SerDes &operator>>(uint8_t &c) { return *this >> reinterpret_cast(c); } + /** + * @brief Read an int8_t from the buffer + * + * @param c Character to read + * @return SerDes& This object + */ SerDes &operator>>(int8_t &c) { if ( _pos < _length ) { c = _store[_pos++]; @@ -128,10 +232,22 @@ class SerDes { /////////////////////////////////////////////////////////// + /** + * @brief Add an uint16_t to the output + * + * @param val Value to add + * @return SerDes& This object + */ SerDes &operator<<(uint16_t val) { return *this << int16_t(val); } + /** + * @brief Add an int16_t to the output + * + * @param val Value to add + * @return SerDes& This object + */ SerDes &operator<<(int16_t val) { if (!extendBy(sizeof(val))) { return *this; @@ -142,10 +258,22 @@ class SerDes { return *this; } + /** + * @brief Read an uint16_t from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ SerDes &operator>>(uint16_t &val) { return *this >> reinterpret_cast(val); } + /** + * @brief Read an int16_t from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ SerDes &operator>>(int16_t &val) { if ( _pos + sizeof(val) <= _length ) { memcpy((char*)&val, &_store[_pos], sizeof(val)); @@ -160,10 +288,22 @@ class SerDes { /////////////////////////////////////////////////////////// + /** + * @brief Add an uint32_t to the output + * + * @param val Value to add + * @return SerDes& This object + */ SerDes &operator<<(uint32_t val) { return *this << int32_t(val); } + /** + * @brief Add an int32_t to the output + * + * @param val Value to add + * @return SerDes& This object + */ SerDes &operator<<(int32_t val) { if (!extendBy(sizeof(val))) { return *this; @@ -174,10 +314,22 @@ class SerDes { return *this; } + /** + * @brief Read an uint32_t from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ SerDes &operator>>(uint32_t &val) { return *this >> reinterpret_cast(val); } + /** + * @brief Read an int32_t from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ SerDes &operator>>(int32_t &val) { if ( _pos + sizeof(val) <= _length ) { memcpy((char*)&val, &_store[_pos], sizeof(val)); @@ -192,6 +344,12 @@ class SerDes { /////////////////////////////////////////////////////////// + /** + * @brief Add an glm::vec3 to the output + * + * @param val Value to add + * @return SerDes& This object + */ SerDes &operator<<(glm::vec3 val) { size_t sz = sizeof(val.x); if (!extendBy(sz*3)) { @@ -206,6 +364,12 @@ class SerDes { return *this; } + /** + * @brief Read a glm::vec3 from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ SerDes &operator>>(glm::vec3 &val) { size_t sz = sizeof(val.x); @@ -224,6 +388,12 @@ class SerDes { /////////////////////////////////////////////////////////// + /** + * @brief Add a glm::vec4 to the output + * + * @param val Value to add + * @return SerDes& This object + */ SerDes &operator<<(glm::vec4 val) { size_t sz = sizeof(val.x); if (!extendBy(sz*4)) { @@ -239,6 +409,12 @@ class SerDes { return *this; } + /** + * @brief Read a glm::vec4 from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ SerDes &operator>>(glm::vec4 &val) { size_t sz = sizeof(val.x); @@ -258,6 +434,12 @@ class SerDes { /////////////////////////////////////////////////////////// + /** + * @brief Add a glm::ivec2 to the output + * + * @param val Value to add + * @return SerDes& This object + */ SerDes &operator<<(glm::ivec2 val) { size_t sz = sizeof(val.x); if (!extendBy(sz*2)) { @@ -271,6 +453,12 @@ class SerDes { return *this; } + /** + * @brief Read a glm::ivec2 from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ SerDes &operator>>(glm::ivec2 &val) { size_t sz = sizeof(val.x); @@ -287,6 +475,14 @@ class SerDes { } /////////////////////////////////////////////////////////// + /** + * @brief Write a null-terminated string into the buffer + * + * The `\0` at the end of the string is also written. + * + * @param val Value to write + * @return SerDes& This object + */ SerDes &operator<<(const char *val) { size_t len = strlen(val)+1; extendBy(len); @@ -295,6 +491,14 @@ class SerDes { return *this; } + /** + * @brief Write a QString into the buffer + * + * The string is encoded in UTF-8 and the `\0` at the end of the string is also written. + * + * @param val Value to write + * @return SerDes& This object + */ SerDes &operator<<(const QString &val) { return *this << val.toUtf8().constData(); } @@ -302,6 +506,17 @@ class SerDes { /////////////////////////////////////////////////////////// + /** + * @brief Pointer to the start of the internal buffer. + * + * The allocated amount can be found with capacity(). + * + * The end of the stored data can be found with length(). + * + * @return Pointer to buffer + */ + char *buffer() const { return _store; } + /** * @brief Current position in the buffer. Starts at 0. * @@ -351,6 +566,16 @@ class SerDes { */ void rewind() { _pos = 0; _overflow = false; } + /** + * @brief Dump the contents of this object into QDebug + * + * This produces a dump of the internal state, and an ASCII/hex dump of + * the contents, for debugging. + * + * @param debug Qt QDebug stream + * @param ds This object + * @return QDebug + */ friend QDebug operator<<(QDebug debug, const SerDes &ds); private: From 194eebf57c4c475752a962884f660ea18c42351d Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Sun, 12 Jun 2022 13:48:08 +0200 Subject: [PATCH 14/35] Finish Texture_ktx conversion to serializer --- libraries/gpu/src/gpu/Texture.h | 27 ++++++++ libraries/gpu/src/gpu/Texture_ktx.cpp | 92 +++++---------------------- libraries/shared/src/SerDes.h | 36 +++++++++++ tests/shared/src/SerializerTests.cpp | 8 +++ 4 files changed, 88 insertions(+), 75 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 5143cc1c23..2e7f8c62ca 100644 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -92,6 +92,33 @@ public: }; typedef std::shared_ptr< SphericalHarmonics > SHPointer; + +inline SerDes &operator<<(SerDes &ser, const SphericalHarmonics &h) { + ser << h.L00 << h.spare0; + ser << h.L1m1 << h.spare1; + ser << h.L10 << h.spare2; + ser << h.L11 << h.spare3; + ser << h.L2m2 << h.spare4; + ser << h.L2m1 << h.spare5; + ser << h.L20 << h.spare6; + ser << h.L21 << h.spare7; + ser << h.L22 << h.spare8; + return ser; +} + +inline SerDes &operator>>(SerDes &des, SphericalHarmonics &h) { + des >> h.L00 >> h.spare0; + des >> h.L1m1 >> h.spare1; + des >> h.L10 >> h.spare2; + des >> h.L11 >> h.spare3; + des >> h.L2m2 >> h.spare4; + des >> h.L2m1 >> h.spare5; + des >> h.L20 >> h.spare6; + des >> h.L21 >> h.spare7; + des >> h.L22 >> h.spare8; + return des; +} + class Sampler { public: diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 748ec1a702..dc3d23948d 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -43,7 +43,7 @@ struct GPUKTXPayload { TextureUsageType _usageType; glm::ivec2 _originalSize { 0, 0 }; - void serialize2(SerDes &ser) { + void serialize(SerDes &ser) { ser << CURRENT_VERSION; ser << _samplerDesc; @@ -55,7 +55,7 @@ struct GPUKTXPayload { ser.addPadding(PADDING); } - bool unserialize2(SerDes &dsr) { + bool unserialize(SerDes &dsr) { Version version = 0; uint32 usageData; uint8_t usagetype = 0; @@ -80,60 +80,6 @@ struct GPUKTXPayload { return true; } - - Byte* serialize(Byte* data) const { - *(Version*)data = CURRENT_VERSION; - data += sizeof(Version); - - memcpy(data, &_samplerDesc, sizeof(Sampler::Desc)); - data += sizeof(Sampler::Desc); - - // We can't copy the bitset in Texture::Usage in a crossplateform manner - // So serialize it manually - uint32 usageData = _usage._flags.to_ulong(); - memcpy(data, &usageData, sizeof(uint32)); - data += sizeof(uint32); - - memcpy(data, &_usageType, sizeof(TextureUsageType)); - data += sizeof(TextureUsageType); - - memcpy(data, glm::value_ptr(_originalSize), sizeof(glm::ivec2)); - data += sizeof(glm::ivec2); - - return data + PADDING; - } - - bool unserialize(const Byte* data, size_t size) { - Version version = *(const Version*)data; - data += sizeof(Version); - - if (version > CURRENT_VERSION) { - // If we try to load a version that we don't know how to parse, - // it will render incorrectly - return false; - } - - memcpy(&_samplerDesc, data, sizeof(Sampler::Desc)); - data += sizeof(Sampler::Desc); - - // We can't copy the bitset in Texture::Usage in a crossplateform manner - // So unserialize it manually - uint32 usageData; - memcpy(&usageData, data, sizeof(uint32)); - _usage = Texture::Usage(usageData); - data += sizeof(uint32); - - memcpy(&_usageType, data, sizeof(TextureUsageType)); - data += sizeof(TextureUsageType); - - if (version >= 2) { - memcpy(&_originalSize, data, sizeof(glm::ivec2)); - data += sizeof(glm::ivec2); - } - - return true; - } - static bool isGPUKTX(const ktx::KeyValue& val) { return (val._key.compare(KEY) == 0); } @@ -143,8 +89,7 @@ struct GPUKTXPayload { if (found != keyValues.end()) { auto value = found->_value; SerDes dsr(value.data(), value.size()); - return payload.unserialize2(dsr); - //return payload.unserialize(value.data(), value.size()); + return payload.unserialize(dsr); } return false; } @@ -164,29 +109,24 @@ struct IrradianceKTXPayload { SphericalHarmonics _irradianceSH; - Byte* serialize(Byte* data) const { - *(Version*)data = CURRENT_VERSION; - data += sizeof(Version); - - memcpy(data, &_irradianceSH, sizeof(SphericalHarmonics)); - data += sizeof(SphericalHarmonics); - - return data + PADDING; + void serialize(SerDes &ser) const { + ser << CURRENT_VERSION; + ser << _irradianceSH; + ser.addPadding(PADDING); } - bool unserialize(const Byte* data, size_t size) { - if (size != SIZE) { + bool unserialize(SerDes &des) { + Version version; + if (des.length() != SIZE) { return false; } - Version version = *(const Version*)data; + des >> version; if (version != CURRENT_VERSION) { return false; } - data += sizeof(Version); - - memcpy(&_irradianceSH, data, sizeof(SphericalHarmonics)); + des >> _irradianceSH; return true; } @@ -198,7 +138,8 @@ struct IrradianceKTXPayload { auto found = std::find_if(keyValues.begin(), keyValues.end(), isIrradianceKTX); if (found != keyValues.end()) { auto value = found->_value; - return payload.unserialize(value.data(), value.size()); + SerDes des(value.data(), value.size()); + return payload.unserialize(des); } return false; } @@ -510,7 +451,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture, const glm::ivec Byte keyvalPayload[GPUKTXPayload::SIZE]; SerDes ser(keyvalPayload, sizeof(keyvalPayload)); - gpuKeyval.serialize2(ser); + gpuKeyval.serialize(ser); ktx::KeyValues keyValues; keyValues.emplace_back(GPUKTXPayload::KEY, (uint32)GPUKTXPayload::SIZE, (ktx::Byte*) &keyvalPayload); @@ -520,7 +461,8 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture, const glm::ivec irradianceKeyval._irradianceSH = *texture.getIrradiance(); Byte irradianceKeyvalPayload[IrradianceKTXPayload::SIZE]; - irradianceKeyval.serialize(irradianceKeyvalPayload); + SerDes ser(irradianceKeyvalPayload, sizeof(irradianceKeyvalPayload)); + irradianceKeyval.serialize(ser); keyValues.emplace_back(IrradianceKTXPayload::KEY, (uint32)IrradianceKTXPayload::SIZE, (ktx::Byte*) &irradianceKeyvalPayload); } diff --git a/libraries/shared/src/SerDes.h b/libraries/shared/src/SerDes.h index fe445fe854..356bb45522 100644 --- a/libraries/shared/src/SerDes.h +++ b/libraries/shared/src/SerDes.h @@ -344,6 +344,42 @@ class SerDes { /////////////////////////////////////////////////////////// + /** + * @brief Add an float to the output + * + * @param val Value to add + * @return SerDes& This object + */ + SerDes &operator<<(float val) { + if (!extendBy(sizeof(val))) { + return *this; + } + + memcpy(&_store[_pos], (char*)&val, sizeof(val)); + _pos += sizeof(val); + return *this; + } + + /** + * @brief Read an float from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + SerDes &operator>>(float &val) { + if ( _pos + sizeof(val) <= _length ) { + memcpy((char*)&val, &_store[_pos], sizeof(val)); + _pos += sizeof(val); + } else { + _overflow = true; + qCritical() << "Deserializer trying to read past end of input, reading float from position " << _pos << ", length " << _length; + } + return *this; + } + + /////////////////////////////////////////////////////////// + + /** * @brief Add an glm::vec3 to the output * diff --git a/tests/shared/src/SerializerTests.cpp b/tests/shared/src/SerializerTests.cpp index a1b3a37544..89598a3c2a 100644 --- a/tests/shared/src/SerializerTests.cpp +++ b/tests/shared/src/SerializerTests.cpp @@ -54,6 +54,9 @@ void SerializerTests::testAdd() { s << v; QCOMPARE(s.length(), 40); + s << 1.2345f; + QCOMPARE(s.length(), 44); + qDebug() << s; } @@ -66,6 +69,8 @@ void SerializerTests::testAddAndRead() { glm::vec4 v4_b; glm::ivec2 iv2_a{10, 24}; glm::ivec2 iv2_b; + float f_a = 1.2345f; + float f_b; s << (qint8)1; s << (qint16)0xaabb; @@ -73,6 +78,7 @@ void SerializerTests::testAddAndRead() { s << v3_a; s << v4_a; s << iv2_a; + s << f_a; qint8 i8; qint16 i16; @@ -86,6 +92,7 @@ void SerializerTests::testAddAndRead() { s >> v3_b; s >> v4_b; s >> iv2_b; + s >> f_b; qDebug() << s; @@ -95,6 +102,7 @@ void SerializerTests::testAddAndRead() { QCOMPARE(v3_a, v3_b); QCOMPARE(v4_a, v4_b); QCOMPARE(iv2_a, iv2_b); + QCOMPARE(f_a, f_b); } void SerializerTests::testReadPastEnd() { From 189f91b05d6f5b9241cc4cef6c4613c36805b64f Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Thu, 23 Jun 2022 21:24:28 +0200 Subject: [PATCH 15/35] Clear blendshapes without memset Avoids warning due to glm::vec3 not being trivially copyable --- libraries/render-utils/src/Model.cpp | 4 +++- libraries/shared/src/BlendshapeConstants.h | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 69a593ed09..6ad2534038 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1949,7 +1949,9 @@ void Blender::run() { blendedMeshSizes.push_back(numVertsInMesh); // initialize offsets to zero - memset(unpackedBlendshapeOffsets.data(), 0, numVertsInMesh * sizeof(BlendshapeOffsetUnpacked)); + for(BlendshapeOffsetUnpacked &bou : unpackedBlendshapeOffsets) { + bou.clear(); + } // for each blendshape in this mesh, accumulate the offsets into unpackedBlendshapeOffsets. const float NORMAL_COEFFICIENT_SCALE = 0.01f; diff --git a/libraries/shared/src/BlendshapeConstants.h b/libraries/shared/src/BlendshapeConstants.h index 596e7df4ee..b741059146 100644 --- a/libraries/shared/src/BlendshapeConstants.h +++ b/libraries/shared/src/BlendshapeConstants.h @@ -122,6 +122,25 @@ struct BlendshapeOffsetUnpacked { float positionOffsetX, positionOffsetY, positionOffsetZ; float normalOffsetX, normalOffsetY, normalOffsetZ; float tangentOffsetX, tangentOffsetY, tangentOffsetZ; + + /** + * @brief Set all components of all the offsets to zero + * + * @note glm::vec3 is not trivially copyable, so it's not correct to clear it with memset. + */ + void clear() { + positionOffsetX = 0.0f; + positionOffsetY = 0.0f; + positionOffsetZ = 0.0f; + + normalOffsetX = 0.0f; + normalOffsetY = 0.0f; + normalOffsetZ = 0.0f; + + tangentOffsetX = 0.0f; + tangentOffsetY = 0.0f; + tangentOffsetZ = 0.0f; + } }; using BlendshapeOffset = BlendshapeOffsetPacked; From 201c531edbff95d9235595c891cf13b655b6e25b Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Thu, 23 Jun 2022 21:25:07 +0200 Subject: [PATCH 16/35] Split SerDes into DataSerializer and DataDeserializer Single class wasn't working well because deserialization may need to be done on const data. With the split, the deserializer part can work with const data without issues. Also cleaned things up a bit. --- libraries/gpu/src/gpu/Texture.h | 10 +- libraries/gpu/src/gpu/Texture_ktx.cpp | 16 +- libraries/octree/src/OctreePacketData.cpp | 9 +- libraries/shared/src/AACube.h | 16 + libraries/shared/src/SerDes.cpp | 8 +- libraries/shared/src/SerDes.h | 622 +++++++++++++--------- 6 files changed, 421 insertions(+), 260 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 2e7f8c62ca..8370462c5a 100644 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -93,7 +93,7 @@ public: typedef std::shared_ptr< SphericalHarmonics > SHPointer; -inline SerDes &operator<<(SerDes &ser, const SphericalHarmonics &h) { +inline DataSerializer &operator<<(DataSerializer &ser, const SphericalHarmonics &h) { ser << h.L00 << h.spare0; ser << h.L1m1 << h.spare1; ser << h.L10 << h.spare2; @@ -106,7 +106,7 @@ inline SerDes &operator<<(SerDes &ser, const SphericalHarmonics &h) { return ser; } -inline SerDes &operator>>(SerDes &des, SphericalHarmonics &h) { +inline DataDeserializer &operator>>(DataDeserializer &des, SphericalHarmonics &h) { des >> h.L00 >> h.spare0; des >> h.L1m1 >> h.spare1; des >> h.L10 >> h.spare2; @@ -185,7 +185,7 @@ public: _maxMip == other._maxMip; } - SerDes &operator<<(SerDes &dsd) { + DataSerializer &operator<<(DataSerializer &dsd) { dsd << _borderColor; dsd << _maxAnisotropy; dsd << _filter; @@ -237,7 +237,7 @@ protected: friend class Deserializer; }; -inline SerDes &operator<<(SerDes &ser, const Sampler::Desc &d) { +inline DataSerializer &operator<<(DataSerializer &ser, const Sampler::Desc &d) { ser << d._borderColor; ser << d._maxAnisotropy; ser << d._filter; @@ -251,7 +251,7 @@ inline SerDes &operator<<(SerDes &ser, const Sampler::Desc &d) { return ser; } -inline SerDes &operator>>(SerDes &dsr, Sampler::Desc &d) { +inline DataDeserializer &operator>>(DataDeserializer &dsr, Sampler::Desc &d) { dsr >> d._borderColor; dsr >> d._maxAnisotropy; dsr >> d._filter; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index dc3d23948d..6845aeb55b 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -43,7 +43,7 @@ struct GPUKTXPayload { TextureUsageType _usageType; glm::ivec2 _originalSize { 0, 0 }; - void serialize(SerDes &ser) { + void serialize(DataSerializer &ser) { ser << CURRENT_VERSION; ser << _samplerDesc; @@ -55,7 +55,7 @@ struct GPUKTXPayload { ser.addPadding(PADDING); } - bool unserialize(SerDes &dsr) { + bool unserialize(DataDeserializer &dsr) { Version version = 0; uint32 usageData; uint8_t usagetype = 0; @@ -88,7 +88,7 @@ struct GPUKTXPayload { auto found = std::find_if(keyValues.begin(), keyValues.end(), isGPUKTX); if (found != keyValues.end()) { auto value = found->_value; - SerDes dsr(value.data(), value.size()); + DataDeserializer dsr(value.data(), value.size()); return payload.unserialize(dsr); } return false; @@ -109,13 +109,13 @@ struct IrradianceKTXPayload { SphericalHarmonics _irradianceSH; - void serialize(SerDes &ser) const { + void serialize(DataSerializer &ser) const { ser << CURRENT_VERSION; ser << _irradianceSH; ser.addPadding(PADDING); } - bool unserialize(SerDes &des) { + bool unserialize(DataDeserializer &des) { Version version; if (des.length() != SIZE) { return false; @@ -138,7 +138,7 @@ struct IrradianceKTXPayload { auto found = std::find_if(keyValues.begin(), keyValues.end(), isIrradianceKTX); if (found != keyValues.end()) { auto value = found->_value; - SerDes des(value.data(), value.size()); + DataDeserializer des(value.data(), value.size()); return payload.unserialize(des); } return false; @@ -449,7 +449,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture, const glm::ivec gpuKeyval._originalSize = originalSize; Byte keyvalPayload[GPUKTXPayload::SIZE]; - SerDes ser(keyvalPayload, sizeof(keyvalPayload)); + DataSerializer ser(keyvalPayload, sizeof(keyvalPayload)); gpuKeyval.serialize(ser); @@ -461,7 +461,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture, const glm::ivec irradianceKeyval._irradianceSH = *texture.getIrradiance(); Byte irradianceKeyvalPayload[IrradianceKTXPayload::SIZE]; - SerDes ser(irradianceKeyvalPayload, sizeof(irradianceKeyvalPayload)); + DataSerializer ser(irradianceKeyvalPayload, sizeof(irradianceKeyvalPayload)); irradianceKeyval.serialize(ser); keyValues.emplace_back(IrradianceKTXPayload::KEY, (uint32)IrradianceKTXPayload::SIZE, (ktx::Byte*) &irradianceKeyvalPayload); diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index c13d58226b..3745582728 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -17,6 +17,7 @@ #include "OctreeLogging.h" #include "NumericalConstants.h" #include +#include "SerDes.h" bool OctreePacketData::_debug = false; AtomicUIntStat OctreePacketData::_totalBytesOfOctalCodes { 0 }; @@ -847,10 +848,10 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QByteA } int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, AACube& result) { - aaCubeData cube; - memcpy(&cube, dataBytes, sizeof(aaCubeData)); - result = AACube(cube.corner, cube.scale); - return sizeof(aaCubeData); + DataDeserializer des(dataBytes, sizeof(aaCubeData)); + des >> result; + + return des.length(); } int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QRect& result) { diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h index 66b29e3185..27c424cadb 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -20,6 +20,7 @@ #include #include "BoxBase.h" +#include "SerDes.h" class AABox; class Extents; @@ -80,6 +81,10 @@ private: glm::vec3 _corner; float _scale; + + friend DataSerializer& operator<<(DataSerializer &ser, const AACube &cube); + friend DataDeserializer& operator>>(DataDeserializer &des, AACube &cube); + }; inline bool operator==(const AACube& a, const AACube& b) { @@ -99,5 +104,16 @@ inline QDebug operator<<(QDebug debug, const AACube& cube) { return debug; } +inline DataSerializer& operator<<(DataSerializer &ser, const AACube &cube) { + ser << cube._corner; + ser << cube._scale; + return ser; +} + +inline DataDeserializer& operator>>(DataDeserializer &des, AACube &cube) { + des >> cube._corner; + des >> cube._scale; + return des; +} #endif // hifi_AACube_h diff --git a/libraries/shared/src/SerDes.cpp b/libraries/shared/src/SerDes.cpp index c64430f201..044ea1fad2 100644 --- a/libraries/shared/src/SerDes.cpp +++ b/libraries/shared/src/SerDes.cpp @@ -12,10 +12,10 @@ #include #include "SerDes.h" -const int SerDes::DEFAULT_SIZE; -const char SerDes::PADDING_CHAR; +const int DataSerializer::DEFAULT_SIZE; +const char DataSerializer::PADDING_CHAR; -QDebug operator<<(QDebug debug, const SerDes &ds) { +QDebug operator<<(QDebug debug, const DataSerializer &ds) { debug << "{ capacity =" << ds.capacity() << "; length = " << ds.length() << "; pos = " << ds.pos() << "}"; debug << "\n"; @@ -54,7 +54,7 @@ QDebug operator<<(QDebug debug, const SerDes &ds) { } -void SerDes::changeAllocation(size_t new_size) { +void DataSerializer::changeAllocation(size_t new_size) { while ( _capacity < new_size) { _capacity *= 2; } diff --git a/libraries/shared/src/SerDes.h b/libraries/shared/src/SerDes.h index 356bb45522..ee38a87aa8 100644 --- a/libraries/shared/src/SerDes.h +++ b/libraries/shared/src/SerDes.h @@ -17,7 +17,7 @@ #include /** - * @brief Data serializer/deserializer + * @brief Data serializer * * When encoding, this class takes in data and encodes it into a buffer. No attempt is made to store version numbers, lengths, * or any other metadata. It's entirely up to the user to use the class in such a way that the process can be @@ -37,7 +37,7 @@ * uint16_t count = 1; * glm::vec3 pos{1.5, 2.0, 9.0}; * - * SerDes ser; + * Serializer ser; * ser << version; * ser << count; * ser << pos; @@ -45,44 +45,20 @@ * // Serialized data is in ser.buffer(), ser.length() long. * @endcode * - * Example of decoding: - * - * @code {.cpp} - * // Incoming data has been placed in: - * // char buffer[1024]; - * - * uint8_t version; - * uint16_t count; - * glm::vec3 pos; - * - * SerDes des(buffer, sizeof(buffer)); - * des >> version; - * des >> count; - * des >> pos; - * @endcode - * * This object should be modified directly to add support for any primitive and common datatypes in the code. To support serializing/deserializing * classes and structures, implement a `operator<<` and `operator>>` functions for that object, eg: * * @code {.cpp} - * SerDes &operator<<(SerDes &ser, const Object &o) { + * Serializer &operator<<(Serializer &ser, const Object &o) { * ser << o._borderColor; * ser << o._maxAnisotropy; * ser << o._filter; * return ser; * } - * - * SerDes &operator>>(SerDes &des, Object &o) { - * des >> o._borderColor; - * des >> o._maxAnisotropy; - * des >> o._filter; - * return des; - * } - * * @endcode * */ -class SerDes { +class DataSerializer { public: /** * @brief Default size for a dynamically allocated buffer. @@ -106,7 +82,7 @@ class SerDes { * * The buffer is SerDes::DEFAULT_SIZE bytes by default, and doubles in size every time the limit is reached. */ - SerDes() { + DataSerializer() { _capacity = DEFAULT_SIZE; _pos = 0; _length = 0; @@ -125,7 +101,7 @@ class SerDes { * @param externalStore External data store * @param storeLength Length of the data store */ - SerDes(char *externalStore, size_t storeLength) { + DataSerializer(char *externalStore, size_t storeLength) { _capacity = storeLength; _length = storeLength; _pos = 0; @@ -144,16 +120,16 @@ class SerDes { * @param externalStore External data store * @param storeLength Length of the data store */ - SerDes(uint8_t *externalStore, size_t storeLength) : SerDes((char*)externalStore, storeLength) { + DataSerializer(uint8_t *externalStore, size_t storeLength) : DataSerializer((char*)externalStore, storeLength) { } - SerDes(const SerDes &) = delete; - SerDes &operator=(const SerDes &) = delete; + DataSerializer(const DataSerializer &) = delete; + DataSerializer &operator=(const DataSerializer &) = delete; - ~SerDes() { + ~DataSerializer() { if (!_storeIsExternal) { delete[] _store; } @@ -169,7 +145,7 @@ class SerDes { * @param bytes Number of bytes to add */ void addPadding(size_t bytes) { - if (!extendBy(bytes)) { + if (!extendBy(bytes, "padding")) { return; } @@ -184,7 +160,7 @@ class SerDes { * @param c Character to add * @return SerDes& This object */ - SerDes &operator<<(uint8_t c) { + DataSerializer &operator<<(uint8_t c) { return *this << int8_t(c); } @@ -194,8 +170,8 @@ class SerDes { * @param c Character to add * @return SerDes& This object */ - SerDes &operator<<(int8_t c) { - if (!extendBy(1)) { + DataSerializer &operator<<(int8_t c) { + if (!extendBy(1, "int8_t")) { return *this; } @@ -203,32 +179,6 @@ class SerDes { return *this; } - /** - * @brief Read an uint8_t from the buffer - * - * @param c Character to read - * @return SerDes& This object - */ - SerDes &operator>>(uint8_t &c) { - return *this >> reinterpret_cast(c); - } - - /** - * @brief Read an int8_t from the buffer - * - * @param c Character to read - * @return SerDes& This object - */ - SerDes &operator>>(int8_t &c) { - if ( _pos < _length ) { - c = _store[_pos++]; - } else { - _overflow = true; - qCritical() << "Deserializer trying to read past end of input, reading 8 bits from position " << _pos << ", length " << _length; - } - - return *this; - } /////////////////////////////////////////////////////////// @@ -238,7 +188,7 @@ class SerDes { * @param val Value to add * @return SerDes& This object */ - SerDes &operator<<(uint16_t val) { + DataSerializer &operator<<(uint16_t val) { return *this << int16_t(val); } @@ -248,8 +198,8 @@ class SerDes { * @param val Value to add * @return SerDes& This object */ - SerDes &operator<<(int16_t val) { - if (!extendBy(sizeof(val))) { + DataSerializer &operator<<(int16_t val) { + if (!extendBy(sizeof(val), "int16_t")) { return *this; } @@ -258,33 +208,7 @@ class SerDes { return *this; } - /** - * @brief Read an uint16_t from the buffer - * - * @param val Value to read - * @return SerDes& This object - */ - SerDes &operator>>(uint16_t &val) { - return *this >> reinterpret_cast(val); - } - /** - * @brief Read an int16_t from the buffer - * - * @param val Value to read - * @return SerDes& This object - */ - SerDes &operator>>(int16_t &val) { - if ( _pos + sizeof(val) <= _length ) { - memcpy((char*)&val, &_store[_pos], sizeof(val)); - _pos += sizeof(val); - } else { - _overflow = true; - qCritical() << "Deserializer trying to read past end of input, reading 16 bits from position " << _pos << ", length " << _length; - } - - return *this; - } /////////////////////////////////////////////////////////// @@ -294,7 +218,7 @@ class SerDes { * @param val Value to add * @return SerDes& This object */ - SerDes &operator<<(uint32_t val) { + DataSerializer &operator<<(uint32_t val) { return *this << int32_t(val); } @@ -304,8 +228,8 @@ class SerDes { * @param val Value to add * @return SerDes& This object */ - SerDes &operator<<(int32_t val) { - if (!extendBy(sizeof(val))) { + DataSerializer &operator<<(int32_t val) { + if (!extendBy(sizeof(val), "int32_t")) { return *this; } @@ -314,33 +238,6 @@ class SerDes { return *this; } - /** - * @brief Read an uint32_t from the buffer - * - * @param val Value to read - * @return SerDes& This object - */ - SerDes &operator>>(uint32_t &val) { - return *this >> reinterpret_cast(val); - } - - /** - * @brief Read an int32_t from the buffer - * - * @param val Value to read - * @return SerDes& This object - */ - SerDes &operator>>(int32_t &val) { - if ( _pos + sizeof(val) <= _length ) { - memcpy((char*)&val, &_store[_pos], sizeof(val)); - _pos += sizeof(val); - } else { - _overflow = true; - qCritical() << "Deserializer trying to read past end of input, reading 32 bits from position " << _pos << ", length " << _length; - } - return *this; - } - /////////////////////////////////////////////////////////// @@ -350,33 +247,15 @@ class SerDes { * @param val Value to add * @return SerDes& This object */ - SerDes &operator<<(float val) { - if (!extendBy(sizeof(val))) { - return *this; - } - - memcpy(&_store[_pos], (char*)&val, sizeof(val)); - _pos += sizeof(val); - return *this; - } - - /** - * @brief Read an float from the buffer - * - * @param val Value to read - * @return SerDes& This object - */ - SerDes &operator>>(float &val) { - if ( _pos + sizeof(val) <= _length ) { - memcpy((char*)&val, &_store[_pos], sizeof(val)); + DataSerializer &operator<<(float val) { + if (extendBy(sizeof(val), "float")) { + memcpy(&_store[_pos], (char*)&val, sizeof(val)); _pos += sizeof(val); - } else { - _overflow = true; - qCritical() << "Deserializer trying to read past end of input, reading float from position " << _pos << ", length " << _length; } return *this; } + /////////////////////////////////////////////////////////// @@ -386,38 +265,14 @@ class SerDes { * @param val Value to add * @return SerDes& This object */ - SerDes &operator<<(glm::vec3 val) { + DataSerializer &operator<<(glm::vec3 val) { size_t sz = sizeof(val.x); - if (!extendBy(sz*3)) { - return *this; - } - - memcpy(&_store[_pos ], (char*)&val.x, sz); - memcpy(&_store[_pos + sz ], (char*)&val.y, sz); - memcpy(&_store[_pos + sz*2], (char*)&val.z, sz); - - _pos += sz*3; - return *this; - } - - /** - * @brief Read a glm::vec3 from the buffer - * - * @param val Value to read - * @return SerDes& This object - */ - SerDes &operator>>(glm::vec3 &val) { - size_t sz = sizeof(val.x); - - if ( _pos + sz*3 <= _length ) { - memcpy((char*)&val.x, &_store[_pos ], sz); - memcpy((char*)&val.y, &_store[_pos + sz ], sz); - memcpy((char*)&val.z, &_store[_pos + sz*2], sz); + if (extendBy(sz*3, "glm::vec3")) { + memcpy(&_store[_pos ], (char*)&val.x, sz); + memcpy(&_store[_pos + sz ], (char*)&val.y, sz); + memcpy(&_store[_pos + sz*2], (char*)&val.z, sz); _pos += sz*3; - } else { - _overflow = true; - qCritical() << "Deserializer trying to read past end of input, reading glm::vec3 from position " << _pos << ", length " << _length; } return *this; } @@ -430,40 +285,15 @@ class SerDes { * @param val Value to add * @return SerDes& This object */ - SerDes &operator<<(glm::vec4 val) { + DataSerializer &operator<<(glm::vec4 val) { size_t sz = sizeof(val.x); - if (!extendBy(sz*4)) { - return *this; - } - - memcpy(&_store[_pos ], (char*)&val.x, sz); - memcpy(&_store[_pos + sz ], (char*)&val.y, sz); - memcpy(&_store[_pos + sz*2], (char*)&val.z, sz); - memcpy(&_store[_pos + sz*3], (char*)&val.w, sz); - - _pos += sz*4; - return *this; - } - - /** - * @brief Read a glm::vec4 from the buffer - * - * @param val Value to read - * @return SerDes& This object - */ - SerDes &operator>>(glm::vec4 &val) { - size_t sz = sizeof(val.x); - - if ( _pos + sz*4 <= _length ) { - memcpy((char*)&val.x, &_store[_pos ], sz); - memcpy((char*)&val.y, &_store[_pos + sz ], sz); - memcpy((char*)&val.z, &_store[_pos + sz*2], sz); - memcpy((char*)&val.w, &_store[_pos + sz*3], sz); + if (extendBy(sz*4, "glm::vec4")) { + memcpy(&_store[_pos ], (char*)&val.x, sz); + memcpy(&_store[_pos + sz ], (char*)&val.y, sz); + memcpy(&_store[_pos + sz*2], (char*)&val.z, sz); + memcpy(&_store[_pos + sz*3], (char*)&val.w, sz); _pos += sz*4; - } else { - _overflow = true; - qCritical() << "Deserializer trying to read past end of input, reading glm::vec3 from position " << _pos << ", length " << _length; } return *this; } @@ -476,39 +306,18 @@ class SerDes { * @param val Value to add * @return SerDes& This object */ - SerDes &operator<<(glm::ivec2 val) { + DataSerializer &operator<<(glm::ivec2 val) { size_t sz = sizeof(val.x); - if (!extendBy(sz*2)) { - return *this; - } - - memcpy(&_store[_pos ], (char*)&val.x, sz); - memcpy(&_store[_pos + sz ], (char*)&val.y, sz); - - _pos += sz*2; - return *this; - } - - /** - * @brief Read a glm::ivec2 from the buffer - * - * @param val Value to read - * @return SerDes& This object - */ - SerDes &operator>>(glm::ivec2 &val) { - size_t sz = sizeof(val.x); - - if ( _pos + sz*2 <= _length ) { - memcpy((char*)&val.x, &_store[_pos ], sz); - memcpy((char*)&val.y, &_store[_pos + sz ], sz); + if (extendBy(sz*2, "glm::ivec2")) { + memcpy(&_store[_pos ], (char*)&val.x, sz); + memcpy(&_store[_pos + sz ], (char*)&val.y, sz); _pos += sz*2; - } else { - _overflow = true; - qCritical() << "Deserializer trying to read past end of input, reading glm::ivec2 from position " << _pos << ", length " << _length; } return *this; } + + /////////////////////////////////////////////////////////// /** @@ -519,11 +328,12 @@ class SerDes { * @param val Value to write * @return SerDes& This object */ - SerDes &operator<<(const char *val) { + DataSerializer &operator<<(const char *val) { size_t len = strlen(val)+1; - extendBy(len); - memcpy(&_store[_pos], val, len); - _pos += len; + if (extendBy(len, "string")) { + memcpy(&_store[_pos], val, len); + _pos += len; + } return *this; } @@ -535,7 +345,7 @@ class SerDes { * @param val Value to write * @return SerDes& This object */ - SerDes &operator<<(const QString &val) { + DataSerializer &operator<<(const QString &val) { return *this << val.toUtf8().constData(); } @@ -612,14 +422,15 @@ class SerDes { * @param ds This object * @return QDebug */ - friend QDebug operator<<(QDebug debug, const SerDes &ds); + friend QDebug operator<<(QDebug debug, const DataSerializer &ds); private: - bool extendBy(size_t bytes) { + bool extendBy(size_t bytes, const QString &type_name) { //qDebug() << "Extend by" << bytes; if ( _capacity < _length + bytes) { if ( _storeIsExternal ) { + qCritical() << "Serializer trying to write past end of input, writing" << bytes << "bytes for" << type_name << " from position " << _pos << ", length " << _length; _overflow = true; return false; } @@ -641,3 +452,336 @@ class SerDes { size_t _length = 0; size_t _pos = 0; }; + +/** + * @brief Data deserializer + * + * This class operates on a fixed size buffer. If an attempt to read past the end is made, the read fails, + * and the overflow flag is set. + * + * The class was written for the maximum simplicity possible and inline friendliness. + * + * Example of decoding: + * + * @code {.cpp} + * // Incoming data has been placed in: + * // char buffer[1024]; + * + * uint8_t version; + * uint16_t count; + * glm::vec3 pos; + * + * Deserializer des(buffer, sizeof(buffer)); + * des >> version; + * des >> count; + * des >> pos; + * @endcode + * + * This object should be modified directly to add support for any primitive and common datatypes in the code. To support deserializing + * classes and structures, implement an `operator>>` function for that object, eg: + * + * @code {.cpp} + * Deserializer &operator>>(Deserializer &des, Object &o) { + * des >> o._borderColor; + * des >> o._maxAnisotropy; + * des >> o._filter; + * return des; + * } + * @endcode + * + */ +class DataDeserializer { + public: + /** + * @brief Construct a Deserializer + * * + * @param externalStore External data store + * @param storeLength Length of the data store + */ + DataDeserializer(const char *externalStore, size_t storeLength) { + _length = storeLength; + _pos = 0; + _store = externalStore; + } + + /** + * @brief Construct a Deserializer + * + * @param externalStore External data store + * @param storeLength Length of the data store + */ + DataDeserializer(const uint8_t *externalStore, size_t storeLength) : DataDeserializer((const char*)externalStore, storeLength) { + + } + + /** + * @brief Construct a new Deserializer reading data from a Serializer + * + * This is a convenience function for testing. + * + * @param serializer Serializer with data + */ + DataDeserializer(const DataSerializer &serializer) : DataDeserializer(serializer.buffer(), serializer.length()) { + + } + + /** + * @brief Skips padding in the input + * + * @param bytes Number of bytes to skip + */ + void skipPadding(size_t bytes) { + if (!canAdvanceBy(bytes, "padding")) { + return; + } + + _pos += bytes; + } + + + /** + * @brief Read an uint8_t from the buffer + * + * @param c Character to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(uint8_t &c) { + return *this >> reinterpret_cast(c); + } + + /** + * @brief Read an int8_t from the buffer + * + * @param c Character to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(int8_t &c) { + if ( canAdvanceBy(1, "int8_t") ) { + c = _store[_pos++]; + } + + return *this; + } + + /////////////////////////////////////////////////////////// + + /** + * @brief Read an uint16_t from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(uint16_t &val) { + return *this >> reinterpret_cast(val); + } + + /** + * @brief Read an int16_t from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(int16_t &val) { + if ( canAdvanceBy(sizeof(val), "int16_t") ) { + memcpy((char*)&val, &_store[_pos], sizeof(val)); + _pos += sizeof(val); + } + + return *this; + } + + /////////////////////////////////////////////////////////// + + /** + * @brief Read an uint32_t from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(uint32_t &val) { + return *this >> reinterpret_cast(val); + } + + /** + * @brief Read an int32_t from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(int32_t &val) { + if ( canAdvanceBy(sizeof(val), "int32_t") ) { + memcpy((char*)&val, &_store[_pos], sizeof(val)); + _pos += sizeof(val); + } + return *this; + } + + + /////////////////////////////////////////////////////////// + + + /** + * @brief Read an float from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(float &val) { + if ( canAdvanceBy(sizeof(val), "float") ) { + memcpy((char*)&val, &_store[_pos], sizeof(val)); + _pos += sizeof(val); + } + return *this; + } + + /////////////////////////////////////////////////////////// + + + + + /** + * @brief Read a glm::vec3 from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(glm::vec3 &val) { + size_t sz = sizeof(val.x); + + if ( canAdvanceBy(sz*3, "glm::vec3") ) { + memcpy((char*)&val.x, &_store[_pos ], sz); + memcpy((char*)&val.y, &_store[_pos + sz ], sz); + memcpy((char*)&val.z, &_store[_pos + sz*2], sz); + + _pos += sz*3; + } + + return *this; + } + + /////////////////////////////////////////////////////////// + + + /** + * @brief Read a glm::vec4 from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(glm::vec4 &val) { + size_t sz = sizeof(val.x); + + if ( canAdvanceBy(sz*4, "glm::vec4")) { + memcpy((char*)&val.x, &_store[_pos ], sz); + memcpy((char*)&val.y, &_store[_pos + sz ], sz); + memcpy((char*)&val.z, &_store[_pos + sz*2], sz); + memcpy((char*)&val.w, &_store[_pos + sz*3], sz); + + _pos += sz*4; + } + return *this; + } + + /////////////////////////////////////////////////////////// + + + /** + * @brief Read a glm::ivec2 from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(glm::ivec2 &val) { + size_t sz = sizeof(val.x); + + if ( canAdvanceBy(sz*2, "glm::ivec2") ) { + memcpy((char*)&val.x, &_store[_pos ], sz); + memcpy((char*)&val.y, &_store[_pos + sz ], sz); + + _pos += sz*2; + } + + return *this; + } + + /////////////////////////////////////////////////////////// + + /** + * @brief Pointer to the start of the internal buffer. + * + * The allocated amount can be found with capacity(). + * + * The end of the stored data can be found with length(). + * + * @return Pointer to buffer + */ + const char *buffer() const { return _store; } + + /** + * @brief Current position in the buffer. Starts at 0. + * + * @return size_t + */ + size_t pos() const { return _pos; } + + /** + * @brief Last position that was written to in the buffer. Starts at 0. + * + * @return size_t + */ + size_t length() const { return _length; } + + /** + * @brief Whether there's any data in the buffer + * + * @return true Something has been written + * @return false The buffer is empty + */ + bool isEmpty() const { return _length == 0; } + + /** + * @brief The buffer size limit has been reached + * + * This can only return true for a statically allocated buffer. + * + * @return true Limit reached + * @return false There is still room + */ + bool isOverflow() const { return _overflow; } + + /** + * @brief Reset the serializer to the start, clear overflow bit. + * + */ + void rewind() { _pos = 0; _overflow = false; } + + /** + * @brief Dump the contents of this object into QDebug + * + * This produces a dump of the internal state, and an ASCII/hex dump of + * the contents, for debugging. + * + * @param debug Qt QDebug stream + * @param ds This object + * @return QDebug + */ + friend QDebug operator<<(QDebug debug, const DataDeserializer &ds); + + private: + bool canAdvanceBy(size_t bytes, const QString &type_name) { + //qDebug() << "Checking advance by" << bytes; + + if ( _length < _pos + bytes) { + qCritical() << "Deserializer trying to read past end of input, reading" << bytes << "bytes for" << type_name << "from position " << _pos << ". Max length " << _length; + _overflow = true; + return false; + } + + return true; + } + + const char *_store; + bool _overflow = false; + size_t _length = 0; + size_t _pos = 0; +}; From 2cef749183946b9392c541ad6e48bbbc06d9b3c5 Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Fri, 24 Jun 2022 14:25:20 +0200 Subject: [PATCH 17/35] Update test to work with the split serializer/deserializer --- libraries/shared/src/SerDes.cpp | 29 ++++++++++--- libraries/shared/src/SerDes.h | 4 +- tests/shared/src/SerializerTests.cpp | 65 +++++++++++++++------------- 3 files changed, 59 insertions(+), 39 deletions(-) diff --git a/libraries/shared/src/SerDes.cpp b/libraries/shared/src/SerDes.cpp index 044ea1fad2..1b30bf9f63 100644 --- a/libraries/shared/src/SerDes.cpp +++ b/libraries/shared/src/SerDes.cpp @@ -15,15 +15,13 @@ const int DataSerializer::DEFAULT_SIZE; const char DataSerializer::PADDING_CHAR; -QDebug operator<<(QDebug debug, const DataSerializer &ds) { - debug << "{ capacity =" << ds.capacity() << "; length = " << ds.length() << "; pos = " << ds.pos() << "}"; - debug << "\n"; - QString literal; +static void dumpHex(QDebug &debug, const char*buf, size_t len) { + QString literal; QString hex; - for(size_t i=0;i> i8; - s >> i16; - s >> i32; - s >> v3_b; - s >> v4_b; - s >> iv2_b; - s >> f_b; + d >> i8; + d >> i16; + d >> i32; + d >> v3_b; + d >> v4_b; + d >> iv2_b; + d >> f_b; - qDebug() << s; + qDebug() << d; QCOMPARE(i8, (qint8)1); QCOMPARE(i16, (qint16)0xaabb); @@ -106,22 +110,23 @@ void SerializerTests::testAddAndRead() { } void SerializerTests::testReadPastEnd() { - SerDes s; + DataSerializer s; qint8 i8; qint16 i16; s << (qint8)1; - s.rewind(); - s >> i8; - QCOMPARE(s.pos(), 1); - s.rewind(); - s >> i16; - QCOMPARE(s.pos(), 0); + DataDeserializer d(s); + d >> i8; + QCOMPARE(d.pos(), 1); + + d.rewind(); + d >> i16; + QCOMPARE(d.pos(), 0); } void SerializerTests::benchmarkEncodingDynamicAlloc() { QBENCHMARK { - SerDes s; + DataSerializer s; glm::vec3 v3_a{1.f, 3.1415f, 2.71828f}; glm::vec3 v3_b; glm::vec4 v4_a{3.1415f, 2.71828f, 1.4142f, 1.6180f}; @@ -142,7 +147,7 @@ void SerializerTests::benchmarkEncodingStaticAlloc() { char buf[1024]; QBENCHMARK { - SerDes s(buf, sizeof(buf)); + DataSerializer s(buf, sizeof(buf)); glm::vec3 v3_a{1.f, 3.1415f, 2.71828f}; glm::vec3 v3_b; glm::vec4 v4_a{3.1415f, 2.71828f, 1.4142f, 1.6180f}; @@ -161,7 +166,7 @@ void SerializerTests::benchmarkEncodingStaticAlloc() { void SerializerTests::benchmarkDecoding() { - SerDes s; + DataSerializer s; qint8 q8 = 1; qint16 q16 = 0xaabb; qint32 q32 = 0xccddeeff; @@ -182,13 +187,13 @@ void SerializerTests::benchmarkDecoding() { QBENCHMARK { - s.rewind(); - s >> q8; - s >> q16; - s >> q32; - s >> v3_a; - s >> v4_a; - s >> iv2_a; + DataDeserializer d(s); + d >> q8; + d >> q16; + d >> q32; + d >> v3_a; + d >> v4_a; + d >> iv2_a; } } From 3b797f57850fdf9a84c3631305b3778f0daf7d45 Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Sat, 9 Jul 2022 20:35:36 +0200 Subject: [PATCH 18/35] Debug code --- libraries/gpu/src/gpu/Texture_ktx.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 6845aeb55b..a7e7844d0b 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -47,12 +47,15 @@ struct GPUKTXPayload { ser << CURRENT_VERSION; ser << _samplerDesc; + qCWarning(gpulogging) << "Offsets: " << offsetof(struct GPUKTXPayload, _samplerDesc) << offsetof(struct GPUKTXPayload, _usage) << offsetof(struct GPUKTXPayload, _usageType) << offsetof(struct GPUKTXPayload, _originalSize); uint32 usageData = _usage._flags.to_ulong(); ser << usageData; ser << (char)_usageType; ser << _originalSize; ser.addPadding(PADDING); + + assert(ser.length() == GPUKTXPayload::SIZE); } bool unserialize(DataDeserializer &dsr) { @@ -65,6 +68,9 @@ struct GPUKTXPayload { if (version > CURRENT_VERSION) { // If we try to load a version that we don't know how to parse, // it will render incorrectly + qCWarning(gpulogging) << "KTX version" << version << "is newer than our own," << CURRENT_VERSION; + qCWarning(gpulogging) << "Offsets: " << offsetof(struct GPUKTXPayload, _samplerDesc) << offsetof(struct GPUKTXPayload, _usage) << offsetof(struct GPUKTXPayload, _usageType) << offsetof(struct GPUKTXPayload, _originalSize); + qCWarning(gpulogging) << dsr; return false; } From 42d6128d9eb995ffe0d94ab8c16807d3edcef594 Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Mon, 28 Nov 2022 01:28:27 +0100 Subject: [PATCH 19/35] Fix KTX issues with the serializer. * Stop trying to be compatible with the old format, and just bump the version number. * Add uint64_t support to serializer * A bit improved debug output from serializer * Add lastAdvance() function to ask the serializer how much data was added/read in the last operation. --- libraries/gpu/src/gpu/Texture_ktx.cpp | 30 +++++---- libraries/shared/src/SerDes.h | 94 +++++++++++++++++++++++++-- tests/shared/src/SerializerTests.cpp | 29 +++++++++ tests/shared/src/SerializerTests.h | 1 + 4 files changed, 136 insertions(+), 18 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index a7e7844d0b..d10fc87dd5 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -32,11 +32,8 @@ struct GPUKTXPayload { using Version = uint8; static const std::string KEY; - static const Version CURRENT_VERSION { 2 }; - static const size_t PADDING { 2 }; - static const size_t SIZE { sizeof(Version) + sizeof(Sampler::Desc) + sizeof(uint32) + sizeof(TextureUsageType) + sizeof(glm::ivec2) + PADDING }; - static_assert(GPUKTXPayload::SIZE == 44, "Packing size may differ between platforms"); - static_assert(GPUKTXPayload::SIZE % 4 == 0, "GPUKTXPayload is not 4 bytes aligned"); + static const Version CURRENT_VERSION { 3 }; + static const size_t SIZE { sizeof(Version) + sizeof(Sampler::Desc) + sizeof(uint64_t) + sizeof(TextureUsageType) + sizeof(glm::ivec2) }; Sampler::Desc _samplerDesc; Texture::Usage _usage; @@ -44,38 +41,43 @@ struct GPUKTXPayload { glm::ivec2 _originalSize { 0, 0 }; void serialize(DataSerializer &ser) { + ser << CURRENT_VERSION; + + ser << _samplerDesc; - qCWarning(gpulogging) << "Offsets: " << offsetof(struct GPUKTXPayload, _samplerDesc) << offsetof(struct GPUKTXPayload, _usage) << offsetof(struct GPUKTXPayload, _usageType) << offsetof(struct GPUKTXPayload, _originalSize); - uint32 usageData = _usage._flags.to_ulong(); + uint64_t usageData = _usage._flags.to_ulong(); ser << usageData; - - ser << (char)_usageType; + ser << ((uint8_t)_usageType); ser << _originalSize; - ser.addPadding(PADDING); + + // The +1 is here because we're adding the CURRENT_VERSION at the top, but since it's declared as a static + // const, it's not actually part of the class' size. assert(ser.length() == GPUKTXPayload::SIZE); } bool unserialize(DataDeserializer &dsr) { Version version = 0; - uint32 usageData; + uint64_t usageData = 0; uint8_t usagetype = 0; dsr >> version; - if (version > CURRENT_VERSION) { + if (version != CURRENT_VERSION) { // If we try to load a version that we don't know how to parse, // it will render incorrectly - qCWarning(gpulogging) << "KTX version" << version << "is newer than our own," << CURRENT_VERSION; - qCWarning(gpulogging) << "Offsets: " << offsetof(struct GPUKTXPayload, _samplerDesc) << offsetof(struct GPUKTXPayload, _usage) << offsetof(struct GPUKTXPayload, _usageType) << offsetof(struct GPUKTXPayload, _originalSize); + qCWarning(gpulogging) << "KTX version" << version << "is different than our own," << CURRENT_VERSION; qCWarning(gpulogging) << dsr; return false; } dsr >> _samplerDesc; + dsr >> usageData; + _usage._flags = gpu::Texture::Usage::Flags(usageData); + dsr >> usagetype; _usageType = (TextureUsageType)usagetype; diff --git a/libraries/shared/src/SerDes.h b/libraries/shared/src/SerDes.h index 365f14d79c..b0371d4b95 100644 --- a/libraries/shared/src/SerDes.h +++ b/libraries/shared/src/SerDes.h @@ -238,6 +238,33 @@ class DataSerializer { return *this; } + /////////////////////////////////////////////////////////// + + /** + * @brief Add an uint64_t to the output + * + * @param val Value to add + * @return SerDes& This object + */ + DataSerializer &operator<<(uint64_t val) { + return *this << int64_t(val); + } + + /** + * @brief Add an int64_t to the output + * + * @param val Value to add + * @return SerDes& This object + */ + DataSerializer &operator<<(int64_t val) { + if (!extendBy(sizeof(val), "int64_t")) { + return *this; + } + + memcpy(&_store[_pos], (char*)&val, sizeof(val)); + _pos += sizeof(val); + return *this; + } /////////////////////////////////////////////////////////// @@ -410,7 +437,18 @@ class DataSerializer { * @brief Reset the serializer to the start, clear overflow bit. * */ - void rewind() { _pos = 0; _overflow = false; } + void rewind() { _pos = 0; _overflow = false; _lastAdvance = 0; } + + + /** + * @brief Size of the last advance + * + * This can be used to get how many bytes were added in the last operation. + * It is reset on rewind() + * + * @return size_t + */ + size_t lastAdvance() const { return _lastAdvance; } /** * @brief Dump the contents of this object into QDebug @@ -430,7 +468,7 @@ class DataSerializer { if ( _capacity < _length + bytes) { if ( _storeIsExternal ) { - qCritical() << "Serializer trying to write past end of output buffer, writing" << bytes << "bytes for" << type_name << " from position " << _pos << ", length " << _length; + qCritical() << "Serializer trying to write past end of output buffer of" << _capacity << "bytes. Error writing" << bytes << "bytes for" << type_name << " from position " << _pos << ", length " << _length; _overflow = true; return false; } @@ -439,6 +477,7 @@ class DataSerializer { } _length += bytes; + _lastAdvance = bytes; return true; } @@ -451,6 +490,7 @@ class DataSerializer { size_t _capacity = 0; size_t _length = 0; size_t _pos = 0; + size_t _lastAdvance = 0; }; /** @@ -502,6 +542,7 @@ class DataDeserializer { _length = storeLength; _pos = 0; _store = externalStore; + _lastAdvance = 0; } /** @@ -536,6 +577,7 @@ class DataDeserializer { } _pos += bytes; + _lastAdvance = bytes; } @@ -558,6 +600,7 @@ class DataDeserializer { DataDeserializer &operator>>(int8_t &c) { if ( canAdvanceBy(1, "int8_t") ) { c = _store[_pos++]; + _lastAdvance = sizeof(c); } return *this; @@ -585,6 +628,7 @@ class DataDeserializer { if ( canAdvanceBy(sizeof(val), "int16_t") ) { memcpy((char*)&val, &_store[_pos], sizeof(val)); _pos += sizeof(val); + _lastAdvance = sizeof(val); } return *this; @@ -612,10 +656,37 @@ class DataDeserializer { if ( canAdvanceBy(sizeof(val), "int32_t") ) { memcpy((char*)&val, &_store[_pos], sizeof(val)); _pos += sizeof(val); + _lastAdvance = sizeof(val); } return *this; } + /////////////////////////////////////////////////////////// + + /** + * @brief Read an uint64_t from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(uint64_t &val) { + return *this >> reinterpret_cast(val); + } + + /** + * @brief Read an int64_t from the buffer + * + * @param val Value to read + * @return SerDes& This object + */ + DataDeserializer &operator>>(int64_t &val) { + if ( canAdvanceBy(sizeof(val), "int64_t") ) { + memcpy((char*)&val, &_store[_pos], sizeof(val)); + _pos += sizeof(val); + _lastAdvance = sizeof(val); + } + return *this; + } /////////////////////////////////////////////////////////// @@ -630,6 +701,7 @@ class DataDeserializer { if ( canAdvanceBy(sizeof(val), "float") ) { memcpy((char*)&val, &_store[_pos], sizeof(val)); _pos += sizeof(val); + _lastAdvance = sizeof(val); } return *this; } @@ -654,6 +726,7 @@ class DataDeserializer { memcpy((char*)&val.z, &_store[_pos + sz*2], sz); _pos += sz*3; + _lastAdvance = sz * 3; } return *this; @@ -678,6 +751,7 @@ class DataDeserializer { memcpy((char*)&val.w, &_store[_pos + sz*3], sz); _pos += sz*4; + _lastAdvance = sz*4; } return *this; } @@ -699,6 +773,7 @@ class DataDeserializer { memcpy((char*)&val.y, &_store[_pos + sz ], sz); _pos += sz*2; + _lastAdvance = sz * 2; } return *this; @@ -753,7 +828,17 @@ class DataDeserializer { * @brief Reset the serializer to the start, clear overflow bit. * */ - void rewind() { _pos = 0; _overflow = false; } + void rewind() { _pos = 0; _overflow = false; _lastAdvance = 0; } + + /** + * @brief Size of the last advance + * + * This can be used to get how many bytes were added in the last operation. + * It is reset on rewind() + * + * @return size_t + */ + size_t lastAdvance() const { return _lastAdvance; } /** * @brief Dump the contents of this object into QDebug @@ -772,7 +857,7 @@ class DataDeserializer { //qDebug() << "Checking advance by" << bytes; if ( _length < _pos + bytes) { - qCritical() << "Deserializer trying to read past end of input, reading" << bytes << "bytes for" << type_name << "from position " << _pos << ". Max length " << _length; + qCritical() << "Deserializer trying to read past end of input buffer of" << _length << "bytes, reading" << bytes << "bytes for" << type_name << "from position " << _pos; _overflow = true; return false; } @@ -784,4 +869,5 @@ class DataDeserializer { bool _overflow = false; size_t _length = 0; size_t _pos = 0; + size_t _lastAdvance = 0; }; diff --git a/tests/shared/src/SerializerTests.cpp b/tests/shared/src/SerializerTests.cpp index c52a9df69c..ae0198f573 100644 --- a/tests/shared/src/SerializerTests.cpp +++ b/tests/shared/src/SerializerTests.cpp @@ -124,6 +124,35 @@ void SerializerTests::testReadPastEnd() { QCOMPARE(d.pos(), 0); } +void SerializerTests::testWritePastEnd() { + qint8 i8 = 255; + qint16 i16 = 65535; + + + char buf[16]; + + + // 1 byte buffer, we can write 1 byte + memset(buf, 0, sizeof(buf)); + DataSerializer s1(buf, 1); + s1 << i8; + QCOMPARE(s1.pos(), 1); + QCOMPARE(s1.isOverflow(), false); + QCOMPARE(buf[0], i8); + + // 1 byte buffer, we can't write 2 bytes + memset(buf, 0, sizeof(buf)); + DataSerializer s2(buf, 1); + s2 << i16; + QCOMPARE(s2.pos(), 0); + QCOMPARE(s2.isOverflow(), true); + QCOMPARE(buf[0], 0); // We didn't write + QCOMPARE(buf[1], 0); +} + + + + void SerializerTests::benchmarkEncodingDynamicAlloc() { QBENCHMARK { DataSerializer s; diff --git a/tests/shared/src/SerializerTests.h b/tests/shared/src/SerializerTests.h index 3a3a3217d2..55da84c41a 100644 --- a/tests/shared/src/SerializerTests.h +++ b/tests/shared/src/SerializerTests.h @@ -21,6 +21,7 @@ private slots: void testAdd(); void testAddAndRead(); void testReadPastEnd(); + void testWritePastEnd(); void benchmarkEncodingDynamicAlloc(); void benchmarkEncodingStaticAlloc(); void benchmarkDecoding(); From 1c06c8652c8a3204c207ff0cff91e977be83d96b Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Fri, 2 Dec 2022 01:54:22 +0100 Subject: [PATCH 20/35] Add serializer size tracker, update documentation --- libraries/gpu/src/gpu/Texture.h | 6 +++ libraries/shared/src/SerDes.h | 86 +++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 8370462c5a..05ad70b872 100644 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -94,6 +94,8 @@ typedef std::shared_ptr< SphericalHarmonics > SHPointer; inline DataSerializer &operator<<(DataSerializer &ser, const SphericalHarmonics &h) { + DataSerializer::SizeTracker tracker(ser); + ser << h.L00 << h.spare0; ser << h.L1m1 << h.spare1; ser << h.L10 << h.spare2; @@ -107,6 +109,8 @@ inline DataSerializer &operator<<(DataSerializer &ser, const SphericalHarmonics } inline DataDeserializer &operator>>(DataDeserializer &des, SphericalHarmonics &h) { + DataDeserializer::SizeTracker tracker(des); + des >> h.L00 >> h.spare0; des >> h.L1m1 >> h.spare1; des >> h.L10 >> h.spare2; @@ -238,6 +242,7 @@ protected: }; inline DataSerializer &operator<<(DataSerializer &ser, const Sampler::Desc &d) { + DataSerializer::SizeTracker tracker(ser); ser << d._borderColor; ser << d._maxAnisotropy; ser << d._filter; @@ -252,6 +257,7 @@ inline DataSerializer &operator<<(DataSerializer &ser, const Sampler::Desc &d) { } inline DataDeserializer &operator>>(DataDeserializer &dsr, Sampler::Desc &d) { + DataDeserializer::SizeTracker tracker(dsr); dsr >> d._borderColor; dsr >> d._maxAnisotropy; dsr >> d._filter; diff --git a/libraries/shared/src/SerDes.h b/libraries/shared/src/SerDes.h index b0371d4b95..4c97ef193b 100644 --- a/libraries/shared/src/SerDes.h +++ b/libraries/shared/src/SerDes.h @@ -49,7 +49,7 @@ * classes and structures, implement a `operator<<` and `operator>>` functions for that object, eg: * * @code {.cpp} - * Serializer &operator<<(Serializer &ser, const Object &o) { + * DataSerializer &operator<<(DataSerializer &ser, const Object &o) { * ser << o._borderColor; * ser << o._maxAnisotropy; * ser << o._filter; @@ -60,6 +60,46 @@ */ class DataSerializer { public: + + /** + * @brief RAII tracker of advance position + * + * When a custom operator<< is implemented for DataSserializer, + * this class allows to easily keep track of how much data has been added and + * adjust the parent's lastAdvance() count on this class' destruction. + * + * @code {.cpp} + * DataSerializer &operator<<(DataSerializer &ser, const Object &o) { + * DataSerializer::SizeTracker tracker(ser); + * + * ser << o._borderColor; + * ser << o._maxAnisotropy; + * ser << o._filter; + * return ser; + * } + * @endcode + */ + class SizeTracker { + public: + SizeTracker(DataSerializer &parent) : _parent(parent) { + _start_pos = _parent.pos(); + } + + ~SizeTracker() { + size_t cur_pos = _parent.pos(); + + if ( cur_pos >= _start_pos ) { + _parent._lastAdvance = cur_pos - _start_pos; + } else { + _parent._lastAdvance = 0; + } + } + + private: + DataSerializer &_parent; + size_t _start_pos = 0; + }; + /** * @brief Default size for a dynamically allocated buffer. * @@ -511,7 +551,7 @@ class DataSerializer { * uint16_t count; * glm::vec3 pos; * - * Deserializer des(buffer, sizeof(buffer)); + * DataDeserializer des(buffer, sizeof(buffer)); * des >> version; * des >> count; * des >> pos; @@ -521,7 +561,7 @@ class DataSerializer { * classes and structures, implement an `operator>>` function for that object, eg: * * @code {.cpp} - * Deserializer &operator>>(Deserializer &des, Object &o) { + * DataDeserializer &operator>>(DataDeserializer &des, Object &o) { * des >> o._borderColor; * des >> o._maxAnisotropy; * des >> o._filter; @@ -532,6 +572,46 @@ class DataSerializer { */ class DataDeserializer { public: + + /** + * @brief RAII tracker of advance position + * + * When a custom operator>> is implemented for DataDeserializer, + * this class allows to easily keep track of how much data has been added and + * adjust the parent's lastAdvance() count on this class' destruction. + * + * @code {.cpp} + * DataDeserializer &operator>>(Deserializer &des, Object &o) { + * DataDeserializer::SizeTracker tracker(des); + * + * des >> o._borderColor; + * des >> o._maxAnisotropy; + * des >> o._filter; + * return des; + * } + * @endcode + */ + class SizeTracker { + public: + SizeTracker(DataDeserializer &parent) : _parent(parent) { + _start_pos = _parent.pos(); + } + + ~SizeTracker() { + size_t cur_pos = _parent.pos(); + + if ( cur_pos >= _start_pos ) { + _parent._lastAdvance = cur_pos - _start_pos; + } else { + _parent._lastAdvance = 0; + } + } + + private: + DataDeserializer &_parent; + size_t _start_pos = 0; + }; + /** * @brief Construct a Deserializer * * From 101592357f5215f4cb5219392fa3750d742cb5da Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Fri, 2 Dec 2022 01:54:42 +0100 Subject: [PATCH 21/35] Restore KTX version to 2 This fixes compatibility with older baked assets --- libraries/gpu/src/gpu/Texture_ktx.cpp | 30 ++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index d10fc87dd5..67f8e9a621 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -28,12 +28,25 @@ using KtxStorage = Texture::KtxStorage; std::vector, std::shared_ptr>> KtxStorage::_cachedKtxFiles; std::mutex KtxStorage::_cachedKtxFilesMutex; + +/** + * @brief Payload for a KTX (texture) + * + * This contains a ready to use texture. This is both used for the local cache, and for baked textures. + * + * @note The usage for textures means breaking compatibility is a bad idea, and that the implementation + * should just keep on adding extra data at the bottom of the structure, and remain able to read old + * formats. In fact, version 1 KTX can be found in older baked assets. + */ struct GPUKTXPayload { using Version = uint8; static const std::string KEY; - static const Version CURRENT_VERSION { 3 }; - static const size_t SIZE { sizeof(Version) + sizeof(Sampler::Desc) + sizeof(uint64_t) + sizeof(TextureUsageType) + sizeof(glm::ivec2) }; + static const Version CURRENT_VERSION { 2 }; + static const size_t PADDING { 2 }; + static const size_t SIZE { sizeof(Version) + sizeof(Sampler::Desc) + sizeof(uint32_t) + sizeof(TextureUsageType) + sizeof(glm::ivec2) + PADDING }; + + static_assert(GPUKTXPayload::SIZE == 44, "Packing size may differ between platforms"); Sampler::Desc _samplerDesc; Texture::Usage _usage; @@ -43,13 +56,14 @@ struct GPUKTXPayload { void serialize(DataSerializer &ser) { ser << CURRENT_VERSION; - + ser.addPadding(1); ser << _samplerDesc; - uint64_t usageData = _usage._flags.to_ulong(); + uint32_t usageData = (uint32_t)_usage._flags.to_ulong(); ser << usageData; ser << ((uint8_t)_usageType); + ser.addPadding(1); ser << _originalSize; @@ -60,15 +74,16 @@ struct GPUKTXPayload { bool unserialize(DataDeserializer &dsr) { Version version = 0; - uint64_t usageData = 0; + uint32_t usageData = 0; uint8_t usagetype = 0; dsr >> version; + dsr.skipPadding(1); - if (version != CURRENT_VERSION) { + if (version > CURRENT_VERSION) { // If we try to load a version that we don't know how to parse, // it will render incorrectly - qCWarning(gpulogging) << "KTX version" << version << "is different than our own," << CURRENT_VERSION; + qCWarning(gpulogging) << "KTX version" << version << "is newer than our own," << CURRENT_VERSION; qCWarning(gpulogging) << dsr; return false; } @@ -79,6 +94,7 @@ struct GPUKTXPayload { _usage._flags = gpu::Texture::Usage::Flags(usageData); dsr >> usagetype; + dsr.skipPadding(1); _usageType = (TextureUsageType)usagetype; if (version >= 2) { From 1aa9fc0c8c910bfafca6582434b0f21ddffa9e61 Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Thu, 6 Jun 2024 18:12:57 +0200 Subject: [PATCH 22/35] Updated headers --- libraries/shared/src/SerDes.cpp | 2 +- libraries/shared/src/SerDes.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/SerDes.cpp b/libraries/shared/src/SerDes.cpp index 1b30bf9f63..b6adc9b0a6 100644 --- a/libraries/shared/src/SerDes.cpp +++ b/libraries/shared/src/SerDes.cpp @@ -3,7 +3,7 @@ // // // Created by Dale Glass on 5/6/2022 -// Copyright 2022 Dale Glass +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/libraries/shared/src/SerDes.h b/libraries/shared/src/SerDes.h index 4c97ef193b..9de547313f 100644 --- a/libraries/shared/src/SerDes.h +++ b/libraries/shared/src/SerDes.h @@ -3,7 +3,7 @@ // // // Created by Dale Glass on 5/6/2022 -// Copyright 2022 Dale Glass +// Copyright 2024 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html From f96ca65812290ff1e66d17a2a3a44154ed9a9843 Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Thu, 6 Jun 2024 18:15:32 +0200 Subject: [PATCH 23/35] Fix indentation --- libraries/shared/src/SerDes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/SerDes.cpp b/libraries/shared/src/SerDes.cpp index b6adc9b0a6..ad32d7014f 100644 --- a/libraries/shared/src/SerDes.cpp +++ b/libraries/shared/src/SerDes.cpp @@ -17,7 +17,7 @@ const char DataSerializer::PADDING_CHAR; static void dumpHex(QDebug &debug, const char*buf, size_t len) { - QString literal; + QString literal; QString hex; for(size_t i=0;i Date: Fri, 7 Jun 2024 00:45:58 +0200 Subject: [PATCH 24/35] Add more warning notes --- libraries/gpu/src/gpu/Texture_ktx.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 67f8e9a621..f8a43bdb5b 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -53,6 +53,14 @@ struct GPUKTXPayload { TextureUsageType _usageType; glm::ivec2 _originalSize { 0, 0 }; + /** + * @brief Serialize the KTX payload + * + * @warning Be careful modifying this code, as it influences baked assets. + * Backwards compatibility must be maintained. + * + * @param ser Destination serializer + */ void serialize(DataSerializer &ser) { ser << CURRENT_VERSION; @@ -72,6 +80,16 @@ struct GPUKTXPayload { assert(ser.length() == GPUKTXPayload::SIZE); } + /** + * @brief Deserialize the KTX payload + * + * @warning Be careful modifying this code, as it influences baked assets. + * Backwards compatibility must be maintained. + * + * @param dsr Deserializer object + * @return true Successful + * @return false Version check failed + */ bool unserialize(DataDeserializer &dsr) { Version version = 0; uint32_t usageData = 0; From e1546ac3d074e284ede7e7ff9959391bc10e65b3 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Fri, 21 Jun 2024 15:30:49 -0700 Subject: [PATCH 25/35] suggested fixes --- libraries/gpu/src/gpu/Texture_ktx.cpp | 7 ++----- libraries/render-utils/src/Model.cpp | 4 +--- libraries/shared/src/SerDes.h | 4 ++-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index f8a43bdb5b..78822ce9d7 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -64,19 +64,16 @@ struct GPUKTXPayload { void serialize(DataSerializer &ser) { ser << CURRENT_VERSION; - ser.addPadding(1); ser << _samplerDesc; uint32_t usageData = (uint32_t)_usage._flags.to_ulong(); ser << usageData; ser << ((uint8_t)_usageType); - ser.addPadding(1); ser << _originalSize; + ser.addPadding(PADDING); - // The +1 is here because we're adding the CURRENT_VERSION at the top, but since it's declared as a static - // const, it's not actually part of the class' size. assert(ser.length() == GPUKTXPayload::SIZE); } @@ -109,7 +106,7 @@ struct GPUKTXPayload { dsr >> _samplerDesc; dsr >> usageData; - _usage._flags = gpu::Texture::Usage::Flags(usageData); + _usage = gpu::Texture::Usage(usageData); dsr >> usagetype; dsr.skipPadding(1); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 6ad2534038..69a593ed09 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1949,9 +1949,7 @@ void Blender::run() { blendedMeshSizes.push_back(numVertsInMesh); // initialize offsets to zero - for(BlendshapeOffsetUnpacked &bou : unpackedBlendshapeOffsets) { - bou.clear(); - } + memset(unpackedBlendshapeOffsets.data(), 0, numVertsInMesh * sizeof(BlendshapeOffsetUnpacked)); // for each blendshape in this mesh, accumulate the offsets into unpackedBlendshapeOffsets. const float NORMAL_COEFFICIENT_SCALE = 0.01f; diff --git a/libraries/shared/src/SerDes.h b/libraries/shared/src/SerDes.h index 9de547313f..f80d09a60a 100644 --- a/libraries/shared/src/SerDes.h +++ b/libraries/shared/src/SerDes.h @@ -105,7 +105,7 @@ class DataSerializer { * * Since this is mostly intended to be used for networking, we default to the largest probable MTU here. */ - static const int DEFAULT_SIZE = 1500; + static const int DEFAULT_SIZE = 1500; /** * @brief Character to use for padding. @@ -113,7 +113,7 @@ class DataSerializer { * Padding should be ignored, so it doesn't matter what we go with here, but it can be useful to set it * to something that would be distinctive in a dump. */ - static const char PADDING_CHAR = 0xAA; + static const char PADDING_CHAR = (char)0xAA; /** * @brief Construct a dynamically allocated serializer From b2665cbd4ef028ad535d3332f572d2657e7b7f79 Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Mon, 24 Jun 2024 20:42:46 +0200 Subject: [PATCH 26/35] Fix warnings --- tests/ktx/src/KtxTests.cpp | 3 +-- tests/ktx/src/KtxTests.h | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ktx/src/KtxTests.cpp b/tests/ktx/src/KtxTests.cpp index cac9690b5c..9b104a7992 100644 --- a/tests/ktx/src/KtxTests.cpp +++ b/tests/ktx/src/KtxTests.cpp @@ -17,8 +17,7 @@ #include #include #include "SerDes.h" -#include "KTX.h" -#include "Texture_ktx.cpp" + QTEST_GUILESS_MAIN(KtxTests) diff --git a/tests/ktx/src/KtxTests.h b/tests/ktx/src/KtxTests.h index 5627dc313d..c59fc17ccc 100644 --- a/tests/ktx/src/KtxTests.h +++ b/tests/ktx/src/KtxTests.h @@ -16,6 +16,7 @@ private slots: void testKtxEvalFunctions(); void testKhronosCompressionFunctions(); void testKtxSerialization(); + void testKtxNewSerializationSphericalHarmonics(); }; From a7be389aed29df3f18bac8b77520ba0b2454ca4e Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Sat, 11 Jan 2025 20:08:31 -0800 Subject: [PATCH 27/35] fix padding, remove unused function --- libraries/gpu/src/gpu/Texture.h | 16 ---------------- libraries/gpu/src/gpu/Texture_ktx.cpp | 4 ++-- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 05ad70b872..a6f527e657 100644 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -188,24 +188,8 @@ public: _minMip == other._minMip && _maxMip == other._maxMip; } - - DataSerializer &operator<<(DataSerializer &dsd) { - dsd << _borderColor; - dsd << _maxAnisotropy; - dsd << _filter; - dsd << _comparisonFunc; - dsd << _wrapModeU; - dsd << _wrapModeV; - dsd << _wrapModeW; - dsd << _mipOffset; - dsd << _minMip; - dsd << _maxMip; - return dsd; - } }; - - Sampler() {} Sampler(const Filter filter, const WrapMode wrap = WRAP_REPEAT) : _desc(filter, wrap) {} Sampler(const Desc& desc) : _desc(desc) {} diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 78822ce9d7..2a4d678208 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -93,7 +93,6 @@ struct GPUKTXPayload { uint8_t usagetype = 0; dsr >> version; - dsr.skipPadding(1); if (version > CURRENT_VERSION) { // If we try to load a version that we don't know how to parse, @@ -109,13 +108,14 @@ struct GPUKTXPayload { _usage = gpu::Texture::Usage(usageData); dsr >> usagetype; - dsr.skipPadding(1); _usageType = (TextureUsageType)usagetype; if (version >= 2) { dsr >> _originalSize; } + dsr.skipPadding(PADDING); + return true; } From febb84e0f5dfb09ebd95b5b37ee334f67e1b7d9d Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Wed, 15 Jan 2025 22:46:08 -0500 Subject: [PATCH 28/35] Add Portal spawner and Copy URL Add Portal spawner and Copy URL --- scripts/system/places/icons/portalFX.png | Bin 0 -> 79835 bytes scripts/system/places/places.css | 67 +++++- scripts/system/places/places.html | 28 ++- scripts/system/places/places.js | 78 ++++++- scripts/system/places/portal.js | 192 ++++++++++++++++++ .../system/places/sounds/teleportSound.mp3 | Bin 0 -> 53079 bytes 6 files changed, 353 insertions(+), 12 deletions(-) create mode 100644 scripts/system/places/icons/portalFX.png create mode 100644 scripts/system/places/portal.js create mode 100644 scripts/system/places/sounds/teleportSound.mp3 diff --git a/scripts/system/places/icons/portalFX.png b/scripts/system/places/icons/portalFX.png new file mode 100644 index 0000000000000000000000000000000000000000..6c781c824befad6e069574c51eecc309f06047d6 GIT binary patch literal 79835 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 i>pI-HapR?2DE|*TvSYzVE)fF&0000
+
+
DOMAIN: @@ -502,6 +504,28 @@ } + function rezPortal(name, address, placeID) { + var portalOrder = { + "channel": channel, + "action": "REQUEST_PORTAL", + "name": name, + "address": address, + "placeID": placeID + }; + EventBridge.emitWebEvent(JSON.stringify(portalOrder)); + + } + + function copyPlaceURL(address) { + var portalOrder = { + "channel": channel, + "action": "COPY_URL", + "address": address + }; + EventBridge.emitWebEvent(JSON.stringify(portalOrder)); + + } + function goHome() { var message = { "channel": channel, @@ -766,6 +790,8 @@ placeUrl = "hifi://" + placeDetail.address; } document.getElementById("placeDetail-visitBtn-container").innerHTML = ""; + document.getElementById("placeDetail-rezPortalBtn-container").innerHTML = ""; + document.getElementById("placeDetail-copyPlaceURLBtn-container").innerHTML = ""; document.getElementById("placeDetail-maturity").innerHTML = placeDetail.maturity.toUpperCase(); document.getElementById("placeDetail-maturity").className = placeDetail.maturity + "FilterOn placeMaturity"; document.getElementById("placeDetail-domain").innerHTML = placeDetail.domain.toUpperCase(); diff --git a/scripts/system/places/places.js b/scripts/system/places/places.js index fa22d536b7..aeb5aba07f 100644 --- a/scripts/system/places/places.js +++ b/scripts/system/places/places.js @@ -3,7 +3,7 @@ // places.js // // Created by Alezia Kurdis, January 1st, 2022. -// Copyright 2022-2023 Overte e.V. +// Copyright 2022-2025 Overte e.V. // // Generate an explore app based on the differents source of placename data. // @@ -36,6 +36,10 @@ var APP_ICON_ACTIVE = ROOT + "icons/appicon_a.png"; var appStatus = false; var channel = "com.overte.places"; + + var portalChannelName = "com.overte.places.portalRezzer"; + var MAX_DISTANCE_TO_CONSIDER_PORTAL = 50.0; //in meters + var PORTAL_DURATION_MILLISEC = 45000; //45 sec var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); @@ -92,6 +96,24 @@ Window.location = messageObj.address; } + } else if (messageObj.action === "REQUEST_PORTAL" && (n - timestamp) > INTERCALL_DELAY) { + d = new Date(); + timestamp = d.getTime(); + var portalPosition = Vec3.sum(MyAvatar.feetPosition, Vec3.multiplyQbyV(MyAvatar.orientation, {"x": 0.0, "y": 0.0, "z": -2.0})); + var requestToSend = { + "action": "REZ_PORTAL", + "position": portalPosition, + "url": messageObj.address, + "name": messageObj.name, + "placeID": messageObj.placeID + }; + Messages.sendMessage(portalChannelName, JSON.stringify(requestToSend), false); + + } else if (messageObj.action === "COPY_URL" && (n - timestamp) > INTERCALL_DELAY) { + d = new Date(); + timestamp = d.getTime(); + Window.copyToClipboard(messageObj.address); + Window.displayAnnouncement("Place URL copied."); } else if (messageObj.action === "GO_HOME" && (n - timestamp) > INTERCALL_DELAY) { d = new Date(); timestamp = d.getTime(); @@ -284,8 +306,8 @@ region = "local"; order = "A"; fetch = true; - pinned = false; - currentFound = true; + pinned = false; + currentFound = true; } else { region = "federation"; order = "F"; @@ -555,6 +577,50 @@ } //####### END of seed random library ################ + function onMessageReceived(paramChannel, paramMessage, paramSender, paramLocalOnly) { + if (paramChannel === portalChannelName) { + var instruction = JSON.parse(paramMessage); + if (instruction.action === "REZ_PORTAL") { + generatePortal(instruction.position, instruction.url, instruction.name, instruction.placeID); + } + } + } + + function generatePortal(position, url, name, placeID) { + var TOLERANCE_FACTOR = 1.1; + if (Vec3.distance(MyAvatar.position, position) < MAX_DISTANCE_TO_CONSIDER_PORTAL) { + var height = MyAvatar.userHeight * MyAvatar.scale * TOLERANCE_FACTOR; + + var portalPosition = Vec3.sum(position, {"x": 0.0, "y": height/2, "z": 0.0}); + var dimensions = {"x": height * 0.618, "y": height, "z": height * 0.618}; + var userdata = { + "url": url, + "name": name, + "placeID": placeID + }; + + var portalID = Entities.addEntity({ + "position": portalPosition, + "dimensions": dimensions, + "type": "Shape", + "shape": "Sphere", + "name": "Portal to " + name, + "canCastShadow": false, + "collisionless": true, + "userData": JSON.stringify(userdata), + "script": ROOT + "portal.js", + "visible": "false", + "grab": { + "grabbable": false + } + }, "local"); + + Script.setTimeout(function () { + Entities.deleteEntity(portalID); + }, PORTAL_DURATION_MILLISEC); + } + } + function cleanup() { if (appStatus) { @@ -562,9 +628,15 @@ tablet.webEventReceived.disconnect(onAppWebEventReceived); } + Messages.messageReceived.disconnect(onMessageReceived); + Messages.unsubscribe(portalChannelName); + tablet.screenChanged.disconnect(onScreenChanged); tablet.removeButton(button); } + Messages.subscribe(portalChannelName); + Messages.messageReceived.connect(onMessageReceived); + Script.scriptEnding.connect(cleanup); }()); diff --git a/scripts/system/places/portal.js b/scripts/system/places/portal.js new file mode 100644 index 0000000000..2cf4c0bc14 --- /dev/null +++ b/scripts/system/places/portal.js @@ -0,0 +1,192 @@ +// +// portal.js +// +// Created by Alezia Kurdis, January 14th, 2025. +// Copyright 2025, Overte e.V. +// +// 3D portal for Places app. portal spawner. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +(function(){ + + var ROOT = Script.resolvePath('').split("portal.js")[0]; + var portalURL = ""; + var portalName = ""; + var TP_SOUND = SoundCache.getSound(ROOT + "sounds/teleportSound.mp3"); + + this.preload = function(entityID) { + + var properties = Entities.getEntityProperties(entityID, ["userData", "dimensions"]); + var userDataObj = JSON.parse(properties.userData); + portalURL = userDataObj.url; + portalName = userDataObj.name; + var portalColor = getColorFromPlaceID(userDataObj.placeID); + + + //HERE WE RENDER EFFECT + + var textLocalPosition = {"x": 0.0, "y": (properties.dimensions.y / 2) * 1.2, "z": 0.0}; + var scale = textLocalPosition.y/1.2; + var textID = Entities.addEntity({ + "type": "Text", + "parentID": entityID, + "localPosition": textLocalPosition, + "dimensions": { + "x": 1 * scale, + "y": 0.15 * scale, + "z": 0.01 + }, + "name": portalName, + "text": portalName, + //"textColor": portalColor.saturated, + "lineHeight": 0.10 * scale, + "backgroundAlpha": 0.0, + "unlit": true, + "alignment": "center", + "verticalAlignment": "center", + "canCastShadow": false, + "billboardMode": "yaw", + "grab": { + "grabbable": false + } + },"local"); + + var fxID = Entities.addEntity({ + "type": "ParticleEffect", + "parentID": entityID, + "localPosition": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "name": "PORTAL_FX", + "dimensions": { + "x": 5.2 * scale, + "y": 5.2 * scale, + "z": 5.2 * scale + }, + "grab": { + "grabbable": false + }, + "shapeType": "ellipsoid", + "color": portalColor.light, + "alpha": 0.1, + "textures": ROOT + "icons/portalFX.png", + "maxParticles": 600, + "lifespan": 0.6, + "emitRate": 1000, + "emitSpeed": -1 * scale, + "speedSpread": 0 * scale, + "emitOrientation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "emitDimensions": { + "x": 1.28 * scale, + "y": 2 * scale, + "z": 1.28 * scale + }, + "polarFinish": 3.1415927410125732, + "emitAcceleration": { + "x": 0, + "y": 0, + "z": 0 + }, + "particleRadius": 0.4000000059604645 * scale, + "radiusSpread": 0.30000001192092896 * scale, + "radiusStart": 1 * scale, + "radiusFinish": 0 * scale, + "colorStart": portalColor.saturated, + "colorFinish": { + "red": 255, + "green": 255, + "blue": 255 + }, + "alphaSpread": 0.019999999552965164, + "alphaStart": 0, + "alphaFinish": 0.20000000298023224, + "emitterShouldTrail": true, + "particleSpin": 1.5700000524520874, + "spinSpread": 2.9700000286102295, + "spinStart": 0, + "spinFinish": 0 + },"local"); + + } + + this.enterEntity = function(entityID) { + var injectorOptions = { + "position": MyAvatar.position, + "volume": 0.3, + "loop": false, + "localOnly": true + }; + var injector = Audio.playSound(TP_SOUND, injectorOptions); + + var timer = Script.setTimeout(function () { + Window.location = portalURL; + Entities.deleteEntity(entityID); + }, 1000); + + }; + + function getColorFromPlaceID(placeID) { + var idIntegerConstant = getStringScore(placeID); + var hue = (idIntegerConstant%360)/360; + var color = hslToRgb(hue, 1, 0.5); + var colorLight = hslToRgb(hue, 1, 0.75); + return { + "saturated": {"red": color[0], "green": color[1], "blue": color[2]}, + "light": {"red": colorLight[0], "green": colorLight[1], "blue": colorLight[2]}, + }; + } + + function getStringScore(str) { + var score = 0; + for (var j = 0; j < str.length; j++){ + score += str.charCodeAt(j); + } + return score; + } + + /* + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param {number} h The hue + * @param {number} s The saturation + * @param {number} l The lightness + * @return {Array} The RGB representation + */ + function hslToRgb(h, s, l){ + var r, g, b; + + if(s == 0){ + r = g = b = l; // achromatic + }else{ + var hue2rgb = function hue2rgb(p, q, t){ + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + } + +}) diff --git a/scripts/system/places/sounds/teleportSound.mp3 b/scripts/system/places/sounds/teleportSound.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e5000e55b57821d16d313ca575411428a6b033c3 GIT binary patch literal 53079 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 literal 0 HcmV?d00001 From ec47320ee28bd8353e8a1c5198e0ee0afcffcfdf Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Thu, 16 Jan 2025 22:19:10 -0500 Subject: [PATCH 29/35] Adding Portal Sound and Maximum Spawn protection Adding Portal Sound and Maximum Spawn protection --- scripts/system/places/places.js | 73 +++++++++++-------- scripts/system/places/portal.js | 33 ++++++++- scripts/system/places/sounds/portalSound.mp3 | Bin 0 -> 83172 bytes 3 files changed, 72 insertions(+), 34 deletions(-) create mode 100644 scripts/system/places/sounds/portalSound.mp3 diff --git a/scripts/system/places/places.js b/scripts/system/places/places.js index aeb5aba07f..5aa8d282b1 100644 --- a/scripts/system/places/places.js +++ b/scripts/system/places/places.js @@ -38,8 +38,10 @@ var channel = "com.overte.places"; var portalChannelName = "com.overte.places.portalRezzer"; - var MAX_DISTANCE_TO_CONSIDER_PORTAL = 50.0; //in meters + var MAX_DISTANCE_TO_CONSIDER_PORTAL = 100.0; //in meters var PORTAL_DURATION_MILLISEC = 45000; //45 sec + var rezzerPortalCount = 0; + var MAX_REZZED_PORTAL = 15; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); @@ -587,37 +589,44 @@ } function generatePortal(position, url, name, placeID) { - var TOLERANCE_FACTOR = 1.1; - if (Vec3.distance(MyAvatar.position, position) < MAX_DISTANCE_TO_CONSIDER_PORTAL) { - var height = MyAvatar.userHeight * MyAvatar.scale * TOLERANCE_FACTOR; - - var portalPosition = Vec3.sum(position, {"x": 0.0, "y": height/2, "z": 0.0}); - var dimensions = {"x": height * 0.618, "y": height, "z": height * 0.618}; - var userdata = { - "url": url, - "name": name, - "placeID": placeID - }; - - var portalID = Entities.addEntity({ - "position": portalPosition, - "dimensions": dimensions, - "type": "Shape", - "shape": "Sphere", - "name": "Portal to " + name, - "canCastShadow": false, - "collisionless": true, - "userData": JSON.stringify(userdata), - "script": ROOT + "portal.js", - "visible": "false", - "grab": { - "grabbable": false - } - }, "local"); - - Script.setTimeout(function () { - Entities.deleteEntity(portalID); - }, PORTAL_DURATION_MILLISEC); + if (rezzerPortalCount <= MAX_REZZED_PORTAL) { + var TOLERANCE_FACTOR = 1.1; + if (Vec3.distance(MyAvatar.position, position) < MAX_DISTANCE_TO_CONSIDER_PORTAL) { + var height = MyAvatar.userHeight * MyAvatar.scale * TOLERANCE_FACTOR; + + var portalPosition = Vec3.sum(position, {"x": 0.0, "y": height/2, "z": 0.0}); + var dimensions = {"x": height * 0.618, "y": height, "z": height * 0.618}; + var userdata = { + "url": url, + "name": name, + "placeID": placeID + }; + + var portalID = Entities.addEntity({ + "position": portalPosition, + "dimensions": dimensions, + "type": "Shape", + "shape": "Sphere", + "name": "Portal to " + name, + "canCastShadow": false, + "collisionless": true, + "userData": JSON.stringify(userdata), + "script": ROOT + "portal.js", + "visible": "false", + "grab": { + "grabbable": false + } + }, "local"); + rezzerPortalCount = rezzerPortalCount + 1; + + Script.setTimeout(function () { + Entities.deleteEntity(portalID); + rezzerPortalCount = rezzerPortalCount - 1; + if (rezzerPortalCount < 0) { + rezzerPortalCount = 0; + } + }, PORTAL_DURATION_MILLISEC); + } } } diff --git a/scripts/system/places/portal.js b/scripts/system/places/portal.js index 2cf4c0bc14..5c16cd93c0 100644 --- a/scripts/system/places/portal.js +++ b/scripts/system/places/portal.js @@ -15,13 +15,17 @@ var portalURL = ""; var portalName = ""; var TP_SOUND = SoundCache.getSound(ROOT + "sounds/teleportSound.mp3"); + var PORTAL_SOUND = SoundCache.getSound(ROOT + "sounds/portalSound.mp3"); + var portalInjector = Uuid.NONE; + var portalPosition; this.preload = function(entityID) { - var properties = Entities.getEntityProperties(entityID, ["userData", "dimensions"]); + var properties = Entities.getEntityProperties(entityID, ["userData", "dimensions", "position"]); var userDataObj = JSON.parse(properties.userData); portalURL = userDataObj.url; portalName = userDataObj.name; + portalPosition = properties.position; var portalColor = getColorFromPlaceID(userDataObj.placeID); @@ -116,6 +120,26 @@ "spinFinish": 0 },"local"); + if (PORTAL_SOUND.downloaded) { + playLoopSound(); + } else { + PORTAL_SOUND.ready.connect(onSoundReady); + } + } + + function onSoundReady() { + PORTAL_SOUND.ready.disconnect(onSoundReady); + playLoopSound(); + } + + function playLoopSound() { + var injectorOptions = { + "position": portalPosition, + "volume": 0.15, + "loop": true, + "localOnly": true + }; + portalInjector = Audio.playSound(PORTAL_SOUND, injectorOptions); } this.enterEntity = function(entityID) { @@ -129,11 +153,16 @@ var timer = Script.setTimeout(function () { Window.location = portalURL; + portalInjector.stop(); Entities.deleteEntity(entityID); }, 1000); - }; + }; + this.unload = function(entityID) { + portalInjector.stop(); + }; + function getColorFromPlaceID(placeID) { var idIntegerConstant = getStringScore(placeID); var hue = (idIntegerConstant%360)/360; diff --git a/scripts/system/places/sounds/portalSound.mp3 b/scripts/system/places/sounds/portalSound.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5e7f5a9bd0f170da8994e137599daed2dcd7349b GIT binary patch literal 83172 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 literal 0 HcmV?d00001 From c70fb313673f8a89ba8dc839e8f8942a88e6732a Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:23:46 -0500 Subject: [PATCH 30/35] Using Sound Entity for the portal Using Sound Entity for the portal, this simplify the termination of the looping sound. --- scripts/system/places/portal.js | 42 +++++++++------------------------ 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/scripts/system/places/portal.js b/scripts/system/places/portal.js index 5c16cd93c0..c77fbc648d 100644 --- a/scripts/system/places/portal.js +++ b/scripts/system/places/portal.js @@ -15,21 +15,14 @@ var portalURL = ""; var portalName = ""; var TP_SOUND = SoundCache.getSound(ROOT + "sounds/teleportSound.mp3"); - var PORTAL_SOUND = SoundCache.getSound(ROOT + "sounds/portalSound.mp3"); - var portalInjector = Uuid.NONE; - var portalPosition; this.preload = function(entityID) { - var properties = Entities.getEntityProperties(entityID, ["userData", "dimensions", "position"]); + var properties = Entities.getEntityProperties(entityID, ["userData", "dimensions"]); var userDataObj = JSON.parse(properties.userData); portalURL = userDataObj.url; portalName = userDataObj.name; - portalPosition = properties.position; var portalColor = getColorFromPlaceID(userDataObj.placeID); - - - //HERE WE RENDER EFFECT var textLocalPosition = {"x": 0.0, "y": (properties.dimensions.y / 2) * 1.2, "z": 0.0}; var scale = textLocalPosition.y/1.2; @@ -44,7 +37,7 @@ }, "name": portalName, "text": portalName, - //"textColor": portalColor.saturated, + "textColor": portalColor.light, "lineHeight": 0.10 * scale, "backgroundAlpha": 0.0, "unlit": true, @@ -119,27 +112,19 @@ "spinStart": 0, "spinFinish": 0 },"local"); - - if (PORTAL_SOUND.downloaded) { - playLoopSound(); - } else { - PORTAL_SOUND.ready.connect(onSoundReady); - } - } - function onSoundReady() { - PORTAL_SOUND.ready.disconnect(onSoundReady); - playLoopSound(); - } - - function playLoopSound() { - var injectorOptions = { - "position": portalPosition, + var loopSoundID = Entities.addEntity({ + "type": "Sound", + "parentID": entityID, + "localPosition": {"x": 0.0, "y": 0.0, "z": 0.0}, + "name": "PORTAL SOUND", + "soundURL": ROOT + "sounds/portalSound.mp3", "volume": 0.15, "loop": true, + "positional": true, "localOnly": true - }; - portalInjector = Audio.playSound(PORTAL_SOUND, injectorOptions); + },"local"); + } this.enterEntity = function(entityID) { @@ -153,16 +138,11 @@ var timer = Script.setTimeout(function () { Window.location = portalURL; - portalInjector.stop(); Entities.deleteEntity(entityID); }, 1000); }; - this.unload = function(entityID) { - portalInjector.stop(); - }; - function getColorFromPlaceID(placeID) { var idIntegerConstant = getStringScore(placeID); var hue = (idIntegerConstant%360)/360; From 29471048c86ef2cc86f97eb0f2c4bc4636014f89 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:37:53 -0500 Subject: [PATCH 31/35] Fix Documentation Link for EntityProperties-Sound Fix Documentation Link for EntityProperties-Sound --- libraries/entities/src/EntityTypes.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index ab3233e639..2b14e417df 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -2,9 +2,9 @@ // EntityTypes.h // libraries/entities/src // -// Created by Brad Hefta-Gaub on 12/4/13. +// Created by Brad Hefta-Gaub on December 4th, 2013. // Copyright 2013 High Fidelity, Inc. -// Copyright 2023 Overte e.V. +// Copyright 2023-2025 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -91,7 +91,7 @@ public: * "Material"Modifies the existing materials on entities and avatars. * {@link Entities.EntityProperties-Material|EntityProperties-Material} * "Sound"Plays a sound. - * {@link Entities.EntityProperties-Material|EntityProperties-Sound} + * {@link Entities.EntityProperties-Sound|EntityProperties-Sound} * * * @typedef {string} Entities.EntityType From f1cecade22a9aff4d36d77528dd5abb74aa13b53 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:48:23 -0500 Subject: [PATCH 32/35] Fix Documentation for PICK_AVATARS In The documentation, the syntax wasn't right for Picks.PICK_AVATARS Previously we could read: PICK_AVATATRS But it was supposed to be be: PICK_AVATARS --- libraries/shared/src/PickFilter.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/PickFilter.h b/libraries/shared/src/PickFilter.h index 1cc1a8b0b5..acf0c70eab 100644 --- a/libraries/shared/src/PickFilter.h +++ b/libraries/shared/src/PickFilter.h @@ -1,6 +1,7 @@ // -// Created by Sam Gondelman on 12/7/18. +// Created by Sam Gondelman on December 7th, 2018. // Copyright 2018 High Fidelity, Inc. +// Copyright 2025 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -26,7 +27,7 @@ public: * PICK_DOMAIN_ENTITIES1Include domain entities when intersecting. * PICK_AVATAR_ENTITIES2Include avatar entities when intersecting. * PICK_LOCAL_ENTITIES4Include local entities when intersecting. - * PICK_AVATATRS8Include avatars when intersecting. + * PICK_AVATARS8Include avatars when intersecting. * PICK_HUD16Include the HUD surface when intersecting in HMD mode. * PICK_INCLUDE_VISIBLE32Include visible objects when intersecting. * PICK_INCLUDE_INVISIBLE64Include invisible objects when intersecting. From 739d95f5f0c416fd862d4f3ec3820af311d59fb1 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Thu, 23 Jan 2025 23:08:36 -0800 Subject: [PATCH 33/35] try disabling jitter doubling in VR --- libraries/gpu/src/gpu/Backend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/gpu/src/gpu/Backend.cpp b/libraries/gpu/src/gpu/Backend.cpp index eb02e15676..ddda894306 100644 --- a/libraries/gpu/src/gpu/Backend.cpp +++ b/libraries/gpu/src/gpu/Backend.cpp @@ -63,7 +63,7 @@ Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, // Apply jitter to projections // We divided by the framebuffer size, which was double-sized, to normalize the jitter, but we want a normal amount of jitter // for each eye, so we multiply by 2 to get back to normal - normalizedJitter.x *= 2.0f; + //normalizedJitter.x *= 2.0f; result._projection[2][0] += normalizedJitter.x; result._projection[2][1] += normalizedJitter.y; From d321301b40fbbe47209d171f6a6e66b10c6e4b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Sun, 26 Jan 2025 01:13:22 +0100 Subject: [PATCH 34/35] Remove missed comment about removing dependency on script library. --- libraries/shared/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 3c08c9a1bc..bc54bcc034 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -1,12 +1,11 @@ # Copyright 2013-2020, High Fidelity, Inc. -# Copyright 2021-2023 Overte e.V. +# Copyright 2021-2025 Overte e.V. # SPDX-License-Identifier: Apache-2.0 set(TARGET_NAME shared) include_directories("${QT_DIR}/include/QtCore/${QT_VERSION}/QtCore" "${QT_DIR}/include/QtCore/${QT_VERSION}") -# TODO: there isn't really a good reason to have Script linked here - let's get what is requiring it out (RegisteredMetaTypes.cpp) setup_hifi_library(Gui Network) if (WIN32) From 1a1779a507c874c360685032ceb5677c0462dd7d Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Mon, 3 Feb 2025 21:26:31 -0500 Subject: [PATCH 35/35] Address ugly image broken link in Place Details Address ugly image broken link in Place Details --- scripts/system/places/places.html | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/scripts/system/places/places.html b/scripts/system/places/places.html index fda67f4066..354fbf1cb6 100644 --- a/scripts/system/places/places.html +++ b/scripts/system/places/places.html @@ -4,7 +4,7 @@ // places.html // // Created by Alezia Kurdis, January 1st, 2022. -// Copyright 2022 Overte e.V. +// Copyright 2022-2025 Overte e.V. // // html for the ui of the Places application. // @@ -107,7 +107,7 @@

×
- +
@@ -751,12 +751,17 @@ } } - document.getElementById("placeDetail-image").src = ""; + var pictureUrl = ""; if (placeDetail.thumbnail === "") { - document.getElementById("placeDetail-image").src = "icons/placeholder_" + placeDetail.metaverseRegion + ".jpg"; + pictureUrl = "icons/placeholder_" + placeDetail.metaverseRegion + ".jpg"; } else { - document.getElementById("placeDetail-image").src = placeDetail.thumbnail; + pictureUrl = placeDetail.thumbnail; } + document.getElementById("placeDetail-image").style.backgroundImage = "url(" + pictureUrl + ")"; + document.getElementById("placeDetail-image").style.backgroundRepeat = "no-repeat"; + document.getElementById("placeDetail-image").style.backgroundPosition = "center center"; + document.getElementById("placeDetail-image").style.backgroundSize = "cover"; + document.getElementById("placeDetail-placeName").innerHTML = placeDetail.name; document.getElementById("placeDetail-managers").innerHTML = "By
    " + placeDetail.managers; document.getElementById("placeDetail-description").innerHTML = placeDetail.description;