]> git.lizzy.rs Git - minetest.git/blobdiff - src/client/render/pipeline.h
Implement rendering pipeline and post-processing (#12465)
[minetest.git] / src / client / render / pipeline.h
diff --git a/src/client/render/pipeline.h b/src/client/render/pipeline.h
new file mode 100644 (file)
index 0000000..4a6df7d
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+Minetest
+Copyright (C) 2022 x2048, Dmitry Kostenko <codeforsmile@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+
+#include <vector>
+#include <memory>
+#include <string>
+
+class RenderSource;
+class RenderTarget;
+class RenderStep;
+class Client;
+class Hud;
+class ShadowRenderer;
+
+struct PipelineContext
+{
+       PipelineContext(IrrlichtDevice *_device, Client *_client, Hud *_hud, ShadowRenderer *_shadow_renderer, video::SColor _color, v2u32 _target_size)
+               : device(_device), client(_client), hud(_hud), shadow_renderer(_shadow_renderer), clear_color(_color), target_size(_target_size)
+       {
+       }
+
+       IrrlichtDevice *device;
+       Client *client;
+       Hud *hud;
+       ShadowRenderer *shadow_renderer;
+       video::SColor clear_color;
+       v2u32 target_size;
+
+       bool show_hud {true};
+       bool show_minimap {true};
+       bool draw_wield_tool {true};
+       bool draw_crosshair {true};
+};
+
+/**
+ * Base object that can be owned by RenderPipeline
+ * 
+ */
+class RenderPipelineObject
+{
+public:
+       virtual ~RenderPipelineObject() = default;
+       virtual void reset(PipelineContext &context) {}
+};
+
+/**
+ * Represents a source of rendering information such as textures
+ */
+class RenderSource : virtual public RenderPipelineObject
+{
+public:
+       /**
+        * Return the number of textures in the source.
+        */
+       virtual u8 getTextureCount() = 0;
+
+       /**
+        * Get a texture by index. 
+        * Returns nullptr is the texture does not exist.
+        */
+       virtual video::ITexture *getTexture(u8 index) = 0;
+};
+
+/**
+ *     Represents a render target (screen or framebuffer)
+ */
+class RenderTarget : virtual public RenderPipelineObject
+{
+public:
+       /**
+        * Activate the render target and configure OpenGL state for the output.
+        * This is usually done by @see RenderStep implementations.
+        */
+       virtual void activate(PipelineContext &context)
+       {
+               m_clear = false;
+       }
+
+       /**
+        * Resets the state of the object for the next pipeline iteration
+        */
+       virtual void reset(PipelineContext &context) override
+       {
+               m_clear = true;
+       }
+
+protected:
+       bool m_clear {true};
+};
+
+/**
+ * Texture buffer represents a framebuffer with a multiple attached textures.
+ *
+ * @note Use of TextureBuffer requires use of gl_FragData[] in the shader
+ */
+class TextureBuffer : public RenderSource, public RenderTarget
+{
+public:
+       virtual ~TextureBuffer() override;
+
+       /**
+        * Configure fixed-size texture for the specific index
+        * 
+        * @param index index of the texture
+        * @param size width and height of the texture in pixels
+        * @param height height of the texture in pixels
+        * @param name unique name of the texture
+        * @param format color format
+        */
+       void setTexture(u8 index, core::dimension2du size, const std::string& name, video::ECOLOR_FORMAT format);
+
+       /**
+        * Configure relative-size texture for the specific index
+        * 
+        * @param index index of the texture
+        * @param scale_factor relation of the texture dimensions to the screen dimensions
+        * @param name unique name of the texture
+        * @param format color format
+        */
+       void setTexture(u8 index, v2f scale_factor, const std::string& name, video::ECOLOR_FORMAT format);
+
+       /**
+        * @Configure depth texture and assign index
+        * 
+        * @param index index to use for the depth texture
+        * @param size width and height of the texture in pixels
+        * @param name unique name for the texture
+        * @param format color format
+        */
+       void setDepthTexture(u8 index, core::dimension2du size, const std::string& name, video::ECOLOR_FORMAT format);
+
+       /**
+        * @Configure depth texture and assign index
+        * 
+        * @param index index to use for the depth texture
+        * @param scale_factor relation of the texture dimensions to the screen dimensions
+        * @param name unique name for the texture
+        * @param format color format
+        */
+       void setDepthTexture(u8 index, v2f scale_factor, const std::string& name, video::ECOLOR_FORMAT format);
+
+       virtual u8 getTextureCount() override { return m_textures.size(); }
+       virtual video::ITexture *getTexture(u8 index) override;
+       virtual void activate(PipelineContext &context) override;
+       virtual void reset(PipelineContext &context) override;
+private:
+       static const u8 NO_DEPTH_TEXTURE = 255;
+
+       struct TextureDefinition
+       {
+               bool valid { false };
+               bool fixed_size { false };
+               bool dirty { false };
+               v2f scale_factor;
+               core::dimension2du size;
+               std::string name;
+               video::ECOLOR_FORMAT format;
+       };
+
+       /**
+        * Make sure the texture in the given slot matches the texture definition given the current context.
+        * @param textureSlot address of the texture pointer to verify and populate.
+        * @param definition logical definition of the texture
+        * @param context current context of the rendering pipeline
+        * @return true if a new texture was created and put into the slot
+        * @return false if the slot was not modified
+        */
+       bool ensureTexture(video::ITexture **textureSlot, const TextureDefinition& definition, PipelineContext &context);
+
+       video::IVideoDriver *m_driver { nullptr };
+       std::vector<TextureDefinition> m_definitions;
+       core::array<video::ITexture *> m_textures;
+       video::ITexture *m_depth_texture { nullptr };
+       u8 m_depth_texture_index { NO_DEPTH_TEXTURE };
+       video::IRenderTarget *m_render_target { nullptr };
+};
+
+/**
+ * Targets output to designated texture in texture buffer
+ */
+class TextureBufferOutput : public RenderTarget
+{
+public:
+       TextureBufferOutput(TextureBuffer *buffer, u8 texture_index);
+       void activate(PipelineContext &context) override;
+private:
+       TextureBuffer *buffer;
+       u8 texture_index;
+};
+
+/**
+ * Allows remapping texture indicies in another RenderSource.
+ * 
+ * @note all unmapped indexes are passed through to the underlying render source.
+ */
+class RemappingSource : RenderSource
+{
+public:
+       RemappingSource(RenderSource *source)
+                       : m_source(source)
+       {}
+
+       /**
+        * Maps texture index to a different index in the dependent source.
+        * 
+        * @param index texture index as requested by the @see RenderStep.
+        * @param target_index matching texture index in the underlying @see RenderSource.
+        */
+       void setMapping(u8 index, u8 target_index)
+       {
+               if (index >= m_mappings.size()) {
+                       u8 start = m_mappings.size();
+                       m_mappings.resize(index);
+                       for (u8 i = start; i < m_mappings.size(); ++i)
+                               m_mappings[i] = i;
+               }
+
+               m_mappings[index] = target_index;
+       }
+
+       virtual u8 getTextureCount() override
+       {
+               return m_mappings.size();
+       }
+
+       virtual video::ITexture *getTexture(u8 index) override
+       {
+               if (index < m_mappings.size())
+                       index = m_mappings[index];
+
+               return m_source->getTexture(index);
+       }
+public:
+       RenderSource *m_source;
+       std::vector<u8> m_mappings;
+};
+
+class DynamicSource : public RenderSource
+{
+public:
+       bool isConfigured() { return upstream != nullptr; }
+       void setRenderSource(RenderSource *value) { upstream = value; }
+
+       /**
+        * Return the number of textures in the source.
+        */
+       virtual u8 getTextureCount() override;
+
+       /**
+        * Get a texture by index. 
+        * Returns nullptr is the texture does not exist.
+        */
+       virtual video::ITexture *getTexture(u8 index) override;
+private:
+       RenderSource *upstream { nullptr };
+};
+
+/**
+ * Implements direct output to screen framebuffer.
+ */
+class ScreenTarget : public RenderTarget
+{
+public:
+       virtual void activate(PipelineContext &context) override;
+       virtual void reset(PipelineContext &context) override;
+private:
+       core::dimension2du size;
+};
+
+class DynamicTarget : public RenderTarget
+{
+public:
+       bool isConfigured() { return upstream != nullptr; }
+       void setRenderTarget(RenderTarget *value) { upstream = value; }
+       virtual void activate(PipelineContext &context) override;
+private:
+       RenderTarget *upstream { nullptr };
+};
+
+/**
+ * Base class for rendering steps in the pipeline
+ */
+class RenderStep : virtual public RenderPipelineObject
+{
+public:
+       /**
+        * Assigns render source to this step.
+        * 
+        * @param source source of rendering information
+        */
+       virtual void setRenderSource(RenderSource *source) = 0;
+
+       /**
+        * Assigned render target to this step.
+        * 
+        * @param target render target to send output to.
+        */
+       virtual void setRenderTarget(RenderTarget *target) = 0;
+
+       /**
+        * Runs the step. This method is invoked by the pipeline.
+        */
+       virtual void run(PipelineContext &context) = 0;
+};
+
+/**
+ * Provides default empty implementation of supporting methods in a rendering step.
+ */
+class TrivialRenderStep : public RenderStep
+{
+public:
+       virtual void setRenderSource(RenderSource *source) override {}
+       virtual void setRenderTarget(RenderTarget *target) override {}
+       virtual void reset(PipelineContext &) override {}
+};
+
+/**
+ * Dynamically changes render target of another step.
+ * 
+ * This allows re-running parts of the pipeline with different outputs
+ */
+class SetRenderTargetStep : public TrivialRenderStep
+{
+public:
+       SetRenderTargetStep(RenderStep *step, RenderTarget *target);
+       virtual void run(PipelineContext &context) override;
+private:
+       RenderStep *step;
+       RenderTarget *target;
+};
+
+/**
+ * Render Pipeline provides a flexible way to execute rendering steps in the engine.
+ * 
+ * RenderPipeline also implements @see RenderStep, allowing for nesting of the pipelines.
+ */
+class RenderPipeline : public RenderStep
+{
+public:
+       /**
+        * Add a step to the end of the pipeline
+        * 
+        * @param step reference to a @see RenderStep implementation.
+        */
+       RenderStep *addStep(RenderStep *step)
+       {
+               m_pipeline.push_back(step);
+               return step;
+       }
+
+       /**
+        * Capture ownership of a dynamically created @see RenderStep instance.
+        * 
+        * RenderPipeline will delete the instance when the pipeline is destroyed.
+        * 
+        * @param step reference to the instance.
+        * @return RenderStep* value of the 'step' parameter.
+        */
+       template<typename T>
+       T *own(std::unique_ptr<T> &&object)
+       {
+               T* result = object.release();
+               m_objects.push_back(std::unique_ptr<RenderPipelineObject>(result));
+               return result;
+       }
+
+       /**
+        * Create a new object that will be managed by the pipeline
+        *
+        * @tparam T type of the object to be created
+        * @tparam Args types of constructor arguments
+        * @param args constructor arguments
+        * @return T* pointer to the newly created object
+        */
+       template<typename T, typename... Args>
+       T *createOwned(Args&&... args) {
+               return own(std::make_unique<T>(std::forward<Args>(args)...));
+       }
+
+       /**
+        * Create and add a step managed by the pipeline and return a pointer
+        * to the step for further configuration.
+        *
+        * @tparam T Type of the step to be added.
+        * @tparam Args Types of the constructor parameters
+        * @param args Constructor parameters
+        * @return RenderStep* Pointer to the created step for further configuration.
+        */
+       template<typename T, typename... Args>
+       RenderStep *addStep(Args&&... args) {
+               T* result = own(std::make_unique<T>(std::forward<Args>(args)...));
+               return addStep(result);
+       }
+
+       RenderSource *getInput();
+       RenderTarget *getOutput();
+
+       v2f getScale() { return scale; }
+       void setScale(v2f value) { scale = value; }
+
+       virtual void reset(PipelineContext &context) override {}
+       virtual void run(PipelineContext &context) override;
+
+       virtual void setRenderSource(RenderSource *source) override;
+       virtual void setRenderTarget(RenderTarget *target) override;
+private:
+       std::vector<RenderStep *> m_pipeline;
+       std::vector< std::unique_ptr<RenderPipelineObject> > m_objects;
+       DynamicSource m_input;
+       DynamicTarget m_output;
+       v2f scale { 1.0f, 1.0f };
+};