/* * * Copyright (C) 2023 Brett Terpstra * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BLT_TEXTURE_H #define BLT_TEXTURE_H #include #include #include #include #include #include namespace blt::gfx { class texture_file; class texture_data { friend texture_file; private: unsigned char* m_data = nullptr; int m_width = 0, m_height = 0, m_channels = 0; public: texture_data(unsigned char* data, int width, int height, int channels): m_data(data), m_width(width), m_height(height), m_channels(channels) {} texture_data(int width, int height, int channels = 4): m_width(width), m_height(height), m_channels(channels) { m_data = static_cast(STBI_MALLOC(width * height * channels)); } texture_data() = default; unsigned char* data() { return m_data; } [[nodiscard]] unsigned char* data() const { return m_data; } [[nodiscard]] int width() const { return m_width; } [[nodiscard]] int height() const { return m_height; } [[nodiscard]] int channels() const { return m_channels; } ~texture_data() { STBI_FREE(m_data); } }; class texture_file { private: std::string m_name; std::string m_path; mutable texture_data m_texture; public: /** * @param path path to the texture file * @param name reference name for this texture. If empty the texture will use path as its identifier */ explicit texture_file(const std::string& path, const std::string& name = ""); texture_file& resize(int target_width, int target_height); texture_data& texture() const { return m_texture; } [[nodiscard]] int channels() const { return m_texture.m_channels; } [[nodiscard]] int width() const { return m_texture.m_width; } [[nodiscard]] int height() const { return m_texture.m_height; } [[nodiscard]] const std::string& getName() const { return m_name; } }; struct texture_gl { protected: unsigned int textureID = 0; GLint textureBindType; GLint textureColorMode; int m_width, m_height; texture_gl( int width, int height, GLint bind_type = GL_TEXTURE_2D, GLint color_mode = GL_RGBA ): textureBindType(bind_type), textureColorMode(color_mode), m_width(width), m_height(height) { glGenTextures(1, &textureID); } public: inline void bind() const { glBindTexture(textureBindType, textureID); } inline void unbind() const { glBindTexture(textureBindType, 0); } void setDefaults() const { bind(); glTexParameteri(textureBindType, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(textureBindType, GL_TEXTURE_WRAP_T, GL_REPEAT); // nearest preserves the pixely look glTexParameteri(textureBindType, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); glTexParameteri(textureBindType, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_LINEAR); #ifdef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT // Anisotropy helps preserve textures at oblique angles float a = 0; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &a); glTexParameterf(textureBindType, GL_TEXTURE_MAX_ANISOTROPY_EXT, a); #endif unbind(); } inline void generateMipmaps() const { // it's a little inefficient binding and unbinding for these small calls, they really should be done in the constructor or data upload bind(); glGenerateMipmap(textureBindType); unbind(); } [[nodiscard]] inline unsigned int getTextureID() const { return textureID; } virtual ~texture_gl() { glDeleteTextures(1, &textureID); } }; struct texture_gl2D : public texture_gl { public: texture_gl2D(int width, int height, GLint colorMode = GL_RGBA): texture_gl(width, height, GL_TEXTURE_2D, colorMode) { bind(); // TODO: const int MIPMAP_LEVELS = 4; glTexStorage2D( textureBindType, MIPMAP_LEVELS, colorMode, width, height ); } void upload(void* data, GLint dataColorMode = GL_RGBA, int level = 0, int x_offset = 0, int y_offset = 0, int sub_width = -1, int sub_height = -1) const { if (sub_width < 0) sub_width = m_width; if (sub_height < 0) sub_height = m_height; bind(); glTexSubImage2D( textureBindType, level, x_offset, y_offset, sub_width, sub_height, dataColorMode, GL_UNSIGNED_BYTE, data ); unbind(); } void upload(const texture_file& tex_file) const { upload(tex_file.texture().data(), tex_file.channels() == 4 ? GL_RGBA : GL_RGB, 0, 0, 0, tex_file.width(), tex_file.height()); } /** * Resizes the internal memory for the texture but does NOT resize the texture image stored */ inline void resize(int width, int height) { m_width = width; m_height = height; bind(); glTexStorage2D(textureBindType, 0, textureColorMode, m_width, m_height); unbind(); } }; struct gl_texture2D_array : public texture_gl { protected: int m_layers; public: gl_texture2D_array(int width, int height, int layers, GLint colorMode = GL_RGBA8): texture_gl(width, height, GL_TEXTURE_2D_ARRAY, colorMode), m_layers(layers) { bind(); // 6+ mipmaps is about where I stop noticing any difference (size is 4x4 pixels, so that makes sense) glTexStorage3D(textureBindType, 6, colorMode, width, height, layers); BLT_DEBUG("Creating 2D Texture Array with ID: %d", textureID); } void upload(void* data, int index, GLint dataColorMode = GL_RGBA, int level = 0, int x_offset = 0, int y_offset = 0, int sub_width = -1, int sub_height = -1) const { if (sub_width < 0) sub_width = m_width; if (sub_height < 0) sub_height = m_height; bind(); glTexSubImage3D( textureBindType, level, x_offset, y_offset, index, sub_width, sub_height, 1, dataColorMode, GL_UNSIGNED_BYTE, data ); unbind(); } }; class gl_buffer_texture : public texture_gl2D { private: public: // make sure a framebuffer is bound before constructing! gl_buffer_texture( int width, int height, GLint format = GL_RGB32F, GLint colorAttachment = GL_COLOR_ATTACHMENT0 ): texture_gl2D(width, height, format) { bind(); // no mipmaping and no interpolation to position textures! glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // glFramebufferTexture2D( GL_FRAMEBUFFER, colorAttachment, GL_TEXTURE_2D, this->textureID, 0 ); } }; } #endif //BLT_TEXTURE_H