From 703fc208565aa4f34bd8554cf2f23e0a9f019df0 Mon Sep 17 00:00:00 2001
From: Olivier Prat <olivier@zvork.fr>
Date: Wed, 11 Jul 2018 12:17:36 +0200
Subject: [PATCH] Separate scale between 3D rendering and HUD

---
 interface/src/Application.cpp                 | 10 +++
 .../src/DeferredLightingEffect.cpp            | 50 ++++++++------
 .../render-utils/src/DeferredLightingEffect.h | 25 ++++++-
 .../render-utils/src/RenderDeferredTask.cpp   | 22 +++++--
 .../render-utils/src/RenderDeferredTask.h     |  2 +
 libraries/render/src/render/ResampleTask.cpp  | 66 +++++++++++++++++++
 libraries/render/src/render/ResampleTask.h    | 31 +++++++++
 7 files changed, 177 insertions(+), 29 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 96756c8be3..1ab1504ea2 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -3271,6 +3271,16 @@ void Application::resizeGL() {
         DependencyManager::get<FramebufferCache>()->setFrameBufferSize(fromGlm(renderSize));
     }
 
+    auto renderResolutionScale = getRenderResolutionScale();
+    if (displayPlugin->getRenderResolutionScale() != renderResolutionScale) {
+        auto renderConfig = _renderEngine->getConfiguration();
+        assert(renderConfig);
+        auto mainView = renderConfig->getConfig("RenderMainView.RenderDeferredTask");
+        assert(mainView);
+        mainView->setProperty("resolutionScale", renderResolutionScale);
+        displayPlugin->setRenderResolutionScale(renderResolutionScale);
+    }
+
     // FIXME the aspect ratio for stereo displays is incorrect based on this.
     float aspectRatio = displayPlugin->getRecommendedAspectRatio();
     _myCamera.setProjection(glm::perspective(glm::radians(_fieldOfView.get()), aspectRatio,
diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp
index 452e5b5ccd..62d8dffe3a 100644
--- a/libraries/render-utils/src/DeferredLightingEffect.cpp
+++ b/libraries/render-utils/src/DeferredLightingEffect.cpp
@@ -393,34 +393,42 @@ graphics::MeshPointer DeferredLightingEffect::getSpotLightMesh() {
     return _spotLightMesh;
 }
 
-void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer) {
+gpu::FramebufferPointer PreparePrimaryFramebuffer::createFramebuffer(const char* name, const glm::uvec2& frameSize) {
+    gpu::FramebufferPointer framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(name));
+    auto colorFormat = gpu::Element::COLOR_SRGBA_32;
+
+    auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
+    auto primaryColorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
+
+    framebuffer->setRenderBuffer(0, primaryColorTexture);
+
+    auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
+    auto primaryDepthTexture = gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
+
+    framebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat);
+
+    return framebuffer;
+}
+
+void PreparePrimaryFramebuffer::configure(const Config& config) {
+    _resolutionScale = config.resolutionScale;
+}
+
+void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, Output& primaryFramebuffer) {
     glm::uvec2 frameSize(renderContext->args->_viewport.z, renderContext->args->_viewport.w);
+    glm::uvec2 scaledFrameSize(glm::vec2(frameSize) * _resolutionScale);
 
     // Resizing framebuffers instead of re-building them seems to cause issues with threaded 
     // rendering
-    if (_primaryFramebuffer && _primaryFramebuffer->getSize() != frameSize) {
-        _primaryFramebuffer.reset();
+    if (!_primaryFramebuffer || _primaryFramebuffer->getSize() != scaledFrameSize) {
+        _primaryFramebuffer = createFramebuffer("deferredPrimary", scaledFrameSize);
     }
 
-    if (!_primaryFramebuffer) {
-        _primaryFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("deferredPrimary"));
-        auto colorFormat = gpu::Element::COLOR_SRGBA_32;
-
-        auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
-        auto primaryColorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
-
-
-        _primaryFramebuffer->setRenderBuffer(0, primaryColorTexture);
-
-
-        auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
-        auto primaryDepthTexture = gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
-
-        _primaryFramebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat);
-    }
-
-    
     primaryFramebuffer = _primaryFramebuffer;
+
+    // Set viewport for the rest of the scaled passes
+    renderContext->args->_viewport.z = scaledFrameSize.x;
+    renderContext->args->_viewport.w = scaledFrameSize.y;
 }
 
 void PrepareDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) {
diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h
index 9b55083ad7..5da2eb22f7 100644
--- a/libraries/render-utils/src/DeferredLightingEffect.h
+++ b/libraries/render-utils/src/DeferredLightingEffect.h
@@ -93,13 +93,34 @@ private:
     friend class RenderDeferredCleanup;
 };
 
+class PreparePrimaryFramebufferConfig : public render::Job::Config {
+    Q_OBJECT
+        Q_PROPERTY(float resolutionScale MEMBER resolutionScale NOTIFY dirty)
+public:
+
+    float resolutionScale{ 1.0f };
+
+signals:
+    void dirty();
+};
+
 class PreparePrimaryFramebuffer {
 public:
-    using JobModel = render::Job::ModelO<PreparePrimaryFramebuffer, gpu::FramebufferPointer>;
 
-    void run(const render::RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer);
+    using Output = gpu::FramebufferPointer;
+    using Config = PreparePrimaryFramebufferConfig;
+    using JobModel = render::Job::ModelO<PreparePrimaryFramebuffer, Output, Config>;
+
+    PreparePrimaryFramebuffer(float resolutionScale = 1.0f) : _resolutionScale{resolutionScale} {}
+    void configure(const Config& config);
+    void run(const render::RenderContextPointer& renderContext, Output& primaryFramebuffer);
 
     gpu::FramebufferPointer _primaryFramebuffer;
+    float _resolutionScale{ 1.0f };
+
+private:
+
+    static gpu::FramebufferPointer createFramebuffer(const char* name, const glm::uvec2& size);
 };
 
 class PrepareDeferred {
diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index 32bdad280c..5ec9f89322 100644
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -26,6 +26,7 @@
 #include <render/DrawStatus.h>
 #include <render/DrawSceneOctree.h>
 #include <render/BlurTask.h>
+#include <render/ResampleTask.h>
 
 #include "RenderHifi.h"
 #include "RenderCommonTask.h"
@@ -59,8 +60,14 @@ RenderDeferredTask::RenderDeferredTask()
 {
 }
 
-void RenderDeferredTask::configure(const Config& config)
-{
+void RenderDeferredTask::configure(const Config& config) {
+    // Propagate resolution scale to sub jobs who need it
+    auto preparePrimaryBufferConfig = config.getConfig<PreparePrimaryFramebuffer>("PreparePrimaryBuffer");
+    auto upsamplePrimaryBufferConfig = config.getConfig<Upsample>("PrimaryBufferUpscale");
+    assert(preparePrimaryBufferConfig);
+    assert(upsamplePrimaryBufferConfig);
+    preparePrimaryBufferConfig->setProperty("resolutionScale", config.resolutionScale);
+    upsamplePrimaryBufferConfig->setProperty("factor", 1.0f / config.resolutionScale);
 }
 
 const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, const char* selectionName,
@@ -103,17 +110,17 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
     
 
     // GPU jobs: Start preparing the primary, deferred and lighting buffer
-    const auto primaryFramebuffer = task.addJob<PreparePrimaryFramebuffer>("PreparePrimaryBuffer");
+    const auto scaledPrimaryFramebuffer = task.addJob<PreparePrimaryFramebuffer>("PreparePrimaryBuffer");
 
     const auto opaqueRangeTimer = task.addJob<BeginGPURangeTimer>("BeginOpaqueRangeTimer", "DrawOpaques");
 
-    const auto prepareDeferredInputs = PrepareDeferred::Inputs(primaryFramebuffer, lightingModel).asVarying();
+    const auto prepareDeferredInputs = PrepareDeferred::Inputs(scaledPrimaryFramebuffer, lightingModel).asVarying();
     const auto prepareDeferredOutputs = task.addJob<PrepareDeferred>("PrepareDeferred", prepareDeferredInputs);
     const auto deferredFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(0);
     const auto lightingFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(1);
 
     // draw a stencil mask in hidden regions of the framebuffer.
-    task.addJob<PrepareStencil>("PrepareStencil", primaryFramebuffer);
+    task.addJob<PrepareStencil>("PrepareStencil", scaledPrimaryFramebuffer);
 
     // Render opaque objects in DeferredBuffer
     const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel, jitter).asVarying();
@@ -223,7 +230,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
     task.addJob<Bloom>("Bloom", bloomInputs);
 
     // Lighting Buffer ready for tone mapping
-    const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer).asVarying();
+    const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, scaledPrimaryFramebuffer).asVarying();
     task.addJob<ToneMappingDeferred>("ToneMapping", toneMappingInputs);
 
     { // Debug the bounds of the rendered items, still look at the zbuffer
@@ -284,6 +291,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
         task.addJob<DebugZoneLighting>("DrawZoneStack", deferredFrameTransform);
     }
 
+    // Upscale to finale resolution
+    const auto primaryFramebuffer = task.addJob<render::Upsample>("PrimaryBufferUpscale", scaledPrimaryFramebuffer);
+
     // Composite the HUD and HUD overlays
     task.addJob<CompositeHUD>("HUD");
 
diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h
index ab6ab177d2..1ce1682cf1 100644
--- a/libraries/render-utils/src/RenderDeferredTask.h
+++ b/libraries/render-utils/src/RenderDeferredTask.h
@@ -105,11 +105,13 @@ class RenderDeferredTaskConfig : public render::Task::Config {
     Q_OBJECT
     Q_PROPERTY(float fadeScale MEMBER fadeScale NOTIFY dirty)
     Q_PROPERTY(float fadeDuration MEMBER fadeDuration NOTIFY dirty)
+    Q_PROPERTY(float resolutionScale MEMBER resolutionScale NOTIFY dirty)
     Q_PROPERTY(bool debugFade MEMBER debugFade NOTIFY dirty)
     Q_PROPERTY(float debugFadePercent MEMBER debugFadePercent NOTIFY dirty)
 public:
     float fadeScale{ 0.5f };
     float fadeDuration{ 3.0f };
+    float resolutionScale{ 1.f };
     float debugFadePercent{ 0.f };
     bool debugFade{ false };
 
diff --git a/libraries/render/src/render/ResampleTask.cpp b/libraries/render/src/render/ResampleTask.cpp
index 07f7367582..008234b437 100644
--- a/libraries/render/src/render/ResampleTask.cpp
+++ b/libraries/render/src/render/ResampleTask.cpp
@@ -81,3 +81,69 @@ void HalfDownsample::run(const RenderContextPointer& renderContext, const gpu::F
         batch.draw(gpu::TRIANGLE_STRIP, 4);
     });
 }
+
+gpu::PipelinePointer Upsample::_pipeline;
+
+void Upsample::configure(const Config& config) {
+    _factor = config.factor;
+}
+
+gpu::FramebufferPointer Upsample::getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer) {
+    if (_factor == 1.0f) {
+        return sourceFramebuffer;
+    }
+
+    auto resampledFramebufferSize = glm::uvec2(glm::vec2(sourceFramebuffer->getSize()) * _factor);
+
+    if (!_destinationFrameBuffer || resampledFramebufferSize != _destinationFrameBuffer->getSize()) {
+        _destinationFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("UpsampledOutput"));
+
+        auto sampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
+        auto target = gpu::Texture::createRenderBuffer(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), resampledFramebufferSize.x, resampledFramebufferSize.y, gpu::Texture::SINGLE_MIP, sampler);
+        _destinationFrameBuffer->setRenderBuffer(0, target);
+    }
+    return _destinationFrameBuffer;
+}
+
+void Upsample::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& resampledFrameBuffer) {
+    assert(renderContext->args);
+    assert(renderContext->args->hasViewFrustum());
+    RenderArgs* args = renderContext->args;
+
+    resampledFrameBuffer = getResampledFrameBuffer(sourceFramebuffer);
+    if (resampledFrameBuffer != sourceFramebuffer) {
+        if (!_pipeline) {
+            auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS();
+            auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS();
+            gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
+
+            gpu::Shader::BindingSet slotBindings;
+            gpu::Shader::makeProgram(*program, slotBindings);
+
+            gpu::StatePointer state = gpu::StatePointer(new gpu::State());
+            state->setDepthTest(gpu::State::DepthTest(false, false));
+            _pipeline = gpu::Pipeline::create(program, state);
+        }
+
+        const auto bufferSize = resampledFrameBuffer->getSize();
+        glm::ivec4 viewport{ 0, 0, bufferSize.x, bufferSize.y };
+
+        gpu::doInBatch("Upsample::run", args->_context, [&](gpu::Batch& batch) {
+            batch.enableStereo(false);
+
+            batch.setFramebuffer(resampledFrameBuffer);
+
+            batch.setViewportTransform(viewport);
+            batch.setProjectionTransform(glm::mat4());
+            batch.resetViewTransform();
+            batch.setPipeline(_pipeline);
+
+            batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(bufferSize, viewport));
+            batch.setResourceTexture(0, sourceFramebuffer->getRenderBuffer(0));
+            batch.draw(gpu::TRIANGLE_STRIP, 4);
+        });
+
+        // Set full final viewport
+        args->_viewport = viewport;
+    }
+}
diff --git a/libraries/render/src/render/ResampleTask.h b/libraries/render/src/render/ResampleTask.h
index da2b7b3537..25f9c6a3e9 100644
--- a/libraries/render/src/render/ResampleTask.h
+++ b/libraries/render/src/render/ResampleTask.h
@@ -36,6 +36,37 @@ namespace render {
 
         gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer);
     };
+
+    class UpsampleConfig : public render::Job::Config {
+        Q_OBJECT
+            Q_PROPERTY(float factor MEMBER factor NOTIFY dirty)
+    public:
+
+        float factor{ 1.0f };
+
+    signals:
+        void dirty();
+    };
+
+    class Upsample {
+    public:
+        using Config = UpsampleConfig;
+        using JobModel = Job::ModelIO<Upsample, gpu::FramebufferPointer, gpu::FramebufferPointer, Config>;
+
+        Upsample(float factor = 2.0f) : _factor{ factor } {}
+
+        void configure(const Config& config);
+        void run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& resampledFrameBuffer);
+
+    protected:
+
+        static gpu::PipelinePointer _pipeline;
+
+        gpu::FramebufferPointer _destinationFrameBuffer;
+        float _factor{ 2.0f };
+
+        gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer);
+    };
 }
 
 #endif // hifi_render_ResampleTask_h