在现代渲染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 -;
} 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_()
, 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设计的文章还会对这里的设计做展开分析。
在现代渲染API下,封装跨平台渲染框架的尝试 - 资源管理的更多相关文章
- 跨平台渲染框架尝试 - GPU Buffer的管理(1)
buffer资源 下面来谈谈buffer的管理.buffer资源从广义上就是C语言的数组.如下图所示. 图 buffer的广义模型 在渲染管线中,无论是opengl还是dx或者其他的渲染api,都会提 ...
- [原创]SOUI GDI+渲染引擎下的字体特效,抛砖引玉
由于SOUI是一种双渲染引擎的DUI库,默认在SKIA渲染引擎下是支持特效字体的,具体请参考DEMO中的源码. 但是使用GDI+渲染时是没有这些特效的,着实比较苦恼,此代抛砖引玉,细节实现 请自己再去 ...
- 【C++】使用 libass,完成 Direct3D 11 下的字幕渲染
前言 前段时间曾经写过一个视频播放器:https://www.cnblogs.com/judgeou/p/14746051.html . 然而这个播放器却无法显示出外挂或者内封的字幕,这里要稍微解释一 ...
- Unite 2018 | 《崩坏3》:在Unity中实现高品质的卡通渲染(下)
http://forum.china.unity3d.com/thread-32273-1-1.html 今天我们继续分享米哈游技术总监贺甲在Unite Beijing 2018大会上的演讲<在 ...
- Fedora 24 Linux 环境下实现 Infinality 字体渲染增强及 Java 字体渲染改善的方法(修订)
Fedora 24 Linux 桌面环境默认字体渲染引擎 freetype 及字体配置工具 fontconfig 采用的是未经优化的编译及设置,字体渲染效果比较差.而某些 Linux 发行版的桌面字体 ...
- Layui下拉选渲染
下拉选渲染有很多方式,这个比较简单,记录一下: HTML代码如下: <div class="layui-input-inline"> <input type=&q ...
- 实例PK(Vue服务端渲染 VS Vue浏览器端渲染)
Vue 2.0 开始支持服务端渲染的功能,所以本文章也是基于vue 2.0以上版本.网上对于服务端渲染的资料还是比较少,最经典的莫过于Vue作者尤雨溪大神的 vue-hacker-news.本人在公司 ...
- Vue服务端渲染和Vue浏览器端渲染的性能对比
Vue 2.0 开始支持服务端渲染的功能,所以本文章也是基于vue 2.0以上版本.网上对于服务端渲染的资料还是比较少,最经典的莫过于Vue作者尤雨溪大神的 vue-hacker-news.本人在公司 ...
- Vue服务端渲染 VS Vue浏览器端渲染)
Vue 2.0 开始支持服务端渲染的功能,所以本文章也是基于vue 2.0以上版本.网上对于服务端渲染的资料还是比较少,最经典的莫过于Vue作者尤雨溪大神的 vue-hacker-news.本人在公司 ...
随机推荐
- SVN二次开发——让SVN、TSVN(TortoiseSVN)支持windows的访问控制模型、NTFS ADS(可选数据流、NTFS的安全属性)
SVN二次开发 ——让SVN.TSVN(TortoiseSVN)支持windows的访问控制模型.NTFS ADS (可选数据流.NTFS的安全属性) SVN secondary developmen ...
- MDX示例:求解中位数、四分位数(median、quartile)
一个人力资源咨询集团通过网络爬虫采集手段将多个知名招聘网站上发布的求职和招聘等信息准实时采集到自己的库里,形成一个数据量浩大的招聘信息库,跟踪全国招聘和求职的行业.工种.职位.待遇等信息,并通过商业智 ...
- Python基础第四天
必须掌握的内置函数 bytes() divmod() eval() exec() isinstance() range() 常用函数 1.数学相关 abs(x) abs()返回一个数字的绝对值.如果给 ...
- 【转】linux之自建yum仓库
原链接:http://www.live-in.org/archives/1410.html 平时使用yum方式安装更新软件,可以自建一个yum源,同步官方更新源,这样如果本地有机器要升级的话就可以直接 ...
- Python核心编程(第九章)--文件和输入输出
文件内建函数: open()函数提供了初始化输入/输出操作的通用接口 open()基本语法:file_object = open(filename,access_mode='r',buffering= ...
- Python学习(一) Python安装配置
我本身是Java程序猿,听说Python很强大,所以准备学习一下Python,虽说语言都是相同的,但java跟python肯定还是有区别的.希望在此记录一下自己的学习过程. 目前,Python分2.X ...
- 字符串:格式化 - 零基础入门学习Python015
字符串:格式化 让编程改变世界 Change the world by program 字符串:格式化 上节课我们介绍了Python字符串的N多种奇葩方法的用法,但我们唯独漏了一个format()方法 ...
- C语言 中缀转后缀
给定字符串型的算术表达式,实现中缀转后缀并运算得出结果: #ifndef STACK_H_INCLUDED #define STACK_H_INCLUDED #include <stdio.h& ...
- VirtualBox修改虚拟盘路径
VirtualBox虚拟盘路径默认是存在C盘的,而当我们发现C盘不够用的时候,想转移就感觉有点麻烦了,现在给大家介绍一个简单又使用的方法. 第一步:到默认目录C:\Users\Administrato ...
- 'ascii' codec can't decode byte 0xef in position 0: ordinal not in range(128)——引用
在Django视图函数中经常出现类似于'ascii' codec can't decode byte 0xef in position 0: ordinal not in range(128)的错误. ...