跨平台渲染框架尝试 - Texture管理

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

在资源管理概述中提到,资源就是一堆内存和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枚举类型中赋予相应的值,再来回顾一下

namespace pipeline
{
    enum class object_type
    {
        texture_1d,
        texture_2d,
        texture_3d,
        texture_cube,
        texture_1d_array,
        texture_2d_array,
        texture_cube_array,
        texture_rt,
        texture_dp,
    };
}

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

namespace pipeline
{
    enum class pixel_format
    {
        unknown,
        rgba_32,
        //.......
        bc_1,
        //.......
    };
}

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

namespace pipeline
{
    template <typename Impl>
    struct texture_traits;
    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;  };
    template <typename Impl>
    class texture : public resource<texture<Impl>>
    {public:
        using base_type = resource<texture<Policy>>;
        using this_type = texture;using subresource_constainer = std::vector<subresource>;
        using traits_type = texture_traits<Impl>;

    protected:

        texture(string const& name, pixel_format format, uint16_t width,
            uint16_t height, uint16_t depth, uint16_t array_size, bool has_mipmap) noexcept
            : base_type(name)
            , width_(width)
            , height_(height)
            , depth_(depth)
            , array_size_(size)
            , has_mipmap_(has_mipmap)
            , subresources_()
        {

        }

    public:

        template <typename = std::enable_if_t<traits_type::is_texture_1d()>>
        void resize(uint16_t widht) noexcept
        {
            width_ = widht;
        }

        template <typename = std::enable_if_t<traits_type::is_texture_2d()>>
        void resize(uint16_t widht, uint16_t height) noexcept
        {
            width_ = widht;
            height_ = height;
        }

        template <typename = std::enable_if_t<traits_type::is_texture_3d()>>
        void resize(uint16_t widht, uint16_t height, uint16_t depth) noexcept
        {
            width_ = widht;
            height_ = height;
            depth_ = depth
        }

        template <typename = std::enable_if_t<traits_type::is_texture_array()>>
        void set_array_size(uint16_t array_size) noexcept
        {
            array_size_ = array_size * traits_type::array_size_unit();
        }

        void set_pixel_format(pixel_format pixel_format)
        {
            traits_type::validate_format(pixel_format);
            pixel_format_ = pixel_format;
        }

        uint16_t width() const noexcept
        {
            return width_;
        }

        uint16_t height() const noexcept
        {
            return height_;
        }

        uint16_t depth() const noexcept
        {
            return depth_;
        }

        uint16_t array_size() const noexcept
        {
            return array_size_;
        }

        bool& mipmap() noexcept
        {
            return has_mipmap_;
        }

        bool mipmap() const noexcept
        {
            return has_mipmap_;
        }

        void construct()
        {
            detail::subresource_generator gen
            {
                pixel_format_, width_, height_, depth_,  array_size_ , has_mipmap_
            };

            data_ = std::move(gen.data());
            size_ = data_.size();
            subresources_ = std::move(gen.subresource());
        }

        decltype(auto) begin() noexcept
        {
            return subresources_.begin();
        }

        decltype(auto) end() noexcept
        {
            return subresources_.end();
        }

    private:
        pixel_format    pixel_format_;
        uint16_t        width_;
        uint16_t        height_;
        uint16_t        depth_;
        uint16_t        array_size_;
        bool            has_mipmap_;

        subresource_constainer    subresources_;
    };
}

这里之所以要用模板,是为了尽可能的让不写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的状态不会发生改变,并且辅助类对象可以完美地保证资源不会泄露。下面给出辅助类的设计。

今天先写到这里,明天补充完。

时间: 2024-12-13 18:14:19

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

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

1. Preface Constant buffer是我们在编写shader的时候,打交道最多的一种buffer resource了.constant表明了constant buffer中的数据,在一次draw call的执行过程中都是不变的:而在不同的draw call之间,我们可以修改其中的数据.它是我们把数据从CPU传递到GPU最常见的方法.constant buffer的概念和定义就不在这里赘述了,鄙文主要讨论如何优雅的管理constant buffer. 2. How to creat

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

buffer资源 下面来谈谈buffer的管理.buffer资源从广义上就是C语言的数组.如下图所示. 图 buffer的广义模型 在渲染管线中,无论是opengl还是dx或者其他的渲染api,都会提供下列的buffer类型.vertex buffer,index buffer,constant buffer,structured buffer,raw buffer与indirect arguments buffer.  这些buffer的不同就在于两个方面. 首先就是内存结构.有流式的buff

跨平台UI框架杂思——02

距离本系列最后一篇随笔<跨平台UI框架杂思--01>的发表已经过去了一年多.这一年多我都没怎么在外头写blog了(写东西都放在公司的 confluence page 里).这一年多我的"跨平台UI框架"实现了,并且用到了公司的产品中.我很欣慰地发现这一年多来,我都是按照最后一篇随笔的思路来开展的-- 硬件加速渲染 高可扩展性 灵活可替换的渲染框架 在Windows上面做透了 首先实现了 Direct2D 的渲染 由于这个框架是我在公司写的,所以目前暂且没能对外开源. 框架的

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

???原文地址 现在用 C# 来开发?跨平台应用已经有很成熟的方案,即共用非界面代码,而每个操作系统搭配特定的用户界面代码.这个方案的好处是可以直接使用操作系统原生的控件和第三方控件,还能够和操作系统深度集成. 这里的深度集成主要是指一些 Windows 专有的系统特性: Windows 托盘 Windows 跳转列表 Windows 系统主题 也包括一些移动平台的特性,例如 iOS 的原生滑动. ?由于操作系统上其他程序一般都使用原生控件,于是只有当你的程序采用同样技术时,它才能很好地保持一致

JobEngine 基于quartz.net 跨平台作业框架

github:https://github.com/zzhi/JobEngine 基于quartz.net 的跨平台作业框架 quartz.net(https://github.com/quartznet/quartznet/tree/features/netcore11) 也支持跨平台了 ,由于NuGet无法安装quartz-DotNetCore dll. 所以我直接把这个解决方案下载下来,删除一些无用的代码,在解决方案上直接创建项目JobServer, 通过添加引用的方式引用quartz-D

MUI框架-08-窗口管理-创建子页面

MUI框架-08-窗口管理-创建子页面 之前写过这一篇,不知道为什么被删了,我就大概写了,抱歉 创建子页面是为了,页面切换时,外面的页面不动,让 MUI 写出来的页面更接近原生 app 官方文档:http://dev.dcloud.net.cn/mui/window/#subpage 创建子页面 1.在HBuilder 新建移动app项目,选择 mui 3.新建html目录用来存放html文件,新建含 mui 的HTML文件 在 Hbuilder 中,新建HTML文件,选择"含 mui的HTML

微软跨平台ORM框架之EFCore

EFCore是微软推出的跨平台ORM框架,想较于EF6.X版本,更加轻量级.EFCore目前已经更新到2.x. 接下来用CodeFirst的方式来使用EFCore. 1.创建控制台程序 2.引入EFCore的Nuget包和Sqlserver的扩展(因为我这里用的Sqlserver数据库,若是别的数据库如Mysql引入相应的包即可). 3.创建继承成自DbContext上下文,并重载OnConfiguring方法来构建数据库连接字符串 public class CoreDbContext : Db

跨平台UI框架杂思——00

其实我写<我应该用什么界面方案>其实就是想要学习和研究 跨平台的,即时或半即时渲染的 UI 框架.这或许能跟 DirectUI技术 扯上关系——传统的 Windows 界面控件都是一个个的 HWND,然而 DirectUI 的思想就是不用 Win32 原生的控件,而是自己渲染(GDI或其他)上去,并且管理他们的各种输入消息(鼠标.键盘). 当然了,关于渲染部分,可能主要就使用 Direct2D 和 OpenGL 以及 移动端的 Open GL ES.但是关于控件的设计比较需要探讨.我一开始会自

基于OpenGL编写一个简易的2D渲染框架-04 绘制图片

阅读文章前需要了解的知识,纹理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/ 过程简述:利用 FreeImage 库加载图像数据,再创建 OpenGL 纹理,通过 Canvas2D 画布绘制,最后又 Renderer 渲染器渲染 本来想用 soil 库加载图像数据的,虽然方便,但是加载有些格式的图像文件时会出现一些问题.最后,改用 FreeImage 库来加载图像了. 添加 FreeImage 库到工