185 lines
6.1 KiB
C++
185 lines
6.1 KiB
C++
/* +------------------------------------------------------+
|
||
____/ \____ /| - Open source game framework licensed to freely use, |
|
||
\ / / | copy, modify and sell without restriction |
|
||
+--\ ^__^ /--+ | |
|
||
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
||
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
||
| SPACE ~~~~~ | /
|
||
| ~~~~~~~ BOX |/
|
||
+-------------*/
|
||
|
||
#include "Texture.hpp"
|
||
using namespace sb;
|
||
|
||
/* Have to pass our deleter to abstract base class at instantiation */
|
||
Texture::Texture() : GLObject(texture_deleter) {}
|
||
|
||
/* If an image path is passed at creation, the path will be stored in the class for later loading */
|
||
Texture::Texture(fs::path path) : Texture()
|
||
{
|
||
associate(path);
|
||
}
|
||
|
||
/* Store an image path as a member variable for loading later. Each texture should have one image
|
||
* path (support for multiple mipmap levels may be added later) */
|
||
void Texture::associate(fs::path path)
|
||
{
|
||
this->path = path;
|
||
}
|
||
|
||
/* Forward the GL texture generate function to the base class */
|
||
void Texture::generate()
|
||
{
|
||
GLObject::generate(glGenTextures);
|
||
}
|
||
|
||
/* Generate a GL_TEXTURE_2D texture ID and allocate GL_RGBA8 storage for the given size */
|
||
void Texture::generate(glm::vec2 size)
|
||
{
|
||
generate();
|
||
bind();
|
||
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, size.x, size.y);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||
sb::Log::gl_errors();
|
||
}
|
||
|
||
void Texture::load()
|
||
{
|
||
if (!path.empty())
|
||
{
|
||
load(path);
|
||
}
|
||
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.c_str()), 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());
|
||
message << "Loading image at " << path;
|
||
message_level = sb::Log::INFO;
|
||
}
|
||
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;
|
||
sb::Log::Level message_level;
|
||
if (surface->w > 0 && surface->h > 0)
|
||
{
|
||
message << "Loading image from SDL surface (" << surface->w << "×" << surface->h << ", " << SDL_GetPixelFormatName(surface->format->format) << ")";
|
||
load(surface->pixels, {surface->w, surface->h}, GL_RGBA, GL_UNSIGNED_BYTE);
|
||
message_level = sb::Log::DEBUG;
|
||
}
|
||
else
|
||
{
|
||
message << "Cannot load into texture, invalid image data without dimensions found";
|
||
message_level = sb::Log::WARN;
|
||
}
|
||
sb::Log::log(message, message_level);
|
||
}
|
||
|
||
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::DEBUG;
|
||
}
|
||
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
|
||
{
|
||
glBindTexture(GL_TEXTURE_2D, this->id());
|
||
}
|
||
|
||
/* glGetTexlevelparameteriv is not available in OpenGL ES 3.0 */
|
||
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(ANDROID)
|
||
void Texture::load(void* pixels, GLenum format, GLenum type)
|
||
{
|
||
if (!generated())
|
||
{
|
||
throw std::invalid_argument("This texture has not been generated and has no size property, so a size must be provided");
|
||
}
|
||
else
|
||
{
|
||
load(pixels, size(), format, type);
|
||
}
|
||
}
|
||
|
||
glm::vec2 Texture::size() const
|
||
{
|
||
if (generated())
|
||
{
|
||
bind();
|
||
int width, height;
|
||
int miplevel = 0;
|
||
glGetTexLevelParameteriv(GL_TEXTURE_2D, miplevel, GL_TEXTURE_WIDTH, &width);
|
||
glGetTexLevelParameteriv(GL_TEXTURE_2D, miplevel, GL_TEXTURE_HEIGHT, &height);
|
||
return {width, height};
|
||
}
|
||
else
|
||
{
|
||
return {0, 0};
|
||
}
|
||
}
|
||
#endif
|
||
|
||
/* Textures are considered equal if they have the same ID */
|
||
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_Log("destroying texture ID %i", *id);
|
||
glDeleteTextures(1, id);
|
||
}
|