void dbgln(const char* format, P&&... parameters) { dbgln(Log::None, true, format, std::forward(parameters)...); }
template void infoln(const char* format, P&&... parameters) { dbgln(Log::Info, true, format, std::forward(parameters)...); }
template void warnln(const char* format, P&&... parameters) { dbgln(Log::Warn, true, format, std::forward(parameters)...); }
template void dangerln(const char* format, P&&... parameters) { dbgln(Log::Danger, true, format, std::forward(parameters)...); }
template void successln(const char* format, P&&... parameters) { dbgln(Log::Success, true, format, std::forward(parameters)...); }
dbgln("Hello {}, print anything! {} {}", "World", 2.5, true);
dangerln("This will print in {}.", "red");
```
Finally, "StringLogStream" is used to convert any supported type into a
std::string. This is achieved by simply converting the buffer in
"BufferedLogStream" to a string and setting the provided string to it when the
object goes out of scope.
```cpp
StringLogStream str(std::string* fill);
std::string result;
str(&result) << "Add " << "anything " << 2.5 << " " << false;
```
### Shaders
"Shader" functionality is split into two classes, "Shader" and "ShaderManager".
The manager does exactly what the name would suggest, it manages the resources.
Using convenient functions you can `add`, `load`, `check existence`, `remove`
shaders. The shaders get stored in a hash table (hash map), with the key being
its name and the value a `std::shared_ptr` to the shader object. Adding anything
to the manager that has already been loaded will simply return the existing
instance, to prevent duplication. The other pairs "Texture/TextureManager",
"Font/FontManager" and "Gltf/GltfManager" are structured similarly.
```cpp
void add(const std::string& name, const std::shared_ptr& shader);
std::shared_ptr load(const std::string& name);
std::shared_ptr load(const std::string& vertexSource,
const std::string& fragmentSource);
bool exists(const std::string& name);
void remove(const std::string& name);
void remove(const std::shared_ptr& shader);
std::unordered_map> m_shaderList;
```
To construct a "Shader", only a name needs to be provided. It will then load,
compile and link both the vertex and fragment shader files. Any errors like
files not existing or GLSL syntax errors will be printed to the console.
```cpp
// Get file contents
std::string vertexSrc = File::read(name + ".vert");
std::string fragmentSrc = File::read(name + ".frag");
// Compile shaders
uint32_t vertexID = compileShader(GL_VERTEX_SHADER, vertexSrc.c_str());
uint32_t fragmentID = compileShader(GL_FRAGMENT_SHADER, fragmentSrc.c_str());
// Link shaders
if (vertexID > 0 && fragmentID > 0) {
m_id = linkShader(vertexID, fragmentID);
}
```
An uniform is a global "Shader" variable, to set uniform data, there are
functions for the most common used data types. ASSERT is a wrapper for `dbgln`,
which can print any registered data types, explained in the
[Logging](#logging)
section.
```cpp
int32_t findUniform(const std::string& name) const;
void setInt(const std::string& name, int value);
void setInt(const std::string& name, int* values, uint32_t count);
void setFloat(const std::string& name, float value) const;
void setFloat(const std::string& name, float v1, float v2, float v3, float v4) const;
void setFloat(const std::string& name, glm::vec2 value) const;
void setFloat(const std::string& name, glm::vec3 value) const;
void setFloat(const std::string& name, glm::vec4 value) const;
void setFloat(const std::string& name, glm::mat3 matrix) const;
void setFloat(const std::string& name, glm::mat4 matrix) const;
void bind() const;
void unbind() const;
int32_t Shader::findUniform(const std::string& name) const
{
int32_t location = glGetUniformLocation(m_id, name.c_str());
ASSERT(location != -1, "Shader could not find uniform '{}'", name);
return location;
}
void Shader::setInt(const std::string& name, int value)
{
// Set unifrom int
glUniform1i(findUniform(name), value);
}
```
In the renderer we only need to do the following. The `the` function in
"ShaderManager" is a static function that gets the instance of the singleton.
```cpp
m_shader = ShaderManager::the().load("assets/glsl/batch-quad");
m_shader->bind();
m_shader->setFloat("u_projectionView", cameraProjectionView);
m_shader->unbind();
```
### Buffers
Rendering in OpenGL is done using a set of two buffers, the vertex buffer and
the index buffer. The vertex buffer is used to store geometry data, in the
format of points. From these points, you can construct triangles, which can
actually be rendered. The constructed triangles are stored as indexes to points
in the index buffer, the simplest form of an index buffer of a single triangle
looks like the following: `[0, 1, 2]`. When you have these two buffers set up
correctly, you can draw a triangle.
```cpp
void RenderCommand::drawIndexed(const VertexArray& vertexArray, uint32_t indexCount)
{
uint32_t count = indexCount ? indexCount : vertexArray.getIndexBuffer()->getCount();
glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, nullptr);
}
```
To create a vertex buffer a lot of manual setup is needed. This includes offset
calculation, which is very prone to errors, so these steps are abstracted away
in the "VertexBuffer" class. A "VertexBuffer" object stores a "BufferLayout",
which stores a `std::vector` of "BufferElements". "BufferElements" are what
OpenGL calls vertex attributes, which allows us to specify any input we want,
because of this we also have to specify how the data should be interpreted. The
"BufferElement" objects hold the variables `type`, the types `size`, `offset` in
the buffer and if the data is normalized, these can be accessed via getter
functions. Now for the fun part of vertex buffers, the "BufferLayout", via a
`std::initializer_list` they can be constructed very easily.
```cpp
BufferLayout::BufferLayout(const std::initializer_list& elements)
: m_elements(elements)
{
calculateOffsetsAndStride();
}
void BufferLayout::calculateOffsetsAndStride()
{
m_stride = 0;
for (auto& element : m_elements) {
element.setOffset(m_stride);
m_stride += element.getSize();
}
}
```
Because of OpenGL's C-like API, rendering requires manual binding of the vertex
attribute configurations and vertex buffers, which is a lot of boilerplate. In
order to simplify this, a new concept was added called a vertex array object
(also known as VAO), which is actually required in OpenGL's core profile. VAOs
store the vertex attribute configuration and the associated vertex buffers, they
are abstracted away in the "VertexArray" class which stores the "VertexBuffers"
and an "IndexBuffer". When adding a "VertexBuffer" to a "VertexArray", it will
set up the vertex attribute configuration.
```cpp
void VertexArray::addVertexBuffer(std::shared_ptr vertexBuffer)
{
const auto& layout = vertexBuffer->getLayout();
ASSERT(layout.getElements().size(), "VertexBuffer has no layout");
bind();
vertexBuffer->bind();
uint32_t index = 0;
for (const auto& element : layout) {
glEnableVertexAttribArray(index);
glVertexAttribPointer(
index,
element.getTypeCount(),
element.getTypeGL(),
element.getNormalized() ? GL_TRUE : GL_FALSE,
layout.getStride(),
reinterpret_cast(element.getOffset()));
index++;
}
m_vertexBuffers.push_back(std::move(vertexBuffer));
unbind();
vertexBuffer->unbind();
}
```
The final usage looks like the following, notice how the layout is created
easily using the `std::initializer_list` in the `setLayout` function.
```cpp
// Create vertex array
m_vertexArray = std::make_shared();
// Create vertex buffer
auto vertexBuffer = std::make_shared(sizeof(QuadVertex) * vertexCount);
vertexBuffer->setLayout({
{ BufferElementType::Vec3, "a_position" },
{ BufferElementType::Vec4, "a_color" },
{ BufferElementType::Vec2, "a_textureCoordinates" },
{ BufferElementType::Float, "a_textureIndex" },
});
m_vertexArray->addVertexBuffer(vertexBuffer);
uint32_t indices[] { 0, 1, 2, 2, 3, 0 };
// Create index buffer
auto indexBuffer = std::make_shared(indices, sizeof(uint32_t) * indexCount);
m_vertexArray->setIndexBuffer(indexBuffer);
```
## Design Structure
Pictured below are all the classes currently in the codebase and the
relationships between them.

## Preview
There isn't much visual to show as of now.
