2024-05-08 12:58:42 -04:00
|
|
|
|
/* +------------------------------------------------------+
|
|
|
|
|
____/ \____ /| - Open source game framework licensed to freely use, |
|
|
|
|
|
\ / / | copy, modify and sell without restriction |
|
|
|
|
|
+--\ ^__^ /--+ | |
|
|
|
|
|
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
|
|
|
|
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
|
|
|
|
| SPACE ~~~~~ | /
|
|
|
|
|
| ~~~~~~~ BOX |/
|
2022-11-08 17:16:05 -05:00
|
|
|
|
+-------------*/
|
2021-10-18 17:33:33 -04:00
|
|
|
|
|
2021-09-20 02:32:15 -04:00
|
|
|
|
#include "Texture.hpp"
|
2024-03-04 19:44:06 -05:00
|
|
|
|
|
2021-10-18 17:33:33 -04:00
|
|
|
|
using namespace sb;
|
2021-09-20 02:32:15 -04:00
|
|
|
|
|
2021-09-24 02:43:38 -04:00
|
|
|
|
Texture::Texture() : GLObject(texture_deleter) {}
|
|
|
|
|
|
|
|
|
|
Texture::Texture(fs::path path) : Texture()
|
2021-09-20 02:32:15 -04:00
|
|
|
|
{
|
|
|
|
|
associate(path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Texture::associate(fs::path path)
|
|
|
|
|
{
|
|
|
|
|
this->path = path;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-27 22:20:20 -05:00
|
|
|
|
void Texture::filter(GLint value)
|
|
|
|
|
{
|
|
|
|
|
_filter = value;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-24 02:43:38 -04:00
|
|
|
|
void Texture::generate()
|
2021-09-20 02:32:15 -04:00
|
|
|
|
{
|
2021-09-28 02:09:49 -04:00
|
|
|
|
GLObject::generate(glGenTextures);
|
2021-09-24 02:43:38 -04:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-27 22:20:20 -05:00
|
|
|
|
void Texture::generate(glm::vec2 size, GLenum format, std::optional<GLint> filter_value)
|
2021-09-24 02:43:38 -04:00
|
|
|
|
{
|
2024-03-04 19:44:06 -05:00
|
|
|
|
/* 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. */
|
2023-08-16 18:57:35 -04:00
|
|
|
|
if (!generated() || !_size.has_value() || !_format.has_value() || _size != size || _format != format)
|
|
|
|
|
{
|
|
|
|
|
generate();
|
|
|
|
|
bind();
|
2024-04-24 15:01:17 -04:00
|
|
|
|
/* 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);
|
2023-08-16 18:57:35 -04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
bind();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Set the resizing algorithm of this texture */
|
2023-12-27 22:20:20 -05:00
|
|
|
|
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());
|
2023-08-16 18:57:35 -04:00
|
|
|
|
|
2023-11-30 00:34:54 -05:00
|
|
|
|
/* 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);
|
|
|
|
|
|
2021-10-02 19:21:07 -04:00
|
|
|
|
sb::Log::gl_errors();
|
2023-08-16 18:57:35 -04:00
|
|
|
|
|
|
|
|
|
/* Store a copy of size */
|
|
|
|
|
_size = size;
|
2021-09-20 02:32:15 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Texture::load()
|
|
|
|
|
{
|
2022-09-19 22:14:31 -04:00
|
|
|
|
if (!path.empty())
|
|
|
|
|
{
|
2023-07-10 20:33:15 -04:00
|
|
|
|
#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;
|
2023-11-14 00:19:32 -05:00
|
|
|
|
sb::Log::log(message, sb::Log::ERR);
|
2023-07-10 20:33:15 -04:00
|
|
|
|
}
|
|
|
|
|
#endif
|
2022-09-19 22:14:31 -04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
std::ostringstream message;
|
|
|
|
|
message << "Cannot load, no path has been specified yet for texture " << id();
|
|
|
|
|
sb::Log::log(message, sb::Log::WARN);
|
|
|
|
|
}
|
2021-09-20 02:32:15 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Texture::load(fs::path path)
|
|
|
|
|
{
|
2022-09-19 22:14:31 -04:00
|
|
|
|
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. */
|
2023-10-18 00:07:32 -04:00
|
|
|
|
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> surface(IMG_Load(path.string().c_str()), SDL_FreeSurface);
|
2023-08-08 12:41:10 -04:00
|
|
|
|
if (surface.get() != nullptr)
|
|
|
|
|
{
|
2024-03-04 19:44:06 -05:00
|
|
|
|
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> flipped_surface(
|
|
|
|
|
rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface);
|
2023-08-08 12:41:10 -04:00
|
|
|
|
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());
|
|
|
|
|
}
|
2022-09-19 22:14:31 -04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
message << "Cannot load, " << path << " is not a vaild path to a file";
|
|
|
|
|
message_level = sb::Log::WARN;
|
|
|
|
|
}
|
|
|
|
|
sb::Log::log(message, message_level);
|
2021-09-20 02:32:15 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Texture::load(SDL_RWops* rw)
|
|
|
|
|
{
|
2024-03-04 19:44:06 -05:00
|
|
|
|
/* 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. */
|
2021-09-20 02:32:15 -04:00
|
|
|
|
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> surface(IMG_Load_RW(rw, 0), SDL_FreeSurface);
|
2024-03-04 19:44:06 -05:00
|
|
|
|
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> flipped_surface(
|
|
|
|
|
rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface);
|
2021-09-20 02:32:15 -04:00
|
|
|
|
load(flipped_surface.get());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Texture::load(SDL_Surface* surface)
|
|
|
|
|
{
|
|
|
|
|
std::ostringstream message;
|
2022-09-19 22:14:31 -04:00
|
|
|
|
if (surface->w > 0 && surface->h > 0)
|
|
|
|
|
{
|
2024-03-04 19:44:06 -05:00
|
|
|
|
message << "Loading image from SDL surface (" << surface->w << "×" << surface->h << ", " <<
|
|
|
|
|
SDL_GetPixelFormatName(surface->format->format) << ")";
|
2023-07-01 19:21:06 -04:00
|
|
|
|
sb::Log::log(message, sb::Log::VERBOSE);
|
2024-04-24 15:01:17 -04:00
|
|
|
|
#if defined(__MACOS__)
|
|
|
|
|
load(surface->pixels, {surface->w, surface->h}, GL_BGRA, GL_UNSIGNED_BYTE);
|
|
|
|
|
#else
|
2022-09-19 22:14:31 -04:00
|
|
|
|
load(surface->pixels, {surface->w, surface->h}, GL_RGBA, GL_UNSIGNED_BYTE);
|
2024-04-24 15:01:17 -04:00
|
|
|
|
#endif
|
2022-09-19 22:14:31 -04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
message << "Cannot load into texture, invalid image data without dimensions found";
|
2023-05-31 15:06:29 -04:00
|
|
|
|
sb::Log::log(message, sb::Log::WARN);
|
2022-09-19 22:14:31 -04:00
|
|
|
|
}
|
2021-09-20 02:32:15 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Texture::load(void* pixels, glm::vec2 size, GLenum format, GLenum type)
|
|
|
|
|
{
|
2022-09-19 22:14:31 -04:00
|
|
|
|
std::ostringstream message;
|
|
|
|
|
sb::Log::Level message_level;
|
|
|
|
|
if (size.x > 0 && size.y > 0)
|
2021-09-20 02:32:15 -04:00
|
|
|
|
{
|
2022-09-19 22:14:31 -04:00
|
|
|
|
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();
|
2023-07-01 19:21:06 -04:00
|
|
|
|
message_level = sb::Log::VERBOSE;
|
2021-09-20 02:32:15 -04:00
|
|
|
|
}
|
2022-09-19 22:14:31 -04:00
|
|
|
|
else
|
|
|
|
|
{
|
2023-08-16 18:57:35 -04:00
|
|
|
|
message << "Cannot load pixels with zero or negative size in either dimension.";
|
2022-09-19 22:14:31 -04:00
|
|
|
|
message_level = sb::Log::WARN;
|
|
|
|
|
}
|
|
|
|
|
sb::Log::log(message, message_level);
|
2021-11-09 23:30:27 -05:00
|
|
|
|
sb::Log::gl_errors("after loading texture");
|
2021-09-20 02:32:15 -04:00
|
|
|
|
}
|
|
|
|
|
|
2022-10-17 21:57:56 -04:00
|
|
|
|
void Texture::bind() const
|
|
|
|
|
{
|
2023-06-26 20:49:14 -04:00
|
|
|
|
if (generated())
|
|
|
|
|
{
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, this->id());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-07-23 17:11:41 -04:00
|
|
|
|
std::ostringstream message;
|
|
|
|
|
message << "Cannot bind texture that has not been generated yet.";
|
|
|
|
|
if (!path.empty())
|
|
|
|
|
{
|
2023-08-14 16:44:25 -04:00
|
|
|
|
message << " Texture path is " << path << ".";
|
2023-07-23 17:11:41 -04:00
|
|
|
|
}
|
|
|
|
|
throw std::runtime_error(message.str());
|
2023-06-26 20:49:14 -04:00
|
|
|
|
}
|
2022-10-17 21:57:56 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-20 02:32:15 -04:00
|
|
|
|
void Texture::load(void* pixels, GLenum format, GLenum type)
|
|
|
|
|
{
|
2023-06-26 20:49:14 -04:00
|
|
|
|
if (generated())
|
2021-09-20 02:32:15 -04:00
|
|
|
|
{
|
2023-06-26 20:49:14 -04:00
|
|
|
|
load(pixels, size(), format, type);
|
2021-09-20 02:32:15 -04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-08-16 18:57:35 -04:00
|
|
|
|
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());
|
2021-09-20 02:32:15 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glm::vec2 Texture::size() const
|
|
|
|
|
{
|
2023-08-16 18:57:35 -04:00
|
|
|
|
if (_size.has_value())
|
2021-09-20 02:32:15 -04:00
|
|
|
|
{
|
2023-08-16 18:57:35 -04:00
|
|
|
|
return *_size;
|
2021-09-20 02:32:15 -04:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-08-16 18:57:35 -04:00
|
|
|
|
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());
|
2021-09-20 02:32:15 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Texture::operator==(const Texture& texture) const
|
|
|
|
|
{
|
|
|
|
|
return id() == texture.id();
|
|
|
|
|
}
|
2021-09-24 02:43:38 -04:00
|
|
|
|
|
|
|
|
|
/* 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) */
|
2021-10-18 17:33:33 -04:00
|
|
|
|
void sb::texture_deleter(GLuint* id)
|
2021-09-24 02:43:38 -04:00
|
|
|
|
{
|
|
|
|
|
/* not sure why SDL_Log works here at program end but SDL_LogDebug and SDL_LogInfo don't */
|
2023-07-01 19:21:06 -04:00
|
|
|
|
SDL_LogVerbose(sb::Log::DEFAULT_CATEGORY, "destroying texture ID %i", *id);
|
2021-09-24 02:43:38 -04:00
|
|
|
|
glDeleteTextures(1, id);
|
2023-06-11 21:49:04 -04:00
|
|
|
|
delete id;
|
2021-09-24 02:43:38 -04:00
|
|
|
|
}
|