/* +------------------------------------------------------+ ____/ \____ /| - Open source game framework licensed to freely use, | \ / / | copy, modify and sell without restriction | +--\ ^__^ /--+ | | | ~/ \~ | | - created for | | ~~~~~~~~~~~~ | +------------------------------------------------------+ | SPACE ~~~~~ | / | ~~~~~~~ BOX |/ +-------------*/ #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 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 surface(IMG_Load(path.string().c_str()), SDL_FreeSurface); if (surface.get() != nullptr) { std::unique_ptr 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 surface(IMG_Load_RW(rw, 0), SDL_FreeSurface); std::unique_ptr 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; }