zoukankan      html  css  js  c++  java
  • 在现代渲染API下,封装跨平台渲染框架的尝试

    小生资历浅薄,不讨论该主题的重要性与未来的意义,只是个人兴趣爱好平日对这个问题思考了很多,总觉得要写点东西记录下来。框架还没有定型,只是记录自己设计的过程。

    系统要跨平台,首先得将平台相关的实现与平台无关的实现分离。鄙文就对资源管理的平台先关与平台无关的分离做一些浅薄的论述。Texture和Buffer的管理将在随后两篇文章中讨论。

    通常引擎会这样封装一个资源,来达到跨平台的目的

    struct ITexture;        // abstruct interface
    
    class D3DTexture : public ITexture
    {
        // dummy
    private:
        ID3D11Texture2D* m_textureInterface;
    };
    
    class GLTexture : public ITexture
    {
        // dummy
    private:
        unsigned int m_nRefCount;
        int m_nTexture;
    };

    然后再运行期的时候由factory根据自己的渲染器来决定创建哪一个实现,剩下就交给The magic of virtual function.

    然而这种设计没有将平台相关的代码与平台无关的代码分离。事实上,平台相关的代码是平台无关代码的实现,泛化关系有着比较高的耦合度。其次就是OO的设计问题。一般情况下在确定渲染平台后的程序运行时,是不存在ITexture指向不同平台实现的情况的。(例如在DX平台中有两个ITexture实例,一个指向D3DTexture,一个指向GLTexture)。虽然程序中是不会犯这种逻辑错误,但是OO给了这种可能。因此,我们在这里完全不需要运行期的分派(虚函数)。

    基于上面两个问题的考量,首先我们要把平台相关与平台无关的代码分离。他们之间用一个ID联系着。

    class texture
    {
    private:
        int id_;
    };
    
    namespace directx
    {
        std::map<int, texture> textures;    //当然,这个容器可能封装在某个manager中
    }

    首先,泛化关系变成了桥接关系,中间以一个ID作为索引的协议,耦合度降低了很多。并且平台无关的实现完全不知道平台相关的实现细节。其次,没有运行期的分派(虚函数),完全阻止了OO设计带来运行期错误实例创建的可能性。小生也是不喜欢用虚函数,如果能用std::function和这种类似设计代替的都不会用虚函数。

    在渲染框架中,资源都是统一管理的,甚至可以说在一个渲染框架中,GPU对象都是可以统一管理的。比如我们可以使用一个共同的基类object来抽象GPU的对象,其中有Resource,Sampler,Shader。Resource又分Texture和Buffer. 而Texture和Buffer又有各自的泛化

    object的抽象存在的意义就是统一管理引用计数,管理对象类型和RTTI等。下面我们用modern c++已有的组件来给出一个实现。

    namespace pipeline
    {
        // 
        enum class object_type
        {
            texture_1d,
            texture_2d,
            texture_3d,
            texture_cube,
            texture_1d_array,
            texture_2d_array,
            texture_cube_array,
            
            //  ....
            //  dummy
            //  ....
            
            vertex_shader,
            sampler,
        };
        
        // enable_shared_from_this是为了使用shared_ptr来完成引用计数
        class object : public std::enable_shared_from_this<object>
        {
        public:
            static constexpr uint32_t invalid_handle()
            {
                return -1;
            }
            
        public:
            // object统一管理不给出默认构造函数
            object() = delete;
            // 可能会从object来西沟一个对象
            virtual ~object() = default;
            // 没有拷贝语义
            object(object const&) = delete;
            object& operator= (object const&) = delete;
            // 能有move语义
            object(object&&) = default;
            object& operator= (object&&) = default;
            
        protected:
            // 不公开构造object的方法
            object(object_type type, string const& name)
                : type_(type)
                , id_(invalid_handle())
                , name_(name)
            {
    
            }
            
        public:
            object_type type() const noexcept
            {
                return type_;
            }
    
            string& name() noexcept
            {
                return name_;
            }
    
            string const& name() const noexcept
            {
                return name_;
            }
    
            uint32_t& id() noexcept
            {
                return id_;
            }
    
            uint32_t id() const noexcept
            {
                return id_;
            }
            
        private:
            object_type   type_;
            uint32_t      id_;
            string        name_;
        };
    };

    object通常要用一个object manager来统一管理。管理object的创建,回收。这里小生借鉴了modern C++ desing一书中的工厂来实现。

    namespace pipeline
    {
        class graphics_object_manager
        {
            typedef std::map<string, object*> object_container;
            typedef std::function<object*(string const& name)> function_type;
            typedef std::map<object_type, function_type> function_container;
        public:
            
            // shader_ptr使用的自定义deleter,最终是用manager来回收
            struct deleter
            {
                void operator() (object* obj) const noexcept
                {
                    assert(nullptr != obj);
                    graphics_object_manager::get().delete_object(obj->name(), obj);
                }
            };
    
            // singleton 暂时不考虑多线程
            static graphics_object_manager& get()
            {
                static graphics_object_manager factor;
                return factor;
            }
    
            // 创建函数
            auto get_object(string const& name, object_type type) -> std::shared_ptr<object>
            {
                auto itr = objects_.find(name);
                object* created_object = nullptr;
                std::shared_ptr<object> obj;
    
                if (objects_.end() == itr)
                {
                    auto func_itr = functinos_.find(type);
                    if (functinos_.end() == func_itr)
                        throw std::exception{};
    
                    created_object = func_itr->second(name);
                    objects_[name] = created_object;
    
                    obj.reset(created_object, deleter{});
                }
                else
                {
                    created_object = itr->second;
                    if(type != created_object->type())
                        throw std::exception{};
                }
                return created_object->shared_from_this();
            }
    
            // 注册一个对象类型的创建方法
            bool register_creator(object_type type, function_type f)
            {
                auto itr = functinos_.end();
                auto result = false;
                std::tie(itr, result) = functinos_.emplace(type, f);
                return result;
            }
    
            // 有注册当然也有反注册
            void unregister_creator(object_type type)
            {
                auto itr = functinos_.find(type);
                if (functinos_.end() != itr)
                    functinos_.erase(itr);
            }
    
            // 当shader_ptr析构返现引用计数为0是,会由注册的deleter来调用manager的回收函数
            void delete_object(std::string const& name, object* object)
            {
                assert(nullptr != object);
    
                auto itr = objects_.find(name);
                if (itr == objects_.end())
                    throw std::exception{};
    
                if (itr->second != object)
                    throw std::exception{};
                
                delete object;
                objects_.erase(itr);
            }
    
        private:
            object_container    objects_;
            function_container    functinos_;
        };
    }

    register_creator和unregister_creator注册一个泛化仿函数到manager中来负责某一个object_type的创建工作。这是一个消除switch case的良好设计,不仅会可以让代码看起来更简洁优雅,更重要的是它消除create函数的集中管来带来的高耦合。详细请参看Modern C++ design.

    get_object函数是一个创建函数,如果在容器中发现已有同名对象,做一个类型检查就返回出来。当没有时,manager会查找object_type为type的创建函数(std::function对象),创建它。并从裸指针通过shared_from_this返回shader_ptr对象。shared_from_this必须要对该指针已经创建了一个shared_ptr才能调用,不然会抛出异常,所以我们创建了一个临时的shared_ptr对象,并注册deleter。函数返回的时候shared_ptr还没有析构,所以shared_from_this能返回一个有效的shared_ptr. 因此我们在manager中只用裸指针来管理这些对象了。

    delete_object是当shared_ptr析构时发现引用计数为0,由pipeline::graphics_object_manager::deleter仿函数调用的,是在创建object的时候向shared_ptr注册的。

    下面就是resource类的设计了,先给出代码再来谈谈自己的设计。

    namespace pipeline
    {
        enum class device_access
        {
            none,
            read,
            write,
            read_write,
        };
        
        template <typename Impl>
        class resource : public object
        {
        protected:
            resource(string const& name)
                : object(Impl::type(), name)
                , size_(0)
                , data_()
                , cpu_access_(device_access::none)
                , gpu_access_(device_access::none)
            {
            }
    
        public:
    
            byte const* data() const
            {
                return data_.data();
            }
    
            byte size() const
            {
                return size_;
            }
    
            device_access cpu_access() const
            {
                return cpu_access_;
            }
    
            device_access& cpu_access()
            {
                return cpu_access_;
            }
    
            device_access gpu_access() const
            {
                return gpu_access_;
            }
    
            device_access& gpu_access()
            {
                return gpu_access_;
            }

    protected: size_t size_; // 32bit integral for x86 & 64bit integral for x64 std::vector<byte> data_; device_access cpu_access_; device_access gpu_access_; }; }

    resource抽象在buffer和texture之上的。GPU资源就是一堆内存和CPU与GPU的访问权限。只所以要用模板是因为,这里无法保证运行期的LSP,Impl模板参数是实际的资源参数,并使用CRTP传递下来的。这样就保证了其实际类型不会丢失,还能用来计算object_type.

    本文先到这里,后面阐述texture与buffer设计的文章还会对这里的设计做展开分析。

  • 相关阅读:
    0052 html5多媒体音频标签audio
    0051 HTML5概述、新增标签、简单案例
    0050 VSCode软件
    0048 :focus -- 获得焦点元素
    0046 CSS3过渡:transition
    Linux 中的常见锁及其基本原理
    小程序海报最佳实现思路,可视化编辑直接生成代码使用
    babel插件的相关知识
    vue-next 函数式 api
    小程序如何改变onLoad 的执行时机?
  • 原文地址:https://www.cnblogs.com/IndignangAngel/p/4836270.html
Copyright © 2011-2022 走看看