--- /dev/null
+/*
+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 };
+};