spacebox/src/Texture.cpp

263 lines
7.9 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ✨ +------------------------------------------------------+
____/ \____ ✨/| Open source game framework licensed to freely use, |
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
+--\ . . /--+ | |
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
| SPACE 🪐🅱 OX | /
| 🌊 ~ ~~~~ ~~ |/
+-------------*/
#include "Texture.hpp"
using namespace sb;
Texture::Texture() : GLObject(texture_deleter) {}
Texture::Texture(fs::path path) : Texture()
{
associate(path);
}
void Texture::associate(fs::path path)
{
this->path = path;
}
void Texture::filter(GLint value)
{
_filter = value;
}
void Texture::generate()
{
GLObject::generate(glGenTextures);
}
void Texture::generate(glm::vec2 size, GLenum format, std::optional<GLint> filter_value)
{
/* Only generate a new texture ID and reallocate memory if the current texture ID hasn't been registered by this object as having
* identically sized memory with the same format. */
if (!generated() || !_size.has_value() || !_format.has_value() || _size != size || _format != format)
{
generate();
bind();
/* Use nullptr because data will be loaded later with glTexSubImage2d */
glTexImage2D(GL_TEXTURE_2D, 0, format, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}
else
{
bind();
}
/* Set the resizing algorithm of this texture */
if (!filter_value.has_value())
{
filter_value = _filter;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_value.value());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_value.value());
/* Set the texture wrap of this texture */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
sb::Log::gl_errors();
/* Store a copy of size */
_size = size;
}
void Texture::load()
{
if (!path.empty())
{
#if !defined(__ANDROID__)
/* Can't check for file existence in an Android APK */
if (fs::exists(path))
{
#endif
load(path);
#if !defined(__ANDROID__)
}
else
{
std::ostringstream message;
message << "Error loading texture path: " << path;
sb::Log::log(message, sb::Log::ERR);
}
#endif
}
else
{
std::ostringstream message;
message << "Cannot load, no path has been specified yet for texture " << id();
sb::Log::log(message, sb::Log::WARN);
}
}
void Texture::load(fs::path path)
{
std::ostringstream message;
sb::Log::Level message_level;
if (path.has_filename())
{
/* Load file path as a surface object to access pixel data and flip into OpenGL orientation. Attach a destructor so it will free
* itself when it goes out of scope at the end of this function. */
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> surface(IMG_Load(path.string().c_str()), SDL_FreeSurface);
if (surface.get() != nullptr)
{
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> flipped_surface(
rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface);
if (flipped_surface.get() != nullptr)
{
load(flipped_surface.get());
message << "Loading image at " << path;
message_level = sb::Log::INFO;
}
else
{
message << "Error flipping image surface at " << path;
throw std::runtime_error(sb::Log::sdl_error(message.str()).str());
}
}
else
{
message << "Error opening image " << path;
throw std::runtime_error(sb::Log::sdl_error(message.str()).str());
}
}
else
{
message << "Cannot load, " << path << " is not a vaild path to a file";
message_level = sb::Log::WARN;
}
sb::Log::log(message, message_level);
}
void Texture::load(SDL_RWops* rw)
{
/* Load RW object as path as a surface object to access pixel data and flip into OpenGL orientation. Attach a destructor so it will
* free itself when it goes out of scope at the end of this function. */
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> surface(IMG_Load_RW(rw, 0), SDL_FreeSurface);
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> flipped_surface(
rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface);
load(flipped_surface.get());
}
void Texture::load(SDL_Surface* surface)
{
std::ostringstream message;
if (surface->w > 0 && surface->h > 0)
{
message << "Loading image from SDL surface (" << surface->w << "×" << surface->h << ", " <<
SDL_GetPixelFormatName(surface->format->format) << ")";
sb::Log::log(message, sb::Log::VERBOSE);
#if defined(__MACOS__)
load(surface->pixels, {surface->w, surface->h}, GL_BGRA, GL_UNSIGNED_BYTE);
#else
load(surface->pixels, {surface->w, surface->h}, GL_RGBA, GL_UNSIGNED_BYTE);
#endif
}
else
{
message << "Cannot load into texture, invalid image data without dimensions found";
sb::Log::log(message, sb::Log::WARN);
}
}
void Texture::load(void* pixels, glm::vec2 size, GLenum format, GLenum type)
{
std::ostringstream message;
sb::Log::Level message_level;
if (size.x > 0 && size.y > 0)
{
if (!generated())
{
generate(size);
}
bind();
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.x, size.y, format, type, pixels);
message << "Loaded " << size.x << "×" << size.y << " image into texture ID " << id();
message_level = sb::Log::VERBOSE;
}
else
{
message << "Cannot load pixels with zero or negative size in either dimension.";
message_level = sb::Log::WARN;
}
sb::Log::log(message, message_level);
sb::Log::gl_errors("after loading texture");
}
void Texture::bind() const
{
if (generated())
{
glBindTexture(GL_TEXTURE_2D, this->id());
}
else
{
std::ostringstream message;
message << "Cannot bind texture that has not been generated yet.";
if (!path.empty())
{
message << " Texture path is " << path << ".";
}
throw std::runtime_error(message.str());
}
}
void Texture::load(void* pixels, GLenum format, GLenum type)
{
if (generated())
{
load(pixels, size(), format, type);
}
else
{
std::ostringstream message;
message << "Texture";
if (!path.empty())
{
message << " with path " << path;
}
message << " has not been generated and has no size property, so a size must be provided to the load function.";
throw std::invalid_argument(message.str());
}
}
glm::vec2 Texture::size() const
{
if (_size.has_value())
{
return *_size;
}
else
{
std::ostringstream message;
message << "Texture";
if (!path.empty())
{
message << " with path " << path;
}
message << " has not been generated or loaded with a size given, so it currently has no size.";
throw std::runtime_error(message.str());
}
}
bool Texture::operator==(const Texture& texture) const
{
return id() == texture.id();
}
/* This function gets passed to the abstract base class for deleting the texture data when the ID
* pointer goes out of scope (when all instances of this texture and its copies are out of scope) */
void sb::texture_deleter(GLuint* id)
{
/* not sure why SDL_Log works here at program end but SDL_LogDebug and SDL_LogInfo don't */
SDL_LogVerbose(sb::Log::DEFAULT_CATEGORY, "destroying texture ID %i", *id);
glDeleteTextures(1, id);
delete id;
}