From 1b67223e0eaaff87363c7f18bf8a0e3c4255943a Mon Sep 17 00:00:00 2001 From: Olivier Prat Date: Mon, 16 Oct 2017 12:38:44 +0200 Subject: [PATCH] Preparing for multiple outline groups in shader --- libraries/render-utils/src/Outline.slh | 8 +- libraries/render-utils/src/OutlineEffect.cpp | 221 +++++++++++------- libraries/render-utils/src/OutlineEffect.h | 92 +++++--- libraries/render-utils/src/Outline_shared.slh | 4 + .../render-utils/src/RenderDeferredTask.cpp | 2 +- 5 files changed, 210 insertions(+), 117 deletions(-) diff --git a/libraries/render-utils/src/Outline.slh b/libraries/render-utils/src/Outline.slh index ac56e4c95c..4e71116f79 100644 --- a/libraries/render-utils/src/Outline.slh +++ b/libraries/render-utils/src/Outline.slh @@ -21,11 +21,13 @@ uniform outlineParamsBuffer { uniform sampler2D sceneDepthMap; uniform sampler2D outlinedDepthMap; +uniform sampler2D outlinedIdMap; in vec2 varTexCoord0; out vec4 outFragColor; const float FAR_Z = 1.0; +const float ID_THRESHOLD = 1.f/64.f; const float LINEAR_DEPTH_BIAS = 5e-3; const float OPACITY_EPSILON = 5e-3; @@ -37,10 +39,11 @@ void main(void) { // sides of the silhouette vec2 halfTexel = getInvWidthHeight() / 2; vec2 texCoord0 = varTexCoord0+halfTexel; + vec4 outlinedIdColor = texture(outlinedIdMap, texCoord0); float outlinedDepth = texture(outlinedDepthMap, texCoord0).x; float intensity = 0.0; - if (outlinedDepth < FAR_Z) { + if (outlinedDepth < FAR_Z && distance(outlinedIdColor, params._idColor) < ID_THRESHOLD) { // We're not on the far plane so we are on the outlined object, thus no outline to do! <@if IS_FILLED@> // But we need to fill the interior @@ -75,7 +78,8 @@ void main(void) { if (uv.x>=0.0 && uv.x<=1.0) { outlinedDepth = texture(outlinedDepthMap, uv).x; - intensity += (outlinedDepth < FAR_Z) ? 1.0 : 0.0; + outlinedIdColor = texture(outlinedIdMap, uv); + intensity += (outlinedDepth < FAR_Z && distance(outlinedIdColor, params._idColor) < ID_THRESHOLD) ? 1.0 : 0.0; weight += 1.f; } uv.x += deltaUv.x; diff --git a/libraries/render-utils/src/OutlineEffect.cpp b/libraries/render-utils/src/OutlineEffect.cpp index 89cedf0676..0ffd2581cf 100644 --- a/libraries/render-utils/src/OutlineEffect.cpp +++ b/libraries/render-utils/src/OutlineEffect.cpp @@ -83,81 +83,139 @@ gpu::TexturePointer OutlineRessources::getIdTexture() { return _idTexture; } +glm::vec4 encodeIdToColor(unsigned int id) { + union { + struct { + unsigned int r : 2; + unsigned int g : 2; + unsigned int b : 2; + unsigned int a : 2; + }; + unsigned char id; + } groupId; + + assert(id < 254); + groupId.id = id+1; + + glm::vec4 idColor{ groupId.r, groupId.g, groupId.b, groupId.a }; + + // Normalize. Since we put 2 bits into each color component, each component has a maximum + // value of 3. + idColor /= 3.f; + return idColor; +} + void DrawOutlineMask::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); - auto& inShapes = inputs.get0(); + auto& groups = inputs.get0(); auto& deferredFrameBuffer = inputs.get1(); - if (!inShapes.empty()) { - RenderArgs* args = renderContext->args; - ShapeKey::Builder defaultKeyBuilder; + RenderArgs* args = renderContext->args; + ShapeKey::Builder defaultKeyBuilder; + auto hasOutline = false; - if (!_outlineRessources) { - _outlineRessources = std::make_shared(); + if (!_outlineRessources) { + _outlineRessources = std::make_shared(); + } + _outlineRessources->update(deferredFrameBuffer->getDeferredColorTexture()); + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + + auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); + auto maskSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); + auto colorLoc = maskPipeline.get()->pipeline->getProgram()->getUniforms().findLocation("color"); + auto skinnedColorLoc = maskSkinnedPipeline.get()->pipeline->getProgram()->getUniforms().findLocation("color"); + unsigned int groupId = 0; + + for (auto& inShapeBounds : groups) { + if (!inShapeBounds.isNull()) { + auto& inShapes = inShapeBounds.get(); + + if (!inShapes.empty()) { + if (!hasOutline) { + batch.setFramebuffer(_outlineRessources->getFramebuffer()); + // Clear it only if it hasn't been done before + batch.clearFramebuffer( + gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH, + vec4(0.0f, 0.0f, 0.0f, 0.0f), 1.0f, 0, false); + + // Setup camera, projection and viewport for all items + 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); + hasOutline = true; + } + + std::vector skinnedShapeKeys{}; + // Encode group id in quantized color + glm::vec4 idColor = encodeIdToColor(groupId); + + // Iterate through all inShapes and render the unskinned + args->_shapePipeline = maskPipeline; + batch.setPipeline(maskPipeline->pipeline); + batch._glUniform4f(colorLoc, idColor.r, idColor.g, idColor.b, idColor.a); + for (auto items : inShapes) { + if (items.first.isSkinned()) { + skinnedShapeKeys.push_back(items.first); + } else { + renderItems(renderContext, items.second); + } + } + + // Reiterate to render the skinned + args->_shapePipeline = maskSkinnedPipeline; + batch.setPipeline(maskSkinnedPipeline->pipeline); + batch._glUniform4f(skinnedColorLoc, idColor.r, idColor.g, idColor.b, idColor.a); + for (const auto& key : skinnedShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } + } + } + + ++groupId; } - _outlineRessources->update(deferredFrameBuffer->getDeferredColorTexture()); - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - - batch.setFramebuffer(_outlineRessources->getFramebuffer()); - // Clear it - batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH, - vec4(0.0f, 0.0f, 0.0f, 0.0f), 1.0f, 0, false); - - // Setup camera, projection and viewport for all items - 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); - - auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); - auto maskSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); - - std::vector skinnedShapeKeys{}; - auto colorLoc = maskPipeline.get()->pipeline->getProgram()->getUniforms().findLocation("color"); - glm::vec4 idColor{ 1.0f, 0.0f, 0.0f, 0.0f }; - - // Iterate through all inShapes and render the unskinned - args->_shapePipeline = maskPipeline; - batch.setPipeline(maskPipeline->pipeline); - batch._glUniform4f(colorLoc, idColor.r, idColor.g, idColor.b, idColor.a); - for (auto items : inShapes) { - if (items.first.isSkinned()) { - skinnedShapeKeys.push_back(items.first); - } - else { - renderItems(renderContext, items.second); - } - } - - colorLoc = maskSkinnedPipeline.get()->pipeline->getProgram()->getUniforms().findLocation("color"); - // Reiterate to render the skinned - args->_shapePipeline = maskSkinnedPipeline; - batch.setPipeline(maskSkinnedPipeline->pipeline); - batch._glUniform4f(colorLoc, idColor.r, idColor.g, idColor.b, idColor.a); - for (const auto& key : skinnedShapeKeys) { - renderItems(renderContext, inShapes.at(key)); - } - - args->_shapePipeline = nullptr; - args->_batch = nullptr; - }); + args->_shapePipeline = nullptr; + args->_batch = nullptr; + }); + if (hasOutline) { output = _outlineRessources; } else { output = nullptr; } } +PrepareDrawOutline::PrepareDrawOutline() { + +} + +void PrepareDrawOutline::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + auto destinationFrameBuffer = inputs; + auto framebufferSize = destinationFrameBuffer->getSize(); + + if (!_primaryWithoutDepthBuffer || framebufferSize != _frameBufferSize) { + // Failing to recreate this frame buffer when the screen has been resized creates a bug on Mac + _primaryWithoutDepthBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("primaryWithoutDepth")); + _primaryWithoutDepthBuffer->setRenderBuffer(0, destinationFrameBuffer->getRenderBuffer(0)); + _frameBufferSize = framebufferSize; + } + + outputs = _primaryWithoutDepthBuffer; +} + +gpu::PipelinePointer DrawOutline::_pipeline; +gpu::PipelinePointer DrawOutline::_pipelineFilled; + DrawOutline::DrawOutline() { } @@ -170,6 +228,7 @@ void DrawOutline::configure(const Config& config) { _fillOpacityOccluded = config.fillOpacityOccluded; _threshold = config.glow ? 1.f : 1e-3f; _intensity = config.intensity * (config.glow ? 2.f : 1.f); + _hasConfigurationChanged = true; } void DrawOutline::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { @@ -179,20 +238,16 @@ void DrawOutline::run(const render::RenderContextPointer& renderContext, const I auto sceneDepthBuffer = inputs.get2(); const auto frameTransform = inputs.get0(); auto outlinedDepthTexture = outlineFrameBuffer->getDepthTexture(); + auto outlinedIdTexture = outlineFrameBuffer->getIdTexture(); auto destinationFrameBuffer = inputs.get3(); auto framebufferSize = glm::ivec2(outlinedDepthTexture->getDimensions()); - if (!_primaryWithoutDepthBuffer || framebufferSize!=_frameBufferSize) { - // Failing to recreate this frame buffer when the screen has been resized creates a bug on Mac - _primaryWithoutDepthBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("primaryWithoutDepth")); - _primaryWithoutDepthBuffer->setRenderBuffer(0, destinationFrameBuffer->getRenderBuffer(0)); - _frameBufferSize = framebufferSize; - } - if (sceneDepthBuffer) { const auto OPACITY_EPSILON = 5e-3f; auto pipeline = getPipeline(_fillOpacityUnoccluded>OPACITY_EPSILON || _fillOpacityOccluded>OPACITY_EPSILON); auto args = renderContext->args; + + if (_hasConfigurationChanged) { auto& configuration = _configuration.edit(); configuration._color = _color; @@ -201,24 +256,27 @@ void DrawOutline::run(const render::RenderContextPointer& renderContext, const I configuration._fillOpacityOccluded = _fillOpacityOccluded; configuration._threshold = _threshold; configuration._blurKernelSize = _blurKernelSize; - configuration._size.x = _size * _frameBufferSize.y / _frameBufferSize.x; + configuration._size.x = (_size * framebufferSize.y) / framebufferSize.x; configuration._size.y = _size; + configuration._idColor = encodeIdToColor(0); + _hasConfigurationChanged = false; } gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); - batch.setFramebuffer(_primaryWithoutDepthBuffer); + batch.setFramebuffer(destinationFrameBuffer); batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(glm::mat4()); batch.resetViewTransform(); - batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_frameBufferSize, args->_viewport)); + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport)); batch.setPipeline(pipeline); batch.setUniformBuffer(OUTLINE_PARAMS_SLOT, _configuration); batch.setUniformBuffer(FRAME_TRANSFORM_SLOT, frameTransform->getFrameTransformBuffer()); batch.setResourceTexture(SCENE_DEPTH_SLOT, sceneDepthBuffer->getPrimaryDepthTexture()); batch.setResourceTexture(OUTLINED_DEPTH_SLOT, outlinedDepthTexture); + batch.setResourceTexture(OUTLINED_ID_SLOT, outlinedIdTexture); batch.draw(gpu::TRIANGLE_STRIP, 4); // Restore previous frame buffer @@ -239,6 +297,7 @@ const gpu::PipelinePointer& DrawOutline::getPipeline(bool isFilled) { slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", FRAME_TRANSFORM_SLOT)); slotBindings.insert(gpu::Shader::Binding("sceneDepthMap", SCENE_DEPTH_SLOT)); slotBindings.insert(gpu::Shader::Binding("outlinedDepthMap", OUTLINED_DEPTH_SLOT)); + slotBindings.insert(gpu::Shader::Binding("outlinedIdMap", OUTLINED_ID_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -400,7 +459,6 @@ void DrawOutlineTask::configure(const Config& config) { void DrawOutlineTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) { const auto groups = inputs.getN(0).get(); - const auto selectedMetas = groups[0]; const auto sceneFrameBuffer = inputs.getN(1); const auto primaryFramebuffer = inputs.getN(2); const auto deferredFrameTransform = inputs.getN(3); @@ -415,19 +473,26 @@ void DrawOutlineTask::build(JobModel& task, const render::Varying& inputs, rende initMaskPipelines(*shapePlumber, state); } - const auto outlinedItemIDs = task.addJob("OutlineMetaToSubItemIDs", selectedMetas); - const auto outlinedItems = task.addJob("OutlineMetaToSubItems", outlinedItemIDs, true); + DrawOutlineMask::Groups sortedBounds; + for (auto i = 0; i < DrawOutline::MAX_GROUP_COUNT; i++) { + const auto groupItems = groups[i]; + const auto outlinedItemIDs = task.addJob("OutlineMetaToSubItemIDs", groupItems); + const auto outlinedItems = task.addJob("OutlineMetaToSubItems", outlinedItemIDs, true); - // Sort - const auto sortedPipelines = task.addJob("OutlinePipelineSort", outlinedItems); - const auto sortedShapes = task.addJob("OutlineDepthSort", sortedPipelines); + // Sort + const auto sortedPipelines = task.addJob("OutlinePipelineSort", outlinedItems); + sortedBounds[i] = task.addJob("OutlineDepthSort", sortedPipelines); + } // Draw depth of outlined objects in separate buffer - const auto drawMaskInputs = DrawOutlineMask::Inputs(sortedShapes, sceneFrameBuffer).asVarying(); + const auto drawMaskInputs = DrawOutlineMask::Inputs(sortedBounds, sceneFrameBuffer).asVarying(); const auto outlinedFrameBuffer = task.addJob("OutlineMask", drawMaskInputs, shapePlumber); + // Prepare for outline group rendering. + const auto destFrameBuffer = task.addJob("PrepareOutline", primaryFramebuffer); + // Draw outline - const auto drawOutlineInputs = DrawOutline::Inputs(deferredFrameTransform, outlinedFrameBuffer, sceneFrameBuffer, primaryFramebuffer).asVarying(); + const auto drawOutlineInputs = DrawOutline::Inputs(deferredFrameTransform, outlinedFrameBuffer, sceneFrameBuffer, destFrameBuffer).asVarying(); task.addJob("OutlineEffect", drawOutlineInputs); // Debug outline diff --git a/libraries/render-utils/src/OutlineEffect.h b/libraries/render-utils/src/OutlineEffect.h index c32d352a01..ce0b2917a4 100644 --- a/libraries/render-utils/src/OutlineEffect.h +++ b/libraries/render-utils/src/OutlineEffect.h @@ -42,22 +42,22 @@ protected: using OutlineRessourcesPointer = std::shared_ptr; -class DrawOutlineMask { +class PrepareDrawOutline { public: + using Inputs = gpu::FramebufferPointer; + using Outputs = gpu::FramebufferPointer; + using Config = render::Job::Config; + using JobModel = render::Job::ModelIO; - using Inputs = render::VaryingSet2; - // Output will contain outlined objects only z-depth texture and the input primary buffer but without the primary depth buffer - using Outputs = OutlineRessourcesPointer; - using JobModel = render::Job::ModelIO; + PrepareDrawOutline(); - DrawOutlineMask(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {} + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output); +private: -protected: + gpu::FramebufferPointer _primaryWithoutDepthBuffer; + gpu::Vec2u _frameBufferSize{ 0, 0 }; - render::ShapePlumberPointer _shapePlumber; - OutlineRessourcesPointer _outlineRessources; }; class DrawOutlineConfig : public render::Job::Config { @@ -95,6 +95,10 @@ signals: class DrawOutline { public: + enum { + MAX_GROUP_COUNT = 7 + }; + using Inputs = render::VaryingSet4; using Config = DrawOutlineConfig; using JobModel = render::Job::ModelI; @@ -109,6 +113,7 @@ private: enum { SCENE_DEPTH_SLOT = 0, OUTLINED_DEPTH_SLOT, + OUTLINED_ID_SLOT, OUTLINE_PARAMS_SLOT = 0, FRAME_TRANSFORM_SLOT @@ -118,12 +123,10 @@ private: using OutlineConfigurationBuffer = gpu::StructBuffer; - const gpu::PipelinePointer& getPipeline(bool isFilled); + static const gpu::PipelinePointer& getPipeline(bool isFilled); - gpu::FramebufferPointer _primaryWithoutDepthBuffer; - glm::ivec2 _frameBufferSize {0, 0}; - gpu::PipelinePointer _pipeline; - gpu::PipelinePointer _pipelineFilled; + static gpu::PipelinePointer _pipeline; + static gpu::PipelinePointer _pipelineFilled; OutlineConfigurationBuffer _configuration; glm::vec3 _color; float _size; @@ -132,6 +135,44 @@ private: float _fillOpacityUnoccluded; float _fillOpacityOccluded; float _threshold; + bool _hasConfigurationChanged{ true }; +}; + +class DrawOutlineTask { +public: + + using Groups = render::VaryingArray; + using Inputs = render::VaryingSet4; + using Config = render::Task::Config; + using JobModel = render::Task::ModelI; + + DrawOutlineTask(); + + void configure(const Config& config); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); + +private: + + static void initMaskPipelines(render::ShapePlumber& plumber, gpu::StatePointer state); +}; + +class DrawOutlineMask { +public: + + using Groups = render::VaryingArray; + using Inputs = render::VaryingSet2; + // Output will contain outlined objects only z-depth texture and the input primary buffer but without the primary depth buffer + using Outputs = OutlineRessourcesPointer; + using JobModel = render::Job::ModelIO; + + DrawOutlineMask(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {} + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output); + +protected: + + render::ShapePlumberPointer _shapePlumber; + OutlineRessourcesPointer _outlineRessources; }; class DebugOutlineConfig : public render::Job::Config { @@ -171,27 +212,6 @@ private: void initializePipelines(); }; -class DrawOutlineTask { -public: - - enum { - MAX_GROUP_COUNT = 7 - }; - - using Groups = render::VaryingArray; - using Inputs = render::VaryingSet4; - using Config = render::Task::Config; - using JobModel = render::Task::ModelI; - - DrawOutlineTask(); - - void configure(const Config& config); - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); - -private: - - static void initMaskPipelines(render::ShapePlumber& plumber, gpu::StatePointer state); -}; #endif // hifi_render_utils_OutlineEffect_h diff --git a/libraries/render-utils/src/Outline_shared.slh b/libraries/render-utils/src/Outline_shared.slh index 902bbd20ad..5ce21c85b4 100644 --- a/libraries/render-utils/src/Outline_shared.slh +++ b/libraries/render-utils/src/Outline_shared.slh @@ -2,9 +2,11 @@ #ifdef __cplusplus # define VEC2 glm::vec2 # define VEC3 glm::vec3 +# define VEC4 glm::vec4 #else # define VEC2 vec2 # define VEC3 vec3 +# define VEC4 vec4 #endif struct OutlineParameters @@ -16,6 +18,8 @@ struct OutlineParameters float _fillOpacityUnoccluded; float _fillOpacityOccluded; + VEC4 _idColor; + float _threshold; int _blurKernelSize; float padding2; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index c20eb10cf6..73d8b93c07 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -181,7 +181,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto selectedItems = addSelectItemJobs(task, selectionBaseName, metas, opaques, transparents); DrawOutlineTask::Groups outlineGroups; outlineGroups[0] = selectedItems; - for (auto i = 1; i < DrawOutlineTask::MAX_GROUP_COUNT; i++) { + for (auto i = 1; i < DrawOutline::MAX_GROUP_COUNT; i++) { std::ostringstream selectionName; selectionName << selectionBaseName; selectionName << i;