纹理是渲染器重要的资源,也是比较简单的资源。本文将详细讨论纹理资源的管理。

在资源管理概述中提到,资源就是一堆内存和CPU与GPU的访问权限。纹理管理在资源管理之上,要负责如何使用者一堆内存构造纹理对象,并告诉渲染器如何使用平台相关的纹理对象。下面,我们开始详细论述。

1. 纹理资源

首先纹理资源是GPU可以使用到的资源。它与Buffer资源不同的地方在于,相邻像素的插值计算中,纹理比Buffer简单并快得多,因为有相应的硬件实现。纹理资源字面意义上就像是一张像素图,但它不仅限于二维的像素的图,还有一维和三维的。如下图,截于Practise Rendering and Computation with Direct3D 11

此外,除了作为RenderTarget和DepthStencil的纹理,一般都有mipmap.  纹理的原始数据作为mipmap的第零级,而每往下一级的mipmap尺寸是上一级的一半。这些基本的概念都会在一些图形学的书中出现,这里就不赘述了。

有的纹理资源除了有mipmap之外,还是一个纹理数组(Texutre Array)。Cube Map是一个很特别的纹理数组,其数组大小始终为6,Cube Map Array始终是6的倍数。而Texture3D Array出于存储大小的原因,暂时不被各种API支持。

纹理数组与mipmap通常是统一管理的,使用Subresource的概念。如下图

以一个纹理大小为8个像素,数组大小为3的Texture1D Array对象为例,其subresource的编号一次由第一张纹理的mipmap第0级至第一张的第3级,再从第二级的mipmap第0级开始一次类推。

纹理的管理还有一个很重要的内容,就是像素格式。像素格式有压缩与无压缩之分,压缩的像素格式主要是以BC1-7位代表的有损压缩格式,由于其以4x4像素为单位的存储方式,它会影响其subresource真正的物理大小。如下图

无压缩的纹理在生成mipmap的时候,纹理大小始终与占用的物理内存大小一致,如上图左边的纹理。而BC压缩纹理,其纹理有一个虚拟尺寸,需要将纹理的大小向4对齐,所以其实际占用的内存大小要更多一些。详细请参阅文档 https://msdn.microsoft.com/en-us/library/windows/desktop/bb694531(v=vs.85).aspx

综上所述,纹理需要管理如下内容。纹理类型,像素格式,纹理大小及Subresource.

2. 设计

在管理纹理的类型,上一文中已经展示方法,就是object_type的scoped枚举类型中赋予相应的值,再来回顾一下

  1. namespace pipeline
  2. {
  3. enum class object_type
  4. {
  5. texture_1d,
  6. texture_2d,
  7. texture_3d,
  8. texture_cube,
  9. texture_1d_array,
  10. texture_2d_array,
  11. texture_cube_array,
  12. texture_rt,
  13. texture_dp,
  14. };
  15. }

像素格式,同样适用一个scoped枚举类型。

  1. namespace pipeline
  2. {
  3. enum class pixel_format
  4. {
  5. unknown,
  6. rgba_32,
  7. //.......
  8. bc_1,
  9. //.......
  10. };
  11. }

下面先给出texture纹理模板的实现,再来详细讨论。

  1. namespace pipeline
  2. {
  3. template <typename Impl>
  4. struct texture_traits;

  5. struct subresource
      {
        uint16_t widht;
        uint16_t height;
        uint16_t depth;
        uint16_t array_size;
        uint16_t mip_level;
        size_t  row_pitch;
        size_t  slice_pitch;
        byte*   ptr;
      };
  6. template <typename Impl>
  7. class texture : public resource<texture<Impl>>
  8. {public:
  9. using base_type = resource<texture<Policy>>;
  10. using this_type = texture;using subresource_constainer = std::vector<subresource>;
  11. using traits_type = texture_traits<Impl>;
  12.  
  13. protected:
  14.  
  15. texture(string const& name, pixel_format format, uint16_t width,
  16. uint16_t height, uint16_t depth, uint16_t array_size, bool has_mipmap) noexcept
  17. : base_type(name)
         , pixel_format_(format)
  18. , width_(width)
  19. , height_(height)
  20. , depth_(depth)
  21. , array_size_(size)
  22. , has_mipmap_(has_mipmap)
  23. , subresources_()
  24. {
  25.  
  26. }
  27.  
  28. public:
  29.  
  30. template <typename = std::enable_if_t<traits_type::is_texture_1d()>>
  31. void resize(uint16_t widht) noexcept
  32. {
  33. width_ = widht;
  34. }
  35.  
  36. template <typename = std::enable_if_t<traits_type::is_texture_2d()>>
  37. void resize(uint16_t widht, uint16_t height) noexcept
  38. {
  39. width_ = widht;
  40. height_ = height;
  41. }
  42.  
  43. template <typename = std::enable_if_t<traits_type::is_texture_3d()>>
  44. void resize(uint16_t widht, uint16_t height, uint16_t depth) noexcept
  45. {
  46. width_ = widht;
  47. height_ = height;
  48. depth_ = depth
  49. }
  50.  
  51. template <typename = std::enable_if_t<traits_type::is_texture_array()>>
  52. void set_array_size(uint16_t array_size) noexcept
  53. {
  54. array_size_ = array_size * traits_type::array_size_unit();
  55. }
  56.  
  57. void set_pixel_format(pixel_format pixel_format)
  58. {
  59. traits_type::validate_format(pixel_format);
  60. pixel_format_ = pixel_format;
  61. }
  62.  
  63. uint16_t width() const noexcept
  64. {
  65. return width_;
  66. }
  67.  
  68. uint16_t height() const noexcept
  69. {
  70. return height_;
  71. }
  72.  
  73. uint16_t depth() const noexcept
  74. {
  75. return depth_;
  76. }
  77.  
  78. uint16_t array_size() const noexcept
  79. {
  80. return array_size_;
  81. }
  82.  
  83. bool& mipmap() noexcept
  84. {
  85. return has_mipmap_;
  86. }
  87.  
  88. bool mipmap() const noexcept
  89. {
  90. return has_mipmap_;
  91. }
  92.  
  93. void construct()
  94. {
  95. detail::subresource_generator gen
  96. {
  97. pixel_format_, width_, height_, depth_, array_size_ , has_mipmap_
  98. };
  99.  
  100. data_ = std::move(gen.data());
  101. size_ = data_.size();
  102. subresources_ = std::move(gen.subresource());
  103. }
  104.  
  105. decltype(auto) begin() noexcept
  106. {
  107. return subresources_.begin();
  108. }
  109.  
  110. decltype(auto) end() noexcept
  111. {
  112. return subresources_.end();
  113. }
  114.  
  115. private:
  116. pixel_format pixel_format_;
  117. uint16_t width_;
  118. uint16_t height_;
  119. uint16_t depth_;
  120. uint16_t array_size_;
  121. bool has_mipmap_;
  122.  
  123. subresource_constainer subresources_;
  124. };
  125. }

这里之所以要用模板,是为了尽可能的不写DRY的代码。

剖析resize函数。这个函数有三个知识点。

1. default tempalte parameter & template member function overload resolution;

2. substitution failure is no an error(SFINAE);  http://en.cppreference.com/w/cpp/language/sfinae

3. std::enable_if; http://www.boost.org/doc/libs/1_59_0/libs/core/doc/html/core/enable_if.html

如果把resize函数在texture_2D,texture_rt,texture_2d_array等都实现一遍,代码就很DRY了,并且texture_1d不能resize height分量。这里就使用了一些元编程的技巧,通过模板的traits模式做编译期的反射,配合enable_if帮我们在编译期决议resize函数是否有效。简而言之,就是texture_1d如果尝试使用resize两个参数的重载,在编译器就会报错。这样,代码只需要在texture基类实现一遍就能保证既不会出现重复代码, 又保证了编译期的安全性。类似的函数同理。

set_pixel_format函数把当前像素格式是否有效的任务分派给了traits来完成。比如texture_3d不能是bc系列的格式,texture_rt与texture_dp都有特定的像素格式。

由于texture是一个复杂对象(complex object),中间有stl容器作为成员变量,RAII的模式不太适合,因此设置参数的函数都只是一些成员变量的赋值,construct函数才是真正创建它的函数。construct函数会为texture分配内存并填充subresource. detail::subresource_generator是一个辅助类,传递texture创建所需要的全部参数,它将在构造函数中完成对texture的内存分配和填充subresource. 这里为了异常安全(exceptional safe),所有的创建都在辅助类里面,创建完毕后使用move语义,高效的将创建好的内存和subresource信息移动到texture相应的成员中。如果在创建过程中发生异常,texture的状态不会发生改变,并且辅助类对象可以完美地保证资源不会泄露。下面给出辅助类的设计。

  1. namespace pipeline { namespace detail
  2. {
  3. struct subresource_generator
  4. {
  5. subresource_generator(
  6. pixel_format format,
  7. uint16_t width,
  8. uint16_t height,
  9. uint16_t depth,
  10. uint16_t array_size,
  11. bool mipmap)
  12. {
  13. construct(format, width, height, depth, array_size, mipmap);
  14. }
  15.  
  16. void construct(pixel_format format, uint16_t width, uint16_t height, uint16_t depth, uint16_t array_size, bool mipmap)
  17. {
  18. // 没有texture3D array
  19. if (depth > && array_size > )
  20. throw std::exception();
  21.  
  22. // 不支持超过15层的mipmap,原始像素太大
  23. auto mip_levels = mipmap ? uint16_t{ } : calculate_mipmap_level(width, height, depth);
  24. if (mip_levels > )
  25. throw std::exception();
  26.  
  27. auto subresource_count = (mip_levels + ) * array_size;
  28. subresources_.resize(subresource_count);
  29.  
  30. size_t texture_size = ;
  31. auto loop = size_t{ };
  32. for (uint16_t array_index = ; array_index < array_size; ++array_index)
  33. {
  34. for (uint16_t mip_level = ; mip_level <= mip_levels; ++mip_level)
  35. {
  36. auto& subres = subresources_[loop++];
  37. subres.mip_level = mip_level;
  38. subres.array_index = array_index;
  39. subres.width = std::max<uint16_t>(width >> mip_level, );
  40. subres.height = std::max<uint16_t>(height >> mip_level, );
  41. subres.depth = std::max<uint16_t>(depth >> mip_level, );
  42. std::tie(subres.row_pitch, subres.slice_pitch) =
  43. calculate_pitch_size(format, subres.width, subres.height);
  44.  
  45. texture_size += subres.slice_pitch * depth;
  46. }
  47. }
  48.  
  49. data_.resize(texture_size);
  50. auto ptr = data_.data();
  51. for (auto& sub_res : subresources_)
  52. {
  53. sub_res.pointer = ptr;
  54. ptr += sub_res.slice_pitch * sub_res.depth;
  55. }
  56. }
  57.  
  58. auto move_data()->std::vector<byte>&&
  59. {
  60. return std::move(data_);
  61. }
  62.  
  63. auto move_subres()->std::vector<subresource>&&
  64. {
  65. return std::move(subresources_);
  66. }
  67.  
  68. private:
  69. std::vector<subresource> subresources_;
  70. std::vector<byte> data_;
  71. };
  72. } }

辅助类主要就是实现构造函数中调用的construct函数,首先它会调用calculate_mipmap_level来计算这张纹理会产生多少级的mipmap,如果它有纹理的话。接着再根据纹理的像素格式,纹理是否是Texture Array等信息为每一个subresource计算信息。这里要提到的两个函数,一个就是calculate_mipmap_level,其实现大概如下

  1. namespace pipeline { namespace detail
  2. {
  3. uint16_t calculate_mipmap_level(uint16_t width, uint16_t height, uint16_t depth)
  4. {
  5. auto mipmap_level = std::max<uint16_t>(log2_integral(width), log2_integral(height));
  6. mipmap_level = std::max<uint16_t>(log2_integral(depth), mipmap_level);
  7. return mipmap_level;
  8. }
  9. } }

其中调用的log2_integral是针对整数计算有快速优化的版本。

  1. template <typename T>
  2. auto log2_integral(T t)
  3. -> std::enable_if_t<std::is_integral<T>::value, T>
  4. {
  5. T log2 = (t & 0xAAAAAAAAu) != ;
  6. log2 |= ((t & 0xFFFF0000u) != ) << ;
  7. log2 |= ((t & 0xFF00FF00u) != ) << ;
  8. log2 |= ((t & 0xF0F0F0F0u) != ) << ;
  9. log2 |= ((t & 0xCCCCCCCCu) != ) << ;
  10. return log2;
  11. }

其次就是calculate_pitch_size函数,给定纹理的像素格式与宽和高,就能算出subresource需要的row_pitch和slice_pitch. 如果是对非压缩的像素格式,直接使用纹理的宽高原始数据就好了。但是如果使用的是压缩格式,例如BC系列的纹理格式,那么就不能逐像素(pixel wise)计算,而得逐块计算,并且也有额外的内存分配。这个函数实现比较长就不贴出来了,大家自行脑补。

接下来就是如何实作纹理的类型了。object_type中全部的纹理类型都会对应一个实作的类,篇幅原因这里以texture_2d为例。

  1. namespace pipeline
  2. {
  3. template <>
  4. struct texture_traits<texture_2d>
  5. {
  6. static constexpr bool is_texture_1d()
  7. {
  8. return false;
  9. }
  10.  
  11. static constexpr bool is_texture_2d()
  12. {
  13. return true;
  14. }
  15.  
  16. static constexpr bool is_texture_3d()
  17. {
  18. return false;
  19. }
  20.  
  21. static constexpr bool is_texture_cube()
  22. {
  23. return false;
  24. }
  25.  
  26. static constexpr bool is_texture_array()
  27. {
  28. return false;
  29. }
  30.  
  31. static void validate_format()
  32. {
  33.  
  34. }
  35. };
  36.  
  37. class texture_2d : public texture<texture_2d>
  38. {
  39. typedef texture<texture_2d> base_type;
  40. public:
  41. static constexpr object_type type()
  42. {
  43. return object_type::texture_2d;
  44. }
  45.  
  46. public:
  47. explicit texture_2d(std::string const& name)
  48. : base_type(name, pixel_format::unknown, , , , , false) noexcept
  49. {
  50.  
  51. }
  52. };
  53. }

实作的texture_2d首先需要特化texture_traits模板,提供给texture模板使用。而texture_2d实作的代码就很少了,仅仅只需要实现一个type函数,提供给texture模板的构造函数使用。如果有特殊需求,还可以给出更复杂的构造函数以方便使用。texture_2d以奇异递归模板模式(CRTP)传递类型信息到texture模板,再与texture_traits合作能够避免实作中大量的重复代码,还能保证编译期的里氏替换原则(LSP)。

Texture管理的平台无关实作就介绍到这里,下面几篇将介绍Buffer的平台无关的管理。Buffer的管理比Texture更复杂。

跨平台渲染框架尝试 - Texture管理的更多相关文章

  1. 跨平台渲染框架尝试 - constant buffer的管理

    1. Preface Constant buffer是我们在编写shader的时候,打交道最多的一种buffer resource了.constant表明了constant buffer中的数据,在一 ...

  2. 跨平台渲染框架尝试 - GPU Buffer的管理(1)

    buffer资源 下面来谈谈buffer的管理.buffer资源从广义上就是C语言的数组.如下图所示. 图 buffer的广义模型 在渲染管线中,无论是opengl还是dx或者其他的渲染api,都会提 ...

  3. 在现代渲染API下,封装跨平台渲染框架的尝试 - 资源管理

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

  4. 【Cocos2d-X开发学习笔记】第03期:渲染框架之导演类(CCDirector)的使用

    本系列学习教程使用的是cocos2d-x-2.1.4版本(截至目前为止最新稳定版) ,PC开发环境Windows7,C++开发环境VS2010 提到“导演”一词,想必读者最先联想到的是电影.作为娱乐产 ...

  5. 【Cocos2d-X开发学习笔记】第05期:渲染框架之布景层类(CCLayer)的使用

    本系列学习教程使用的是cocos2d-x-2.1.4版本(截至目前为止最新稳定版) ,PC开发环境Windows7,C++开发环境VS2010 图层也是渲染框架中很重要的内容.场景类用来划分游戏的状态 ...

  6. .NET 跨平台界面框架和为什么你首先要考虑再三

    ​​​原文地址 现在用 C# 来开发​跨平台应用已经有很成熟的方案,即共用非界面代码,而每个操作系统搭配特定的用户界面代码.这个方案的好处是可以直接使用操作系统原生的控件和第三方控件,还能够和操作系统 ...

  7. 【译】.NET 跨平台界面框架和为什么你首先要考虑再三

    现在用 C# 来开发跨平台应用已经有很成熟的方案,即共用非界面代码,而每个操作系统搭配特定的用户界面代码.这个方案的好处是可以直接使用操作系统原生的控件和第三方控件,还能够和操作系统深度集成. 这里的 ...

  8. .NET 跨平台RPC框架DotNettyRPC

    DotNettyRPC 1.简介 DotNettyRPC是一个基于DotNetty的跨平台RPC框架,支持.NET45以及.NET Standard2.0 2.产生背景 传统.NET开发中遇到远程调用 ...

  9. .NET 跨平台RPC框架DotNettyRPC Web后台快速开发框架(.NET Core) EasyWcf------无需配置,无需引用,动态绑定,轻松使用 C# .NET 0配置使用Wcf(半成品) C# .NET Socket 简单实用框架 C# .NET 0命令行安装Windows服务程序

    .NET 跨平台RPC框架DotNettyRPC   DotNettyRPC 1.简介 DotNettyRPC是一个基于DotNetty的跨平台RPC框架,支持.NET45以及.NET Standar ...

随机推荐

  1. Python代码一定要对齐

    不然会出现错误: IndentationError: unindent does not match any outer indentation level PS:新的Python语法,是不支持的代码 ...

  2. cxiamge 使用静态库 vs2010

    首先下载cxiamge,我使用的是cxiamge_702 下载地址:http://download.csdn.net/detail/xing_ping_1987/8085129 编译静态库 新建项目, ...

  3. CentOS和Ubuntu的区别

    CentOS(Community ENTerprise Operating System)是Linux发行版之一,它是来自于Red Hat Enterprise Linux依照开放源代码规定释出的源代 ...

  4. photoSlider-html5原生js移动开发轮播图-相册滑动插件

    简单的移动端图片滑动切换浏览插件 分别引用css文件和js文件 如: <link rel="stylesheet" type="text/css" hre ...

  5. eclipse Content Assist 无法使用,不能自动补全的解决办法

    今天用eclipse写JAVA代码,写着写着突然,eclipse 的自动补全功能失效了,没办法自动补全.折腾半天,终于解决了. 在window->Preferences->Java-> ...

  6. 使用div+iframe实现弹窗及弹出内容无法显示的解决

    使用div+iframe实现弹窗 除了使用实际的弹出窗口,还可以使用控制一个div的display属性来模拟一个弹出窗口的操作,这里使用在Div里放一个iFrame的方式,主要考虑到可以在需要的时候加 ...

  7. linux系统结构和系统命令初步

    以上是第五课和第14课笔记 linux 基本结构: 系统构成:kernel,Modules,Lib,(Shell,Tool)系统引导:BIOS -> Bootlooder -> Kerne ...

  8. mongodb3 权限认证问题总结

    mongodb3 权限认证问题总结 标签(空格分隔): mongodb 权限 数据库 认证 ubuntu用户安装最新版本mongodb 添加key sudo apt-key adv --keyserv ...

  9. UVA 10791 Minimum Sum LCM

    唯一分解定理 把n分解为 n=a1^p1*a2^p2*...的形式,易得每个ai^pi作为一个单独的整数最优. 坑: n==1     ans=2: n因子种数只有一个     ans++: 注意溢出 ...

  10. python中的函数存入list中的实例

    最近由于接触了python这个强大的东西,在写代码时考虑到代码的扩展性,就想到了将python的函数名存入list中.有点像习惯的c/c++中的函数指针的意思. 下面上代码: # coding=utf ...