前言
这次要做的工作是,将shader缓存起来,我们设计一个ShaderLibrary缓存Shader,方便在整个引擎范围内对shader进行管理,底层我们用unorder_map<string, int>容器实现存储。
shader加载&缓存
shader中增加GetName方法,用name唯一标识shader,避免重复。Shader::Create方法中,也增加name参数,同时也要修改OpenGLShader中的Create实现。
Sandbox/Hazel/src/Hazel/Renderer/Shader.h
c++
class Shader
{
public:
virtual ~Shader() = default;
virtual void Bind() const = 0;
virtual void Unbind() const = 0;
virtual const std::string& GetName() const = 0;
static Ref<Shader> Create(const std::string& filepath);
static Ref<Shader> Create(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc);
};
增加ShaderLibrary类,声明Add、Load、Get、Exists四种接口,对shader进行操作。
c++
class ShaderLibrary {
public:
void Add(const std::string& name, const Ref<Shader>& shader);
void Add(const Ref<Shader>& shader);
Ref<Shader> Load(const std::string& filePath);
Ref<Shader> Load(const std::string& name, const std::string& filePath);
Ref<Shader> Get(const std::string& name) const;
bool Exists(const std::string& name) const;
private:
std::unordered_map<std::string, Ref<Shader>> m_Shaders;
};
注意,每次Load一个Shader,会缓存到shader map中。
Sandbox/Hazel/src/Hazel/Renderer/Shader.cpp
c++
void ShaderLibrary::Add(const std::string &name, const Ref<Shader> &shader) {
HZ_CORE_ASSERT(!Exists(name), "Shader already exists!");
m_Shaders[name] = shader;
}
void ShaderLibrary::Add(const Ref<Shader> &shader) {
auto& name = shader->GetName();
HZ_CORE_ASSERT(!Exists(name), "Shader already exists!");
Add(name, shader);
}
Ref<Shader> ShaderLibrary::Load(const std::string &filePath) {
auto shader = Shader::Create(filePath);
Add(shader);
return shader;
}
Ref<Shader> ShaderLibrary::Load(const std::string &name, const std::string &filePath) {
auto shader = Shader::Create(filePath);
Add(shader);
return shader;
}
Ref<Shader> ShaderLibrary::Get(const std::string &name) const {
HZ_CORE_ASSERT(Exists(name), "Share not found!");
return m_Shaders.at(name);
}
bool ShaderLibrary::Exists(const std::string &name) const {
return m_Shaders.find(name) != m_Shaders.end();
}
OpenGLShader中增加增加Name
OpenGLShader中增加std::string m_Name。
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLShader.h
c++
OpenGLShader(const std::string name, const std::string& vertexSrc, const std::string& fragmentSrc);
c++
private:
std::string m_Name;
将.glsl文件名作为Shader的名称。
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLShader.cpp
c++
OpenGLShader::OpenGLShader(const std::string &filePath) {
std::string source = ReadFile(filePath);
auto ShaderSources = PreProcess(source);
Compile(ShaderSources);
// 以文件名作为shader名字
// Extract name from filepath
auto lastSlash = filePath.find_last_of("/\\");
lastSlash = lastSlash == std::string::npos ? 0 : lastSlash + 1;
auto lastDot = filePath.rfind('.');
auto count = lastDot == std::string::npos ? filePath.size() - lastSlash : lastDot - lastSlash;
m_Name = filePath.substr(lastSlash, count);
}
Compile中用array替换vector
OpenGLShader::Compile方法中,有一处代码可以优化。glShaderIDs用std::array代替std::vector。
std::vector方便拓展,用起来很方便,但是内容存储于堆上,相对而言,std::array内存在栈上申请,性能会更好一些。
且shader的引用是一个int值,不会占太多内存。
c++
void OpenGLShader::Compile(const std::unordered_map<GLenum, std::string> &shaderSources) {
GLuint program = glCreateProgram();
HZ_CORE_ASSERT(shaderSources.size() <= 2, "we only support 2 shaders for now");
std::array<GLenum, 2> glShaderIDs;
int glShaderIDIndex = 0;
...
...
glAttachShader(program, shader);
glShaderIDs[glShaderIDIndex++] = shader;
}
SandBoxApp中使用ShaderLibrary
将textureShader的直接调用,改为通过ShaderLibrary.Load()方法
c++
auto textureShader = m_ShaderLibrary.Load("../assets/shaders/Texture.glsl");
...
textureShader->Bind();
还有一些细节不一一说明了,请参考下面完整代码对比参考。
完整代码&总结
修改的地方比较零碎,参考完整代码:
github.com/summer-go/H...
总结:
所有的软件工程,做到最后都是在扣性能,需要开发者对内存很敏感,对容器的区别很熟悉,才能针对自己的业务场景做深度优化。