【Cherno的OpenGL视频】How to make your uniform faster in OpenGL

本文介绍如何通过在C++代码中缓存OpenGL中的uniform位置,避免频繁查询GPU,从而提高设置uniform的效率。作者展示了如何在Shader类中实现uniform位置的缓存,以及在实际项目中的应用效果。
摘要由CSDN通过智能技术生成

1、

Today we’re gonna be talking about uniforms in shaders, specifically how we can caching uniforms and in general just how we can deal with uniforms in a bit of a smarter way, bc we all know how to set uniforms, instead of retrieving uniforms from OpenGL from GPU every single time when we need to set a uniform, there are many different things that we can actually do to kind of retain that location, and take the advantage of the fact that once uniforms have kind of been retrieved, once we’ve compiled our shader, we’ve got that actual kind of shader object on our GPU, and of course the shader in the GPU is aware of all of the uniforms that are kind of contained within and all of the valid uniforms, once we’ve got all that data there is no reason for us to continually query the GPU being like"hey please tell me where my uniform is", bc we should already know that stuff, and we know as we should know pretty much any OpenGL call that we actually run is gonna be slower than a lot of the c++ style stuff that we can actually do, bc a lot of it actually hits the GPU, and thus kind of requires a little bit more processing power and takes more time, then if we did sth a lot more simple. Today specifically we’re gonna talk about caching that uniforms so that we can just kind of retrieve the location at any time we want, basically building a very simple system that just holds on to a uniform location for a given name that we have inside our C++ side shader object.

But in general, what typically happens is once we actually compile our shader, pretty much every kind of Game Engine or every kind of rendering engine is actually going to physically read that shader source code and then determine what to do with that, so in other words, if we just load a shader, and we compile it and we use OpenGL to do all that stuff, another very useful thing for us to do is actually read the shader source code inside C++ and then extract data from it, so from that we can actually find out all of the attributes all of the uniforms all of the functions, everything that is in that shader we can write a program essentially that just kind of reads that text, that shader source code, and then from there it can intelligently almost convert it into actual data that we can leverage and use at runtime, so in a setup like that where we’ve got a proper system and a proper engine like that we would typically just read our shader source code, and then find all of the uniforms in the actual shader text, and then retrieve all the location or validate or all the uniforms basically when we’re compiling our shader, so in that case anytime when our renderer is actually setting uniforms in our shader, all of that stuff has already been done, we already have all of the location, we can easily check to see if a uniform even exists or not without touching OpenGL, why? Because we’ve read the shader source code, so that is something that would happen in a more complete system, what Cherno’s showing us today is a quick and dirty little kind of hack, just to speed up our program a little bit if we’re just building something quick in OpenGL, we don’t want to have this massive like rendering engine or anything like that set up, and we just wanna speed up our code quickly without much effort really, but just kind of one simple trick to faster uniform setting, the point is: it’s super simple and it’s a way we can just easily speed up our uniform setting by just not retrieving the location every time from OpenGL but by caching it.

2、Take a look at our code now.

We have this very very simple shader class here, what it has really, what it represents is just basically a shader which contains a rendering ID, so it’s like a kind of compiled shader object, this is like the CPU representation of an actual compiled and create a shader object on our GPU, a bunch of functions for like void Bind() const; and void Unbind() const; and also setting all of these uniforms:

	// Set uniforms.
	void SetUniform1i(const std::string& name, int BoundTextureSlot);// for u_Texture.
	void SetUniform1f(const std::string& name, float value);
	void SetUniform2f(const std::string& name, const glm::vec2& value);
	void SetUniform3f(const std::string& name, const glm::vec3& value);
	void SetUniform4f(const std::string& name, const glm::vec4& value);

	void SetUniformMat3(const std::string& name, const glm::mat3& matrix);
	void SetUniformMat4(const std::string& name, const glm::mat4& matrix);// for u_MVP.

This is kind of the meat of everything.

3、代码实现

如图私有成员增加m_UniformLocationCache以及私有方法GetUniformLocation
在这里插入图片描述
Shader.h代码

#pragma once
#include <string>
#include <unordered_map>
#include <GL/glew.h>
#include "glm/glm.hpp"

struct ShaderProgramSource
{
	std::string VertexSource;
	std::string FragmentSource;
};

class Shader
{
private:
	std::string m_Filepath;
	unsigned int m_RendererID;

	// Caching for uniforms.
	mutable std::unordered_map<std::string, GLint> m_UniformLocationCache;
public:
	Shader(const std::string& filepath);
	~Shader();

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

	// Set uniforms.
	void SetUniform1i(const std::string& name, int BoundTextureSlot);// for u_Texture.
	void SetUniform1f(const std::string& name, float value);
	void SetUniform2f(const std::string& name, const glm::vec2& value);
	void SetUniform3f(const std::string& name, const glm::vec3& value);
	void SetUniform4f(const std::string& name, const glm::vec4& value);

	void SetUniformMat3(const std::string& name, const glm::mat3& matrix);
	void SetUniformMat4(const std::string& name, const glm::mat4& matrix);// for u_MVP.
private:
	ShaderProgramSource ParseShader(const std::string& filepath);
	unsigned int CompileShader(unsigned int type, const std::string& source);
	unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader);

	GLint GetUniformLocation(const std::string& name) const;
public:
	unsigned int GetID() const { return m_RendererID; }
};

Shader.cpp代码

#include "Shader.h"
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include "Renderer.h"

Shader::Shader(const std::string& filepath)
	:m_Filepath(filepath), m_RendererID(0)
{
	ShaderProgramSource source = ParseShader(filepath);
	// Use our shader.
	m_RendererID = CreateShader(source.VertexSource, source.FragmentSource);
}

Shader::~Shader()
{
	GLCall(glDeleteProgram(m_RendererID));
}

void Shader::Bind() const
{
	GLCall(glUseProgram(m_RendererID));
}

void Shader::Unbind() const
{
	GLCall(glUseProgram(0));
}

void Shader::SetUniform1i(const std::string& name, int BoundTextureSlot)
{
	GLint location = GetUniformLocation(name);
	GLCall(glUniform1i(location, BoundTextureSlot));
}

void Shader::SetUniform1f(const std::string& name, float value)
{
	GLint location = GetUniformLocation(name);
	GLCall(glUniform1f(location, value));
}

void Shader::SetUniform2f(const std::string& name, const glm::vec2& value)
{
	GLint location = GetUniformLocation(name);
	GLCall(glUniform2f(location, value.x, value.y));
}

void Shader::SetUniform3f(const std::string& name, const glm::vec3& value)
{
	GLint location = GetUniformLocation(name);
	if (location != -1)
	{
		GLCall(glUniform3f(location, value.x, value.y, value.z));
	}
}

void Shader::SetUniform4f(const std::string& name, const glm::vec4& value)
{
	GLint location = GetUniformLocation(name);
	GLCall(glUniform4f(location, value.x, value.y, value.z, value.w));
}

void Shader::SetUniformMat3(const std::string& name, const glm::mat3& matrix)
{
	GLint location = GetUniformLocation(name);
	GLCall(glUniformMatrix3fv(location, 1, GL_FALSE, &matrix[0][0]));
}

void Shader::SetUniformMat4(const std::string& name, const glm::mat4& matrix)
{
	GLint location = GetUniformLocation(name);
	GLCall(glUniformMatrix4fv(location, 1, GL_FALSE, &matrix[0][0]));
}

ShaderProgramSource Shader::ParseShader(const std::string& filepath)
{
	enum class ShaderType
	{
		NONE = -1, VERTEX = 0, FRAGMENT = 1
	};

	std::ifstream stream(filepath);
	std::string line;
	std::stringstream ss[2];
	ShaderType type = ShaderType::NONE;

	// go through the file line-by-line.
	while (getline(stream, line))
	{
		if (line.find("#shader") != std::string::npos)
		{
			if (line.find("vertex") != std::string::npos)
			{
				// set mode to vertex.
				type = ShaderType::VERTEX;
			}
			else if (line.find("fragment") != std::string::npos)
			{
				// set mode to fragment.
				type = ShaderType::FRAGMENT;
			}
		}
		else
		{
			ss[(int)type] << line << '\n';
		}
	}
	return { ss[0].str(), ss[1].str() };
}

unsigned int Shader::CompileShader(unsigned int type, const std::string& source)
{
	unsigned int id = glCreateShader(type);
	const char* src = source.c_str();
	glShaderSource(id, 1, &src, nullptr);
	glCompileShader(id);

	// error handling.
	int result;
	glGetShaderiv(id, GL_COMPILE_STATUS, &result);
	if (result == GL_FALSE)
	{
		int length;
		glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
		char* message = (char*)alloca(length * sizeof(char));
		glGetShaderInfoLog(id, length, &length, message);
		std::cout << "CompileShader() Failed, " << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader!" << std::endl;
		std::cout << message << std::endl;
		glDeleteShader(id);
		return 0;
	}

	return id;
}

// Creat a shader.
unsigned int Shader::CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
	unsigned int program = glCreateProgram();
	// create our true shader objects.
	unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
	unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);
	// link vs and fs.
	glAttachShader(program, vs);
	glAttachShader(program, fs);
	// link the program.
	glLinkProgram(program);
	glValidateProgram(program);
	// delete it after linked.
	glDeleteShader(vs);
	glDeleteShader(fs);

	return program;
}

// The meat.
GLint Shader::GetUniformLocation(const std::string& name) const
{
	// Check the cache and see if we've got that value in there.
	if (m_UniformLocationCache.find(name) != m_UniformLocationCache.end())
	{
		return m_UniformLocationCache[name];
	}

	// 在我们的shader里,每个uniform都会被分配一个ID。
	// If it's not in the cache, then we need to retrieve it.
	GLCall(GLint location = glGetUniformLocation(m_RendererID, name.c_str()));//在shader中检索name的位置。
	if (location == -1)// 
	{
		std::cout << "Warning: uniform " << name << " doesn't exist!" << std::endl;
	}
	m_UniformLocationCache[name] = location; 
	return location;
}

4、运行效果

When we ask for this u_MVP matrix location, them_UniformLocationCache obviously contains it which means that now it just simply returns this uniform location as we can see, and that’s a lot faster than actually querying OpenGL and being like “hey what’s this uniform location?”.
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值