图形API学习工程(5):图形管线&顶点缓冲

工程GIT地址:https://gitee.com/yaksue/yaksue-graphics

目标

目标是可以让各个图形API都能渲染出一个三角形。为此需要:

  • 配置图形管线
  • 创建顶点缓冲并使用

图形管线

概念

关于“图形管线”这个概念,《DX12龙书》《Vulkan官方教程》里的描述都很类似:
在这里插入图片描述
不过还是有差别,比如DirectX中有 “Hull Shader”“Domain Shader” 的概念,而Vulkan中没有。

代码定义

D3D12中的图形管线用一个ID3D12PipelineState来表示,创建它需要的信息是一个 D3D12_GRAPHICS_PIPELINE_STATE_DESC

struct D3D12_GRAPHICS_PIPELINE_STATE_DESC
{
	ID3D12RootSignature *pRootSignature;
	D3D12_SHADER_BYTECODE VS;
	D3D12_SHADER_BYTECODE PS;
	D3D12_SHADER_BYTECODE DS;
	D3D12_SHADER_BYTECODE HS;
	D3D12_SHADER_BYTECODE GS;
	D3D12_STREAM_OUTPUT_DESC StreamOutput;
	D3D12_BLEND_DESC BlendState;
	UINT SampleMask;
	D3D12_RASTERIZER_DESC RasterizerState;
	D3D12_DEPTH_STENCIL_DESC DepthStencilState;
	D3D12_INPUT_LAYOUT_DESC InputLayout;
	D3D12_INDEX_BUFFER_STRIP_CUT_VALUE IBStripCutValue;
	D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType;
	UINT NumRenderTargets;
	DXGI_FORMAT RTVFormats[ 8 ];
	DXGI_FORMAT DSVFormat;
	DXGI_SAMPLE_DESC SampleDesc;
	UINT NodeMask;
	D3D12_CACHED_PIPELINE_STATE CachedPSO;
	D3D12_PIPELINE_STATE_FLAGS Flags;
}

Vulkan中的图形管线用一个VkPipeline来表示,创建它需要一个 VkGraphicsPipelineCreateInfo

struct VkGraphicsPipelineCreateInfo 
{
    VkStructureType                                  sType;
    const void*                                      pNext;
    VkPipelineCreateFlags                            flags;
    uint32_t                                         stageCount;
    const VkPipelineShaderStageCreateInfo*           pStages;
    const VkPipelineVertexInputStateCreateInfo*      pVertexInputState;
    const VkPipelineInputAssemblyStateCreateInfo*    pInputAssemblyState;
    const VkPipelineTessellationStateCreateInfo*     pTessellationState;
    const VkPipelineViewportStateCreateInfo*         pViewportState;
    const VkPipelineRasterizationStateCreateInfo*    pRasterizationState;
    const VkPipelineMultisampleStateCreateInfo*      pMultisampleState;
    const VkPipelineDepthStencilStateCreateInfo*     pDepthStencilState;
    const VkPipelineColorBlendStateCreateInfo*       pColorBlendState;
    const VkPipelineDynamicStateCreateInfo*          pDynamicState;
    VkPipelineLayout                                 layout;
    VkRenderPass                                     renderPass;
    uint32_t                                         subpass;
    VkPipeline                                       basePipelineHandle;
    int32_t                                          basePipelineIndex;
}

与旧式图形API的区别

而对于【OpenGL】和【D3D11】,所谓的“图形管线”并没有被封装为一个对象,但“图形管线”这一概念当然还是存在,只不过其中的信息是被单独设置的。


引用《Vulkan官方教程-图像管线介绍》中对此的讨论:
If you’ve used older APIs like OpenGL and Direct3D before, then you’ll be used to being able to change any pipeline settings at will with calls like glBlendFunc and OMSetBlendState. The graphics pipeline in Vulkan is almost completely immutable, so you must recreate the pipeline from scratch if you want to change shaders, bind different framebuffers or change the blend function. The disadvantage is that you’ll have to create a number of pipelines that represent all of the different combinations of states you want to use in your rendering operations. However, because all of the operations you’ll be doing in the pipeline are known in advance, the driver can optimize for it much better.

如果你之前使用过老的图形API像【OpenGL】和【D3D11】,你要想改变管线的设置,需要调用glBlendFuncOMSetBlendState这样的函数。但在Vulkan中,图形管线是一个几乎不可变的整体,所以你要想改变某个设置(例如改变shader,绑定不同的Framebuffer,改变blend函数)就需要从零开始创建一个新的管线。这样的缺点是你不得不创建大量的管线来代表不同的组合。然而,正是由于你提前创建了管线,所以driver就可以对其进行优化。


引用《DX12龙书-6.9 PIPELINE STATE OBJECT》中对此的讨论:
In the Direct3D 11 state model, these render states pieces were set separately. However, the states are related; if one piece of state gets changed, it may additionally require the driver to reprogram the hardware for another piece of dependent state. As many states are changed to configure the pipeline, the state of the hardware could get reprogrammed redundantly. To avoid this redundancy, the drivers typically deferred programming the hardware state until a draw call is issued when the entire pipeline state would be known. But this deferral requires additional bookkeeping work by the driver at runtime; it needs to track which states have changed, and then generate the code to program the hardware state at runtime. In the new Direct3D 12 model, the driver can generate all the code needed to program the pipeline state at initialization time because we specify the majority of pipeline state as an aggregate.

在D3D11里,管线中的这些渲染状态是被单独设置的。然而,这些渲染状态实际上是有关联的,如果其中一部分改变了,那么可能有其他一个依赖它的部分还需要driver去重新对硬件进行编码。由于很多状态会为了配置管线而不断改变,那么就可能会导致硬件重新编码地很“冗余”。为了避免这种“冗余”,driver一般会推迟编码硬件直到一个DrawCall被调用。但是这种推迟要求driver在运行时去记录:它需要去追踪哪些状态被改变了,然后在运行时生成代码来编码硬件。而在新的D3D12里,driver在程序开始就可以生成所有需要的代码了,因为我们在开始就设定了管线的大部分状态。

此外,书中还指明了因此所需要的采取的行为:
Because PSO validation and creation can be time consuming, PSOs should be generated at initialization time. One exception to this might be to create a PSO at runtime on demand the first time it is referenced; then store it in a collection such as a hash table so it can quickly be fetched for future use.

由于图形管线对象(PSO)的验证和创建是需要消耗时间的,所以PSO应该在初始化时期就创建好。除非只能在运行时创建,这时应该在其第一次创建时将其存到一个哈希表中,随后就可以快速找到它。

另外,并非所有的状态都在PSO中:
Not all rendering states are encapsulated in a PSO. Some states like the viewport and scissor rectangles are specified independently to the PSO. Such state can efficiently be set independently to the other pipeline state, so no advantage was gained by including them in the PSO.

一些状态例如“视窗”和“裁剪矩形”是独立于PSO中的,这些状态可以独立高效地进行设置,所以将他们包含在PSO中就没有好处了。

当前的抽象

顶点缓冲

顶点缓冲类定义:

//抽象顶点缓存
class AbstractVertexBuffer
{
public:
	int VertexCount;
};

各个图形API应该继承这个类,然后附加上自己图形API所专有的顶点缓冲对象。其中“VertexCount”是必须要记录的,因为DrawCall需要。

当前顶点的数据类型是:

//顶点数据,指定每一个顶点包含哪些数据
struct RawVertexData
{
	float Pos[3];	//位置
	float Color[4];	//颜色
};

顶点缓冲是工程里四个图形API所共有的概念,所以被定义在GraphicsInterface中:

//创建顶点缓冲
virtual AbstractVertexBuffer* CreateVertexBuffer(std::vector<RawVertexData> VertexData) = 0;
//设置顶点缓冲
virtual void SetVertexBuffer(AbstractVertexBuffer* Buffer) = 0;
图像管线

图形管线定义,当前只是一个空的(子类应该会加上自己各自的成员):

//抽象图形管线
class AbstractGraphicsPipeline
{
};

当前创建图形管线需要的信息是(最终应该有很多信息,当前只有着色器文件信息):

//图形管线,理论上需要描述出一个管线的状态,当前做了简化
struct GraphicsPipelineInfo
{
	std::string VertexShaderFile;	//顶点着色器的文件名
	std::string PixelShaderFile;	//像素着色器的文件名
};

GraphicsInterface接口:

//创建图形管线
virtual AbstractGraphicsPipeline* CreateGraphicsPipeline(GraphicsPipelineInfo Info) = 0;
//设置图形管线
virtual void SetGraphicsPipeline(AbstractGraphicsPipeline* Pipeline) = 0;
shader文件

封装情况理想时,shader文件应该只需要一种,但是现在两种:hlsl(给D3D11和D3D12用)和glsl(给OpenGL和Vulkan用)。就算算法一样,他们在语法上也有差别,所以当前只好写两份算法一样的。

理想情况下,可以在一个更高层级指定算法,然后自动转变为hlslglsl格式。不过这个难度有待评估。

渲染器

目前的渲染器,会在开始生成一个测试的顶点数据(三角形),指定着色器并用其创建图形管线

//作为测试的顶点缓冲数据:
std::vector<RawVertexData> vertices = {
	{{0.0f, 0.5f, 0.5f},{1.0f,1.0f,0.0f,1.0f}},
	{{0.5f, -0.5f, 0.5f},{0.0f,1.0f,1.0f,1.0f}},
	{{-0.5f, -0.5f, 0.5f},{1.0f,0.0f,1.0f,1.0f}},
};
//创建顶点缓冲
MyTestVertexBuffer = graphicsAPI->CreateVertexBuffer(vertices);


//作为测试的图形管线数据:
GraphicsPipelineInfo Info;
Info.VertexShaderFile = "TestShader_vs";
Info.PixelShaderFile = "TestShader_ps";
//创建图形管线
MyTestGraphicsPipeline = graphicsAPI->CreateGraphicsPipeline(Info);

然后在渲染时使用顶点缓冲和图形管线。

对于StandardRenderer

void StandardRenderer::Render()
{
	graphicsAPI_standard->Clear(0.4f, 0.5f, 0.6f, 1.0f);

	//设置图形管线
	graphicsAPI->SetGraphicsPipeline(MyTestGraphicsPipeline);
	//设置顶点缓冲
	graphicsAPI->SetVertexBuffer(MyTestVertexBuffer);

	//绘制
	graphicsAPI_standard->Draw(MyTestVertexBuffer->VertexCount, 0);

	graphicsAPI->Present();
}

对于AdvancedRenderer

void AdvancedRenderer::Render()
{
	for (int CommandListIndex = 0; CommandListIndex < graphicsAPI_Advanced->QueryCommandListCount(); CommandListIndex++)
	{
		graphicsAPI_Advanced->CurrentCommandListIndex = CommandListIndex;

		graphicsAPI_Advanced->BeginRecordCommandList();//开始录制命令
		{
			//开始RenderPass
			float ClearColor[4] = { 0.6f, 0.1f, 0.1f, 1.0f };
			graphicsAPI_Advanced->BeginRenderPass(ClearColor);

			//设置图形管线
			graphicsAPI->SetGraphicsPipeline(MyTestGraphicsPipeline);
			//设置顶点缓冲
			graphicsAPI->SetVertexBuffer(MyTestVertexBuffer);

			//绘制
			graphicsAPI_Advanced->CmdDraw(MyTestVertexBuffer->VertexCount, 0);

			//结束Renderpass
			graphicsAPI_Advanced->EndRenderPass();
		}
		graphicsAPI_Advanced->EndRecordCommandList();//结束录制命令
	}

	//执行命令
	graphicsAPI_Advanced->ExecuteCommandLists();

	graphicsAPI->Present();
}

当前的实现

各个图形API都继承了AbstractVertexBufferAbstractGraphicsPipeline
当前的实现如下:

//OpenGL的顶点缓存
class OpenGL_VertexBuffer : public AbstractVertexBuffer
{
public:
    //OpenGL的顶点数组对象
    GLuint  VertexArrayObject;
};

//OpenGL的图形管线数据
class OpenGL_GraphicsPipeline : public AbstractGraphicsPipeline
{
public:
    //shader的program
    GLuint Program;
};
//D3D11的顶点缓存
class D3D11_VertexBuffer : public AbstractVertexBuffer
{
public:
    //D3D11的缓冲数据
    ID3D11Buffer* VertexBuffer;
};

//D3D11的图形管线数据
class D3D11_GraphicsPipeline : public AbstractGraphicsPipeline
{
public:
    ID3D11VertexShader* VertexShader;	//顶点着色器
    ID3D11PixelShader* PixelShader;		//像素着色器
    ID3D11InputLayout* VertexLayout;	//顶点输入布局
};
//D3D12的顶点缓存
class D3D12_VertexBuffer : public AbstractVertexBuffer
{
public:
    //顶点缓冲数据
    ComPtr<ID3D12Resource> VertexBuffer;
    //D3D12的顶点缓冲View
    D3D12_VERTEX_BUFFER_VIEW VertexBufferView;
};

//D3D12的图形管线数据
class D3D12_GraphicsPipeline : public AbstractGraphicsPipeline
{
public:
    //RootSignature???
    ComPtr<ID3D12RootSignature> RootSignature;
    //D3D12的图形管线
    ComPtr<ID3D12PipelineState> PipelineState;
    //视窗
    CD3DX12_VIEWPORT Viewport;
    //裁剪矩形
    CD3DX12_RECT ScissorRect;
};
//Vulkan的顶点缓存
class Vulkan_VertexBuffer : public AbstractVertexBuffer
{
public:
	VkBuffer VertexBuffer;
	VkDeviceMemory VertexBufferMemory;
};

//Vulkan的图形管线数据
class Vulkan_GraphicsPipeline : public AbstractGraphicsPipeline
{
public:
	VkPipeline GraphicsPipeline;
};

这些定义其实体现了对于各个图形API,在 创建/设置 一个 顶点缓冲/图形管线 时,所关注的对象是什么。例如对于D3D12和Vulkan的图形管线,最主要的就是ID3D12PipelineStateVkPipeline了,而对于OpenGL和D3D11,就是一系列对象的集合。

关于具体 创建/设置 函数的内部实现,可见工程代码。

效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(唯有Vulkan的三角形反了,看来屏幕坐标和其他的不一致吗?待查。。)

之后需要考虑的问题

当前的信息其实比较简化,在之后应该会进行更多的封装和增加内容。

  • 当前的“图形管线”的GraphicsPipelineInfo其实缺失很多内容,只有着色器相关的信息,剩下都是各个图形API默认的,之后会补充。
  • OpenGL和D3D11的AbstractGraphicsPipeline其实缺少很多内容,之后会补充。
  • 顶点信息需要更好的封装。现在的顶点数据中信息较少(只有位置和颜色,重要的uv还没有)。其次,如果增加顶点数据中的信息,还需要对顶点布局依次修改,此处需要封装。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值