You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
630 lines
15 KiB
630 lines
15 KiB
/* |
|
* Copyright (C) 2022,2024 Riyyi |
|
* |
|
* SPDX-License-Identifier: MIT |
|
*/ |
|
|
|
#include <algorithm> // std::min |
|
#include <utility> // std::move |
|
|
|
#include "glad/glad.h" |
|
#include "ruc/format/log.h" |
|
|
|
#include "inferno/render/buffer.h" |
|
#include "inferno/render/renderer.h" |
|
#include "inferno/render/shader.h" |
|
#include "inferno/render/texture.h" |
|
|
|
namespace Inferno { |
|
|
|
void RenderCommand::initialize() |
|
{ |
|
setDepthTest(true); |
|
|
|
// Enable transparency |
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
|
glEnable(GL_BLEND); |
|
|
|
ruc::info("RenderCommand initialized"); |
|
} |
|
|
|
void RenderCommand::destroy() |
|
{ |
|
} |
|
|
|
void RenderCommand::clear() |
|
{ |
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
|
} |
|
|
|
void RenderCommand::clearColor(const glm::vec4& color) |
|
{ |
|
glClearColor(color.r, color.g, color.b, color.a); |
|
} |
|
|
|
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); |
|
} |
|
|
|
void RenderCommand::setViewport(int32_t x, int32_t y, uint32_t width, uint32_t height) |
|
{ |
|
glViewport(x, y, width, height); |
|
} |
|
|
|
void RenderCommand::setDepthTest(bool enabled) |
|
{ |
|
// Set z-buffer / depth buffer |
|
enabled ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST); |
|
} |
|
|
|
bool RenderCommand::depthTest() |
|
{ |
|
unsigned char depthTest = GL_FALSE; |
|
glGetBooleanv(GL_DEPTH_TEST, &depthTest); |
|
return depthTest == GL_TRUE; |
|
} |
|
|
|
int32_t RenderCommand::textureUnitAmount() |
|
{ |
|
int32_t amount = 0; |
|
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &amount); |
|
return amount; |
|
} |
|
|
|
// ----------------------------------------- |
|
|
|
uint32_t Renderer::m_supportedTextureUnitPerBatch = 0; |
|
|
|
void Renderer::initialize() |
|
{ |
|
// Get amount of texture units supported by the GPU |
|
uint32_t constTextureUnitCount = textureUnitPerBatch; |
|
uint32_t gpuTextureUnitCount = RenderCommand::textureUnitAmount(); |
|
m_supportedTextureUnitPerBatch = std::min(constTextureUnitCount, gpuTextureUnitCount); |
|
|
|
// Texture unit 0 is reserved for no texture |
|
m_textureUnits[0] = nullptr; |
|
|
|
// Create texture unit samplers |
|
int32_t samplers[textureUnitPerBatch]; |
|
for (uint32_t i = 0; i < textureUnitPerBatch; i++) { |
|
samplers[i] = i; |
|
} |
|
|
|
// Create shader |
|
loadShader(); |
|
m_shader->bind(); |
|
m_shader->setInt("u_textures", samplers, textureUnitPerBatch); |
|
m_shader->unbind(); |
|
|
|
// Create vertex array |
|
m_vertexArray = std::make_shared<VertexArray>(); |
|
} |
|
|
|
void Renderer::destroy() |
|
{ |
|
} |
|
|
|
uint32_t Renderer::addTextureUnit(std::shared_ptr<Texture> texture) |
|
{ |
|
if (texture == nullptr) { |
|
return 0; |
|
} |
|
|
|
// Create a new batch if the texture unit limit has been reached |
|
if (m_textureUnitIndex >= m_supportedTextureUnitPerBatch) { |
|
nextBatch(); |
|
} |
|
|
|
// If texure was already added |
|
for (uint32_t i = 1; i < m_textureUnitIndex; i++) { |
|
if (m_textureUnits[i] == texture) { |
|
return i; |
|
} |
|
} |
|
|
|
// Add texture |
|
uint32_t textureUnitIndex = m_textureUnitIndex; |
|
m_textureUnits[textureUnitIndex] = texture; |
|
m_textureUnitIndex++; |
|
|
|
return textureUnitIndex; |
|
} |
|
|
|
void Renderer::bind() |
|
{ |
|
m_shader->bind(); |
|
|
|
for (uint32_t i = 1; i < m_textureUnitIndex; i++) { |
|
m_textureUnits[i]->bind(i); |
|
} |
|
|
|
m_vertexArray->bind(); |
|
} |
|
|
|
void Renderer::unbind() |
|
{ |
|
m_vertexArray->unbind(); |
|
|
|
for (uint32_t i = 1; i < m_textureUnitIndex; i++) { |
|
m_textureUnits[i]->unbind(); |
|
} |
|
|
|
m_shader->unbind(); |
|
} |
|
|
|
// ----------------------------------------- |
|
|
|
Renderer2D::Renderer2D(s) |
|
{ |
|
Renderer::initialize(); |
|
|
|
// CPU |
|
// --------------------------------- |
|
|
|
// Create array for storing quads vertices |
|
m_vertexBufferBase = std::make_unique<QuadVertex[]>(vertexCount); |
|
m_vertexBufferPtr = m_vertexBufferBase.get(); |
|
|
|
// Set default quad vertex positions |
|
m_vertexPositions[0] = { -0.5f, -0.5f, 0.0f, 1.0f }; |
|
m_vertexPositions[1] = { 0.5f, -0.5f, 0.0f, 1.0f }; |
|
m_vertexPositions[2] = { 0.5f, 0.5f, 0.0f, 1.0f }; |
|
m_vertexPositions[3] = { -0.5f, 0.5f, 0.0f, 1.0f }; |
|
|
|
// Generate indices |
|
|
|
uint32_t* indices = new uint32_t[indexCount]; |
|
|
|
uint32_t offset = 0; |
|
for (uint32_t i = 0; i < indexCount; i += indexPerQuad) { |
|
indices[i + 0] = offset + 0; |
|
indices[i + 1] = offset + 1; |
|
indices[i + 2] = offset + 2; |
|
indices[i + 3] = offset + 2; |
|
indices[i + 4] = offset + 3; |
|
indices[i + 5] = offset + 0; |
|
|
|
offset += vertexPerQuad; |
|
} |
|
|
|
// GPU |
|
// --------------------------------- |
|
|
|
// Create vertex buffer |
|
auto vertexBuffer = std::make_shared<VertexBuffer>(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); |
|
|
|
// Create index buffer |
|
auto indexBuffer = std::make_shared<IndexBuffer>(indices, sizeof(uint32_t) * indexCount); |
|
m_vertexArray->setIndexBuffer(indexBuffer); |
|
delete[] indices; |
|
|
|
ruc::info("Renderer2D initialized"); |
|
} |
|
|
|
Renderer2D::~Renderer2D() |
|
{ |
|
Renderer::destroy(); |
|
} |
|
|
|
void Renderer2D::beginScene(glm::mat4 cameraProjection, glm::mat4 cameraView) |
|
{ |
|
m_shader->bind(); |
|
m_shader->setFloat("u_projectionView", cameraProjection * cameraView); |
|
m_shader->unbind(); |
|
} |
|
|
|
void Renderer2D::endScene() |
|
{ |
|
nextBatch(); |
|
} |
|
|
|
void Renderer2D::drawQuad(const TransformComponent& transform, glm::vec4 color) |
|
{ |
|
drawQuad(transform, color, nullptr); |
|
} |
|
|
|
void Renderer2D::drawQuad(const TransformComponent& transform, glm::mat4 color) |
|
{ |
|
drawQuad(transform, color, nullptr); |
|
} |
|
|
|
void Renderer2D::drawQuad(const TransformComponent& transform, glm::vec4 color, std::shared_ptr<Texture> texture) |
|
{ |
|
drawQuad(transform, glm::mat4(color, color, color, color), texture); |
|
} |
|
|
|
void Renderer2D::drawQuad(const TransformComponent& transform, glm::mat4 color, std::shared_ptr<Texture> texture) |
|
{ |
|
// Create a new batch if the quad limit has been reached |
|
if (m_quadIndex >= quadCount) { |
|
nextBatch(); |
|
} |
|
|
|
constexpr glm::vec2 textureCoordinates[] = { |
|
{ 0.0f, 0.0f }, |
|
{ 1.0f, 0.0f }, |
|
{ 1.0f, 1.0f }, |
|
{ 0.0f, 1.0f } |
|
}; |
|
|
|
uint32_t textureUnitIndex = addTextureUnit(texture); |
|
|
|
// Add the quads 4 vertices |
|
for (uint32_t i = 0; i < vertexPerQuad; i++) { |
|
m_vertexBufferPtr->position = transform.transform * m_vertexPositions[i]; |
|
m_vertexBufferPtr->color = color[i]; |
|
m_vertexBufferPtr->textureCoordinates = textureCoordinates[i]; |
|
m_vertexBufferPtr->textureIndex = (float)textureUnitIndex; |
|
m_vertexBufferPtr++; |
|
} |
|
|
|
m_quadIndex++; |
|
} |
|
|
|
void Renderer2D::loadShader() |
|
{ |
|
m_shader = ShaderManager::the().load("assets/glsl/batch-quad"); |
|
} |
|
|
|
void Renderer2D::flush() |
|
{ |
|
if (m_quadIndex == 0) { |
|
return; |
|
} |
|
|
|
// Upload vertex data to GPU |
|
m_vertexArray->getVertexBuffers().at(0)->uploadData( |
|
m_vertexBufferBase.get(), |
|
m_quadIndex * vertexPerQuad * sizeof(QuadVertex)); |
|
|
|
bind(); |
|
|
|
// Render |
|
RenderCommand::drawIndexed(*m_vertexArray, m_quadIndex * indexPerQuad); |
|
|
|
unbind(); |
|
} |
|
|
|
void Renderer2D::startBatch() |
|
{ |
|
m_quadIndex = 0; |
|
m_vertexBufferPtr = m_vertexBufferBase.get(); |
|
|
|
m_textureUnitIndex = 1; |
|
} |
|
|
|
void Renderer2D::nextBatch() |
|
{ |
|
flush(); |
|
startBatch(); |
|
} |
|
|
|
// ----------------------------------------- |
|
|
|
RendererCubemap::RendererCubemap(s) |
|
{ |
|
Renderer::initialize(); |
|
|
|
// CPU |
|
// --------------------------------- |
|
|
|
// Create array for storing quads vertices |
|
m_vertexBufferBase = std::make_unique<CubemapVertex[]>(vertexCount); |
|
m_vertexBufferPtr = m_vertexBufferBase.get(); |
|
|
|
// Set default cubemap vertex positions |
|
|
|
// Back face - v |
|
m_vertexPositions[0] = { 0.5f, -0.5f, 0.5f, 1.0f }; |
|
m_vertexPositions[1] = { -0.5f, -0.5f, 0.5f, 1.0f }; |
|
m_vertexPositions[2] = { -0.5f, 0.5f, 0.5f, 1.0f }; |
|
m_vertexPositions[3] = { 0.5f, 0.5f, 0.5f, 1.0f }; |
|
|
|
// Left face - v |
|
m_vertexPositions[7] = { -0.5f, 0.5f, 0.5f, 1.0f }; |
|
m_vertexPositions[6] = { -0.5f, 0.5f, -0.5f, 1.0f }; |
|
m_vertexPositions[5] = { -0.5f, -0.5f, -0.5f, 1.0f }; |
|
m_vertexPositions[4] = { -0.5f, -0.5f, 0.5f, 1.0f }; |
|
|
|
// Right face - v |
|
m_vertexPositions[8] = { 0.5f, -0.5f, -0.5f, 1.0f }; |
|
m_vertexPositions[9] = { 0.5f, -0.5f, 0.5f, 1.0f }; |
|
m_vertexPositions[10] = { 0.5f, 0.5f, 0.5f, 1.0f }; |
|
m_vertexPositions[11] = { 0.5f, 0.5f, -0.5f, 1.0f }; |
|
|
|
// Front face - v |
|
m_vertexPositions[12] = { -0.5f, -0.5f, -0.5f, 1.0f }; |
|
m_vertexPositions[13] = { 0.5f, -0.5f, -0.5f, 1.0f }; |
|
m_vertexPositions[14] = { 0.5f, 0.5f, -0.5f, 1.0f }; |
|
m_vertexPositions[15] = { -0.5f, 0.5f, -0.5f, 1.0f }; |
|
|
|
// Top face |
|
m_vertexPositions[16] = { -0.5f, 0.5f, -0.5f, 1.0f }; |
|
m_vertexPositions[17] = { 0.5f, 0.5f, -0.5f, 1.0f }; |
|
m_vertexPositions[18] = { 0.5f, 0.5f, 0.5f, 1.0f }; |
|
m_vertexPositions[19] = { -0.5f, 0.5f, 0.5f, 1.0f }; |
|
|
|
// Bottom face |
|
m_vertexPositions[20] = { -0.5f, -0.5f, -0.5f, 1.0f }; |
|
m_vertexPositions[21] = { -0.5f, -0.5f, 0.5f, 1.0f }; |
|
m_vertexPositions[22] = { 0.5f, -0.5f, 0.5f, 1.0f }; |
|
m_vertexPositions[23] = { 0.5f, -0.5f, -0.5f, 1.0f }; |
|
|
|
// Generate indices |
|
|
|
uint32_t* indices = new uint32_t[indexCount]; |
|
|
|
uint32_t offset = 0; |
|
for (uint32_t i = 0; i < indexCount; i += indexPerQuad) { |
|
indices[i + 0] = offset + 0; |
|
indices[i + 1] = offset + 1; |
|
indices[i + 2] = offset + 2; |
|
indices[i + 3] = offset + 2; |
|
indices[i + 4] = offset + 3; |
|
indices[i + 5] = offset + 0; |
|
|
|
offset += vertexPerQuad; |
|
} |
|
|
|
// GPU |
|
// --------------------------------- |
|
|
|
// Create vertex buffer |
|
auto vertexBuffer = std::make_shared<VertexBuffer>(sizeof(CubemapVertex) * vertexCount); |
|
vertexBuffer->setLayout({ |
|
{ BufferElementType::Vec3, "a_position" }, |
|
{ BufferElementType::Vec4, "a_color" }, |
|
{ BufferElementType::Float, "a_textureIndex" }, |
|
}); |
|
m_vertexArray->addVertexBuffer(vertexBuffer); |
|
|
|
// Create index buffer |
|
auto indexBuffer = std::make_shared<IndexBuffer>(indices, sizeof(uint32_t) * indexCount); |
|
m_vertexArray->setIndexBuffer(indexBuffer); |
|
delete[] indices; |
|
|
|
ruc::info("RendererCubemap initialized"); |
|
} |
|
|
|
RendererCubemap::~RendererCubemap() |
|
{ |
|
Renderer::destroy(); |
|
} |
|
|
|
void RendererCubemap::beginScene(glm::mat4 cameraProjection, glm::mat4 cameraView) |
|
{ |
|
// We want the skybox fixed in position, so only retain the rotation and scale. |
|
// Set the translation of the camera's view matrix to 0, meaning: |
|
// x x x 0 |
|
// x x x 0 |
|
// x x x 0 |
|
// 0 0 0 1 |
|
cameraView = glm::mat4(glm::mat3(cameraView)); |
|
|
|
m_shader->bind(); |
|
m_shader->setFloat("u_projectionView", cameraProjection * cameraView); |
|
m_shader->unbind(); |
|
} |
|
|
|
void RendererCubemap::endScene() |
|
{ |
|
nextBatch(); |
|
} |
|
|
|
void RendererCubemap::drawCubemap(const TransformComponent& transform, glm::vec4 color, std::shared_ptr<Texture> texture) |
|
{ |
|
drawCubemap(transform, glm::mat4(color, color, color, color), texture); |
|
} |
|
|
|
void RendererCubemap::drawCubemap(const TransformComponent& transform, glm::mat4 color, std::shared_ptr<Texture> texture) |
|
{ |
|
// Create a new batch if the quad limit has been reached |
|
if (m_quadIndex >= quadCount) { |
|
nextBatch(); |
|
} |
|
|
|
uint32_t textureUnitIndex = addTextureUnit(texture); |
|
|
|
// Add the quads 4 vertices |
|
for (uint32_t i = 0; i < vertexPerQuad * quadPerCube; i++) { |
|
m_vertexBufferPtr->position = transform.transform * m_vertexPositions[i]; |
|
m_vertexBufferPtr->color = color[i % 4]; |
|
m_vertexBufferPtr->textureIndex = (float)textureUnitIndex; |
|
m_vertexBufferPtr++; |
|
} |
|
|
|
m_quadIndex += quadPerCube; |
|
} |
|
|
|
void RendererCubemap::loadShader() |
|
{ |
|
m_shader = ShaderManager::the().load("assets/glsl/batch-cubemap"); |
|
} |
|
|
|
void RendererCubemap::flush() |
|
{ |
|
if (m_quadIndex == 0) { |
|
return; |
|
} |
|
|
|
// Upload vertex data to GPU |
|
m_vertexArray->getVertexBuffers().at(0)->uploadData( |
|
m_vertexBufferBase.get(), |
|
m_quadIndex * vertexPerQuad * sizeof(CubemapVertex)); |
|
|
|
bind(); |
|
|
|
// Render |
|
bool depthTest = RenderCommand::depthTest(); |
|
RenderCommand::setDepthTest(false); |
|
RenderCommand::drawIndexed(*m_vertexArray, m_quadIndex * indexPerQuad); |
|
RenderCommand::setDepthTest(depthTest); |
|
|
|
unbind(); |
|
} |
|
|
|
void RendererCubemap::startBatch() |
|
{ |
|
m_quadIndex = 0; |
|
m_vertexBufferPtr = m_vertexBufferBase.get(); |
|
|
|
m_textureUnitIndex = 1; |
|
} |
|
|
|
void RendererCubemap::nextBatch() |
|
{ |
|
flush(); |
|
startBatch(); |
|
} |
|
|
|
// ----------------------------------------- |
|
|
|
RendererCharacter::RendererCharacter(s) |
|
{ |
|
Renderer::initialize(); |
|
|
|
// CPU |
|
// --------------------------------- |
|
|
|
// Create array for storing quads vertices |
|
m_vertexBufferBase = std::make_unique<CharacterVertex[]>(vertexCount); |
|
m_vertexBufferPtr = m_vertexBufferBase.get(); |
|
|
|
// Generate indices |
|
|
|
uint32_t* indices = new uint32_t[indexCount]; |
|
|
|
uint32_t offset = 0; |
|
for (uint32_t i = 0; i < indexCount; i += indexPerQuad) { |
|
indices[i + 0] = offset + 0; |
|
indices[i + 1] = offset + 1; |
|
indices[i + 2] = offset + 2; |
|
indices[i + 3] = offset + 2; |
|
indices[i + 4] = offset + 3; |
|
indices[i + 5] = offset + 0; |
|
|
|
offset += vertexPerQuad; |
|
} |
|
|
|
// GPU |
|
// --------------------------------- |
|
|
|
// Create vertex buffer |
|
auto vertexBuffer = std::make_shared<VertexBuffer>(sizeof(CharacterVertex) * vertexCount); |
|
vertexBuffer->setLayout({ |
|
{ BufferElementType::Vec3, "a_position" }, |
|
{ BufferElementType::Vec4, "a_color" }, |
|
{ BufferElementType::Vec2, "a_textureCoordinates" }, |
|
{ BufferElementType::Float, "a_textureIndex" }, |
|
{ BufferElementType::Float, "a_width" }, |
|
{ BufferElementType::Float, "a_edge" }, |
|
{ BufferElementType::Float, "a_borderWidth" }, |
|
{ BufferElementType::Float, "a_borderEdge" }, |
|
{ BufferElementType::Vec4, "a_borderColor" }, |
|
{ BufferElementType::Float, "a_offset" }, |
|
}); |
|
m_vertexArray->addVertexBuffer(vertexBuffer); |
|
|
|
// Create index buffer |
|
auto indexBuffer = std::make_shared<IndexBuffer>(indices, sizeof(uint32_t) * indexCount); |
|
m_vertexArray->setIndexBuffer(indexBuffer); |
|
delete[] indices; |
|
|
|
ruc::info("RendererCharacter initialized"); |
|
} |
|
|
|
RendererCharacter::~RendererCharacter() |
|
{ |
|
Renderer::destroy(); |
|
} |
|
|
|
void RendererCharacter::beginScene() |
|
{ |
|
} |
|
|
|
void RendererCharacter::endScene() |
|
{ |
|
nextBatch(); |
|
} |
|
|
|
void RendererCharacter::drawCharacter(std::array<CharacterVertex, vertexPerQuad>& characterQuad, std::shared_ptr<Texture> texture) |
|
{ |
|
// Create a new batch if the quad limit has been reached |
|
if (m_quadIndex >= quadCount) { |
|
nextBatch(); |
|
} |
|
|
|
uint32_t textureUnitIndex = addTextureUnit(texture); |
|
|
|
// Add the quads 4 vertices |
|
for (uint32_t i = 0; i < vertexPerQuad; i++) { |
|
m_vertexBufferPtr->quad.position = characterQuad[i].quad.position; |
|
m_vertexBufferPtr->quad.color = characterQuad[i].quad.color; |
|
m_vertexBufferPtr->quad.textureCoordinates = characterQuad[i].quad.textureCoordinates; |
|
m_vertexBufferPtr->quad.textureIndex = (float)textureUnitIndex; |
|
|
|
m_vertexBufferPtr->width = characterQuad[i].width; |
|
m_vertexBufferPtr->edge = characterQuad[i].edge; |
|
m_vertexBufferPtr->borderWidth = characterQuad[i].borderWidth; |
|
m_vertexBufferPtr->borderEdge = characterQuad[i].borderEdge; |
|
m_vertexBufferPtr->borderColor = characterQuad[i].borderColor; |
|
m_vertexBufferPtr->offset = characterQuad[i].offset; |
|
|
|
m_vertexBufferPtr++; |
|
} |
|
|
|
m_quadIndex++; |
|
} |
|
|
|
void RendererCharacter::loadShader() |
|
{ |
|
m_shader = ShaderManager::the().load("assets/glsl/batch-font"); |
|
} |
|
|
|
void RendererCharacter::flush() |
|
{ |
|
if (m_quadIndex == 0) { |
|
return; |
|
} |
|
|
|
// Upload vertex data to GPU |
|
m_vertexArray->getVertexBuffers().at(0)->uploadData( |
|
m_vertexBufferBase.get(), |
|
m_quadIndex * vertexPerQuad * sizeof(CharacterVertex)); |
|
|
|
bind(); |
|
|
|
// Render |
|
bool depthTest = RenderCommand::depthTest(); |
|
RenderCommand::setDepthTest(false); |
|
RenderCommand::drawIndexed(*m_vertexArray, m_quadIndex * indexPerQuad); |
|
RenderCommand::setDepthTest(depthTest); |
|
|
|
unbind(); |
|
} |
|
|
|
void RendererCharacter::startBatch() |
|
{ |
|
m_quadIndex = 0; |
|
m_vertexBufferPtr = m_vertexBufferBase.get(); |
|
|
|
m_textureUnitIndex = 1; |
|
} |
|
|
|
void RendererCharacter::nextBatch() |
|
{ |
|
flush(); |
|
startBatch(); |
|
} |
|
|
|
} // namespace Inferno
|
|
|