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.
257 lines
6.5 KiB
257 lines
6.5 KiB
/* |
|
* Copyright (C) 2022,2024 Riyyi |
|
* |
|
* SPDX-License-Identifier: MIT |
|
*/ |
|
|
|
#include <climits> // UINT_MAX |
|
#include <cstdint> // uint8_t, uint32_t |
|
#include <memory> // std::shared_ptr |
|
#include <utility> // std::move |
|
|
|
#include "glad/glad.h" |
|
#include "ruc/format/log.h" |
|
#include "ruc/meta/assert.h" |
|
#define STB_IMAGE_IMPLEMENTATION |
|
#include "stb/stb_image.h" |
|
|
|
#include "inferno/render/texture.h" |
|
|
|
namespace Inferno { |
|
|
|
Texture::~Texture() |
|
{ |
|
glDeleteTextures(1, &m_id); |
|
} |
|
|
|
void Texture::init(uint32_t width, uint32_t height, uint8_t channels) |
|
{ |
|
m_width = width; |
|
m_height = height; |
|
|
|
m_internalFormat = (channels == 3) ? GL_RGB8 : GL_RGBA8; |
|
m_dataFormat = (channels == 3) ? GL_RGB : GL_RGBA; |
|
} |
|
|
|
// ----------------------------------------- |
|
|
|
std::shared_ptr<Texture> Texture2D::create(const std::string& path) |
|
{ |
|
auto result = std::shared_ptr<Texture2D>(new Texture2D); |
|
result->m_path = path; |
|
|
|
int width = 0; |
|
int height = 0; |
|
int channels = 0; |
|
unsigned char* data = nullptr; |
|
|
|
// Load image data |
|
stbi_set_flip_vertically_on_load(1); |
|
data = stbi_load(path.c_str(), &width, &height, &channels, STBI_default); |
|
VERIFY(data, "failed to load image: '{}'", path); |
|
|
|
result->init(width, height, channels); |
|
result->create(data); |
|
|
|
// Clean resources |
|
stbi_image_free(data); |
|
|
|
return result; |
|
} |
|
|
|
void Texture2D::bind(uint32_t unit) const |
|
{ |
|
// Set active unit |
|
glActiveTexture(GL_TEXTURE0 + unit); |
|
|
|
glBindTexture(GL_TEXTURE_2D, m_id); |
|
|
|
// Reset unit |
|
glActiveTexture(GL_TEXTURE0); |
|
} |
|
|
|
void Texture2D::unbind() const |
|
{ |
|
glBindTexture(GL_TEXTURE_2D, 0); |
|
} |
|
|
|
void Texture2D::create(unsigned char* data) |
|
{ |
|
m_id = UINT_MAX; |
|
|
|
// Create texture object |
|
glGenTextures(1, &m_id); |
|
|
|
// Bind texture object |
|
glBindTexture(GL_TEXTURE_2D, m_id); |
|
|
|
// Set unpacking of pixel data to byte-alignment, |
|
// this prevents alignment issues when using a single byte for color |
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
|
|
|
// Generate texture |
|
glTexImage2D( |
|
GL_TEXTURE_2D, // Texture target |
|
0, // Midmap level, base starts at level 0 |
|
m_internalFormat, // Texture format |
|
m_width, m_height, // Image width/height |
|
0, // Always 0 (legacy) |
|
m_dataFormat, // Texture source format |
|
GL_UNSIGNED_BYTE, // Texture source datatype |
|
data); // Image data |
|
|
|
// Set the texture wrapping / filtering options |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Magnify |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Minify |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // X |
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Y |
|
|
|
// Automatically generate all mipmap levels |
|
glGenerateMipmap(GL_TEXTURE_2D); |
|
|
|
// Unbind texture object |
|
glBindTexture(GL_TEXTURE_2D, 0); |
|
} |
|
|
|
// ----------------------------------------- |
|
|
|
std::shared_ptr<Texture> TextureCubemap::create(const std::string& path) |
|
{ |
|
auto result = std::shared_ptr<TextureCubemap>(new TextureCubemap); |
|
result->m_path = path; |
|
|
|
result->create(); |
|
|
|
return result; |
|
} |
|
|
|
void TextureCubemap::bind(uint32_t unit) const |
|
{ |
|
// Set active unit |
|
glActiveTexture(GL_TEXTURE0 + unit); |
|
|
|
glBindTexture(GL_TEXTURE_CUBE_MAP, m_id); |
|
|
|
// Reset unit |
|
glActiveTexture(GL_TEXTURE0); |
|
} |
|
|
|
void TextureCubemap::unbind() const |
|
{ |
|
glBindTexture(GL_TEXTURE_CUBE_MAP, 0); |
|
} |
|
|
|
void TextureCubemap::create() |
|
{ |
|
m_id = UINT_MAX; |
|
|
|
// Create texture object |
|
glGenTextures(1, &m_id); |
|
|
|
// Bind texture object |
|
glBindTexture(GL_TEXTURE_CUBE_MAP, m_id); |
|
|
|
// Set unpacking of pixel data to byte-alignment, |
|
// this prevents alignment issues when using a single byte for color |
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
|
|
|
stbi_set_flip_vertically_on_load(0); |
|
|
|
static constexpr const char* cubemapPaths[6] { "-px", "-nx", "-py", "-ny", "-pz", "-nz" }; |
|
size_t dotIndex = m_path.find_last_of('.'); |
|
std::string path = m_path.substr(0, dotIndex); |
|
std::string extension = m_path.substr(dotIndex); |
|
|
|
int width = 0; |
|
int height = 0; |
|
int channels = 0; |
|
unsigned char* data = nullptr; |
|
for (size_t i = 0; i < 6; ++i) { |
|
std::string facePath = path + cubemapPaths[i] + extension; |
|
|
|
// Load image data |
|
data = stbi_load(facePath.c_str(), &width, &height, &channels, STBI_default); |
|
VERIFY(data, "failed to load image: '{}'", facePath.c_str()); |
|
|
|
init(width, height, channels); |
|
|
|
// Generate texture face |
|
glTexImage2D( |
|
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, // Texture target |
|
0, // Midmap level, base starts at level 0 |
|
m_internalFormat, // Internal format |
|
m_width, m_height, // Image width/height |
|
0, // Always 0 (legacy) |
|
m_dataFormat, // Texture source format |
|
GL_UNSIGNED_BYTE, // Texture source datatype |
|
data); // Image data |
|
|
|
// Clean resources |
|
stbi_image_free(data); |
|
} |
|
|
|
// Set the texture wrapping / filtering options |
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Magnify |
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Minify |
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // X |
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Y |
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Z |
|
|
|
// Unbind texture object |
|
glBindTexture(GL_TEXTURE_CUBE_MAP, 0); |
|
} |
|
|
|
// ----------------------------------------- |
|
|
|
TextureManager::TextureManager(s) |
|
{ |
|
ruc::info("TextureManager initialized"); |
|
} |
|
|
|
TextureManager::~TextureManager() |
|
{ |
|
} |
|
|
|
void TextureManager::add(const std::string& path, std::shared_ptr<Texture> texture) |
|
{ |
|
// Construct (key, value) pair and insert it into the unordered_map |
|
m_textureList.emplace(std::move(path), std::move(texture)); |
|
} |
|
|
|
std::shared_ptr<Texture> TextureManager::load(const std::string& path, Texture::Type type) |
|
{ |
|
if (exists(path)) { |
|
return get(path); |
|
} |
|
|
|
auto texture = (type == Texture::TwoDimensional) ? Texture2D::create(path) : TextureCubemap::create(path); |
|
add(path, texture); |
|
|
|
return texture; |
|
} |
|
|
|
std::shared_ptr<Texture> TextureManager::get(const std::string& path) |
|
{ |
|
return exists(path) ? m_textureList.at(path) : nullptr; |
|
} |
|
|
|
bool TextureManager::exists(const std::string& path) |
|
{ |
|
return m_textureList.find(path) != m_textureList.end(); |
|
} |
|
|
|
void TextureManager::remove(const std::string& path) |
|
{ |
|
if (exists(path)) { |
|
m_textureList.erase(path); |
|
} |
|
} |
|
|
|
void TextureManager::remove(std::shared_ptr<Texture> texture) |
|
{ |
|
if (exists(texture->path())) { |
|
m_textureList.erase(texture->path()); |
|
} |
|
} |
|
|
|
} // namespace Inferno
|
|
|