Renderer API Abstraction

引言

上几节我们绘制了自己的三角形并正确导入了着色器,今天我们将讨论从未讨论过的内容:设计抽象渲染 API 调用。

我们的引擎想支持多种图形 API,实际上是在不同的平台(诸如 Windows、MacOS、Linux、Android、IOS、PS4、XBOX、Switch 等等)上进行抽象的。

本质上,我们可以使用多个渲染 API 通过编译实现,当我实际编译我的代码时需要选择一个渲染 API 例如 DirectX,然后根据我们的配置文件编译。我们有 OpenGL Shader 类,DirectX Shader 类,而只有其中之一将被正确编译,这个“一”取决于配置设置。


该方案最大的问题在于对开发人员不够友好。假设我已经实现了 OpenGL,转向 DirectX 开发需要我在它们之间进行切换,此过程需要不断重新编译我的代码。

这样做的好处也很明显:性能。我们知道该使用哪个 API,所有工作都是离线完成的,引擎不必在此过程中做任何抉择,也不必浪费时间来决定当前使用哪个 API,它在编译时已经提前完成了。放弃了灵活性,带给我们更高的性能。

渲染API抽象

Infinite/Render/ 目录下新建 Renderer.hRenderer.cpp

#pragma once

namespace Infinite {

enum class RendererAPI
{
None = 0,
OpenGL = 1
};

class Renderer
{
public:
inline static RendererAPI GetAPI() { return s_RendererAPI; }

private:
static RendererAPI s_RendererAPI;
};
}

头文件 Renderer.h 定义 API 类型和访问器。

#include "ifnpch.h"
#include "Renderer.h"

namespace Infinite {

RendererAPI Renderer::s_RendererAPI = RendererAPI::OpenGL;
}

Renderer.cpp 设置 API 为 OpenGL

缓冲大类设计

上一节为了尽快在屏幕上显示我们直接在 Application.cpp 中加入了许多 OpenGL 代码,所以今天我们需要封装这部分代码。

Infinite/Renderer/ 目录下新建 Buffer.hBuffer.cpp

#pragma once

namespace Infinite {

class VertexBuffer
{
public:
virtual ~VertexBuffer() {}

virtual void Bind() const = 0;
virtual void Unbind() const = 0;

static VertexBuffer* Create(float* vertices, uint32_t size);
};


class IndexBuffer
{
public:
virtual ~IndexBuffer() {}

virtual void Bind() const = 0;
virtual void Unbind() const = 0;

virtual uint32_t GetCount() const = 0;

static IndexBuffer* Create(uint32_t* indices, uint32_t size);
};


}

在头文件 Buffer.h 中我们声明了顶点缓冲区和索引缓冲区,内置绑定函数和创建方法。

#include "ifnpch.h"
#include "Buffer.h"

#include "Renderer.h"

#include "Platform/OpenGL/OpenGLBuffer.h"

namespace Infinite {

VertexBuffer* VertexBuffer::Create(float* vertices, uint32_t size)
{
switch (Renderer::GetAPI())
{
case RendererAPI::None:
IFN_ASSERT(false, "RendererAPI::None is currently not supported!");
return nullptr;
case RendererAPI::OpenGL:
return new OpenGLVertexBuffer(vertices, size);
}

IFN_ASSERT(false, "Unknown RendererAPI!");
return nullptr;
}

IndexBuffer* IndexBuffer::Create(uint32_t* indices, uint32_t size)
{
switch (Renderer::GetAPI())
{
case RendererAPI::None:
IFN_ASSERT(false, "RendererAPI::None is currently not supported!");
return nullptr;
case RendererAPI::OpenGL:
return new OpenGLIndexBuffer(indices, size);
}

IFN_ASSERT(false, "Unknown RendererAPI!");
return nullptr;
}

}

Buffer.cpp 使用 switch 语句筛选合适的图形 API,加入引擎的断言调试。

OpenGL缓冲实现

最后将之前在 Application.cpp 中内嵌的 OpenGL 语句解耦抽象,在 src/Platform/OpenGL 目录下新建 OpenGLBuffer.hOpenGLBuffer.cpp

#pragma once

#include "Infinite/Renderer/Buffer.h"

namespace Infinite {

class OpenGLVertexBuffer : public VertexBuffer
{
public:
OpenGLVertexBuffer(float* vertices, uint32_t size);
virtual ~OpenGLVertexBuffer();

virtual void Bind() const;
virtual void Unbind() const;

private:
uint32_t m_RendererID;
};


class OpenGLIndexBuffer : public IndexBuffer
{
public:
OpenGLIndexBuffer(uint32_t* indices, uint32_t count);
virtual ~OpenGLIndexBuffer();

virtual void Bind() const;
virtual void Unbind() const;

virtual uint32_t GetCount() const { return m_Count; }

private:
uint32_t m_RendererID;
uint32_t m_Count;
};

}

OpenGLBuffer.cpp 完成对顶点缓冲区和索引缓冲区类方法的实现:

#include "ifnpch.h"
#include "OpenGLBuffer.h"

#include <glad/glad.h>

namespace Infinite {

/////////////////////////////////////////////////////////////////////////////
// VertexBuffer /////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

OpenGLVertexBuffer::OpenGLVertexBuffer(float* vertices, uint32_t size)
{
glCreateBuffers(1, &m_RendererID);
glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW);
}

OpenGLVertexBuffer::~OpenGLVertexBuffer()
{
glDeleteBuffers(1, &m_RendererID);
}

void OpenGLVertexBuffer::Bind() const
{
glBindBuffer(GL_ARRAY_BUFFER, m_RendererID);
}

void OpenGLVertexBuffer::Unbind() const
{
glBindBuffer(GL_ARRAY_BUFFER, 0);
}

/////////////////////////////////////////////////////////////////////////////
// IndexBuffer /////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

OpenGLIndexBuffer::OpenGLIndexBuffer(uint32_t* indices, uint32_t count)
:m_Count(count)
{
glCreateBuffers(1, &m_RendererID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(uint32_t), indices, GL_STATIC_DRAW);
}

OpenGLIndexBuffer::~OpenGLIndexBuffer()
{
glDeleteBuffers(1, &m_RendererID);
}

void OpenGLIndexBuffer::Bind() const
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID);
}

void OpenGLIndexBuffer::Unbind() const
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

}

应用类调整

Application.h 加入替代的顶点缓冲和索引缓冲共享指针:

#include "./ImGui/ImGuiLayer.h"

#include "./Renderer/Shader.h"
+#include "./Renderer/Buffer.h"

namespace Infinite {
class INFINITE_API Application
{
public:
Application();
- virtual ~Application();
+ virtual ~Application() = default;

void Run();
void OnEvent(Event& e);
@@ -34,8 +35,10 @@ namespace Infinite {
bool m_Running = true;
LayerStack m_LayerStack;

- unsigned int m_VertexArray, m_VertexBuffer, m_IndexBuffer;
- unsigned int m_VertexArray;
std::unique_ptr<Shader> m_Shader;
+ std::unique_ptr<VertexBuffer> m_VertexBuffer;
+ std::unique_ptr<IndexBuffer> m_IndexBuffer;
private:
static Application* s_Instance;
};

......

Application.cpp 解耦之前的实现方式:

		glGenVertexArrays(1, &m_VertexArray);
glBindVertexArray(m_VertexArray);

- // Vertex Buffer
- glGenBuffers(1, &m_VertexBuffer);
- glBindBuffer(GL_ARRAY_BUFFER, m_VertexBuffer);


float vertices[3 * 3] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};

+ m_VertexBuffer.reset(VertexBuffer::Create(vertices, sizeof(vertices)));

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);

- glGenBuffers(1, &m_IndexBuffer);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexBuffer);
-
- unsigned int indices[3] = { 0, 1, 2 };
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+ uint32_t indices[3] = { 0, 1, 2 };
+ m_IndexBuffer.reset(IndexBuffer::Create(indices, sizeof(indices) / sizeof(uint32_t)));


std::string vertexSrc = R"(
+ #version 330 core
+
layout(location = 0) in vec3 a_Position;
+ out vec3 v_Position;
void main()
{
- gl_Position = vec4(a_Position, 1.0);
+ v_Position = a_Position;
+ gl_Position = vec4(a_Position, 1.0);
}
)";


std::string fragmentSrc = R"(
+ #version 330 core

layout(location = 0) out vec4 color;
+ in vec3 v_Position;
void main()
{
color = vec4(0.8, 0.2, 0.3, 1.0);
color = vec4(v_Position * 0.5 + 0.5, 1.0);
}
)";

@@ -81,8 +76,6 @@ namespace Infinite {

}

- Application::~Application() {}
-
void Application::PushLayer(Layer* layer)
{
m_LayerStack.PushLayer(layer);
@@ -116,7 +109,7 @@ namespace Infinite {

m_Shader->Bind();
glBindVertexArray(m_VertexArray);
- glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr);
+ glDrawElements(GL_TRIANGLES, m_IndexBuffer->GetCount(), GL_UNSIGNED_INT, nullptr);


for (Layer* layer : m_LayerStack)

调试