[Análise detalhada do mecanismo de jogo de sobrecarga] Encapsulamento UBO e SSBO

1. UBO do OpenGL

  No OpenGL Shader, se a lógica for mais complexa, serão utilizadas variáveis ​​mais uniformes. Normalmente, vários shaders usam a mesma variável uniforme. Como a localização da variável uniforme é gerada quando o shader é vinculado, o índice obtido na aplicação será alterado. Uniform Buffer Object (UBO) é um método que otimiza o acesso uniforme a variáveis ​​​​e permite que diferentes shaders compartilhem diretamente dados não uniformes.  

No mecanismo de sobrecarga, muitos Shaders contêm os seguintes fragmentos, onde uma variável UBO é definida. Ele reúne a matriz MVP na variável UBO.

layout (std140) uniform EngineUBO
{
    mat4    ubo_Model;
    mat4    ubo_View;
    mat4    ubo_Projection;
    vec3    ubo_ViewPos;
    float   ubo_Time;
};

std140 é o qualificador de layout de memória, além de std430, vinculativo, empacotado e outros qualificadores.

2. Encapsulamento de sobrecarga do UBO

   O UBO no mecanismo Overload é encapsulado nos arquivos UniformBuffer.h, UniformBuffer.inl e UniformBuffer.cpp e suas operações são empacotadas em uma classe UniformBuffer. Ao usar, chame Bind primeiro, depois UnBind e defina o valor usando SetSubData.

namespace OvRendering::Buffers
{
	/**
	* OpenGL UBO的封装
	*/
	class UniformBuffer
	{
	public:
		/**
		* Create a UniformBuffer
		* @param p_size (Specify the size in bytes of the UBO data)
		* @param p_bindingPoint (Specify the binding point on which the uniform buffer should be binded)
		* @parma p_offset (The offset of the UBO, sizeof previouses UBO if the binding point is != 0)
		* @param p_accessSpecifier
		*/
		UniformBuffer(size_t p_size, uint32_t p_bindingPoint = 0, uint32_t p_offset = 0, EAccessSpecifier p_accessSpecifier = EAccessSpecifier::DYNAMIC_DRAW);

		/**
		* Destructor of the UniformBuffer
		*/
		~UniformBuffer();

		/**
		* Bind the UBO
		*/
		void Bind();

		/**
		* Unbind the UBO
		*/
		void Unbind();

		/**
		* Set the data in the UBO located at p_offset to p_data
		* @param p_data
		* @param p_offset
		*/
		template<typename T>
		void SetSubData(const T& p_data, size_t p_offset);

		/**
		* Set the data in the UBO located at p_offset to p_data
		* @param p_data
		* @param p_offsetInOut (Will keep track of the current stride of the data layout)
		*/
		template<typename T>
		void SetSubData(const T& p_data, std::reference_wrapper<size_t> p_offsetInOut);

		/**
		* Return the ID of the UBO
		*/
		uint32_t GetID() const;

		/**
		* Bind a block identified by the given ID to given shader
		* @param p_shader
		* @param p_uniformBlockLocation
		* @param p_bindingPoint
		*/
		static void BindBlockToShader(OvRendering::Resources::Shader& p_shader, uint32_t p_uniformBlockLocation, uint32_t p_bindingPoint = 0);

		/**
		* Bind a block identified by the given name to the given shader
		* @param p_shader
		* @param p_name
		* @param p_bindingPoint
		*/
		static void BindBlockToShader(OvRendering::Resources::Shader& p_shader, const std::string& p_name, uint32_t p_bindingPoint = 0);

		/**
		* Return the location of the block (ID)
		* @param p_shader
		* @param p_name
		*/
		static uint32_t GetBlockLocation(OvRendering::Resources::Shader& p_shader, const std::string& p_name);

	private:
		uint32_t m_bufferID;
	};
}

#include "OvRendering/Buffers/UniformBuffer.inl"

Sua implementação específica está em UniformBuffer.cpp. Vejamos primeiro o código do construtor:

OvRendering::Buffers::UniformBuffer::UniformBuffer(size_t p_size, uint32_t p_bindingPoint, uint32_t p_offset, EAccessSpecifier p_accessSpecifier)
{
	// 生成buffer
	glGenBuffers(1, &m_bufferID);
	// 绑定UBO
	glBindBuffer(GL_UNIFORM_BUFFER, m_bufferID);
	// 分配内存
	glBufferData(GL_UNIFORM_BUFFER, p_size, NULL, static_cast<GLint>(p_accessSpecifier));
	glBindBuffer(GL_UNIFORM_BUFFER, 0);
	// 将缓存对象m_bufferID绑定到索引为p_bindingPoint的UBO上
	glBindBufferRange(GL_UNIFORM_BUFFER, p_bindingPoint, m_bufferID, p_offset, p_size);
}

O buffer UBO é criado diretamente no construtor e vinculado ao UBO cujo índice é p_bindingPoint. A função OpenGL glBindBufferRange é usada aqui. Se você não precisar especificar o valor de deslocamento e tamanho, poderá usar a função glBindBufferBase.

Bind() e UnBind() em UniformBuffer.cpp são muito simples e não serão analisados ​​novamente. Olhando para baixo, há uma função estática BindBlockToShader. Esta função vincula explicitamente um bloco uniforme ao índice p_bindingPoint para que o mesmo cache possa ser vinculado. A função glUniformBlockBinding é usada aqui. Esta função exibe principalmente o índice do BUO especificado. Ela pode garantir que o índice do UBO seja o mesmo entre vários programas Shader diferentes, mas precisa ser chamado antes de chamar glLinkProgram.

void OvRendering::Buffers::UniformBuffer::BindBlockToShader(OvRendering::Resources::Shader& p_shader, uint32_t p_uniformBlockLocation, uint32_t p_bindingPoint)
{
	glUniformBlockBinding(p_shader.id, p_uniformBlockLocation, p_bindingPoint);
}

void OvRendering::Buffers::UniformBuffer::BindBlockToShader(OvRendering::Resources::Shader& p_shader, const std::string& p_name, uint32_t p_bindingPoint)
{
	glUniformBlockBinding(p_shader.id, GetBlockLocation(p_shader, p_name), p_bindingPoint);
}

// 获取UBO的索引位置
uint32_t OvRendering::Buffers::UniformBuffer::GetBlockLocation(OvRendering::Resources::Shader& p_shader, const std::string& p_name)
{
	return glGetUniformBlockIndex(p_shader.id, p_name.c_str());
}

Mas no mecanismo de sobrecarga, esse método é chamado após a chamada de glProgram, e o valor do índice é obtido usando GetBlockLocation, que também é o valor de índice padrão do UBO no Shader, portanto, esse método deve ser excluído. Comentei que não encontrei problemas ao usar esse método.

Finalmente, vamos dar uma olhada em como definir o valor para UBO. A implementação está no arquivo UniformBuffer.inl, usando principalmente a função glBufferSubData e especificando seu valor de deslocamento e tamanho dos dados.

	template<typename T>
	inline void UniformBuffer::SetSubData(const T& p_data, size_t p_offsetInOut)
	{
		Bind();
		glBufferSubData(GL_UNIFORM_BUFFER, p_offsetInOut, sizeof(T), std::addressof(p_data));
		Unbind();
	}

	template<typename T>
	inline void UniformBuffer::SetSubData(const T& p_data, std::reference_wrapper<size_t> p_offsetInOut)
	{
		Bind();
		size_t dataSize = sizeof(T);
		glBufferSubData(GL_UNIFORM_BUFFER, p_offsetInOut.get(), dataSize, std::addressof(p_data));
		p_offsetInOut.get() += dataSize;
		Unbind();
	}

3. SSBO do OpenGL

Shader Storage Buffer Object (SSBO), um objeto de cache de armazenamento de shader, se comporta como UBO, mas é mais poderoso. Primeiro, um shader pode gravar em um bloco de buffer, modificar seu conteúdo e renderizá-lo para outros shaders ou para o próprio aplicativo. Em segundo lugar, o tamanho pode ser medido antes da renderização, e não durante a compilação e vinculação. No Overload, as informações de luz são armazenadas usando SSBO, veja o seguinte clipe Shader:

layout(std430, binding = 0) buffer LightSSBO
{
    mat4 ssbo_Lights[];
};

Você pode usar length() no shader para obter o comprimento de ssbo_Lights.

O método de configuração do SSBO é semelhante à configuração do UBO, mas glBindBuffer(), glBindBufferRange() e glBindBufferBase() precisam usar GL_SHADER_STORAGE_BUFFER como parâmetro de destino.

4. Encapsulamento de SSBO por sobrecarga

A sobrecarga encapsula a operação do SSBO na classe ShaderStorageBuffer. O código específico não será analisado. É semelhante ao UBO.

Acho que você gosta

Origin blog.csdn.net/loveoobaby/article/details/133564169
Recomendado
Clasificación