/* * * 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 #ifdef __EMSCRIPTEN__ #else #endif namespace blt::gfx { class file_texture { private: std::string m_Name; std::string m_Path; int width = 0, height = 0, channels = 0; unsigned char* m_Data = nullptr; 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 file_texture(const std::string& path, const std::string& name = ""): m_Name(name.empty() ? path : name), m_Path(path) {} static file_texture* load(file_texture*& texture) { // we want to load every texture as if it has transparency, // otherwise textures won't be correctly resized and loaded to the gpu constexpr int channel_count = 4; texture->m_Data = stbi_load( texture->m_Path.c_str(), &texture->width, &texture->height, &texture->channels, channel_count ); texture->channels = channel_count; return texture; } static file_texture* resize( file_texture* texture, int target_width, int target_height ) { if (target_width == texture->width && target_height == texture->height) return texture; // since we will be replacing the loaded data pointer, is it wise to use the allocator // that matches with what stb image uses, which is malloc, since we unload with stbi_image_free -> (free) auto* output_Data = (unsigned char*) malloc( target_width * target_height * texture->channels ); // resize the texture if (stbir_resize_uint8( // input texture->m_Data, texture->width, texture->height, 0, // output output_Data, target_width, target_height, 0, // channels texture->channels )) { BLT_WARN("Error resizing block texture image!"); } // free up the old data stbi_image_free(texture->m_Data); texture->m_Data = output_Data; texture->width = target_width; texture->height = target_height; return texture; } unsigned char* data() { return m_Data; } [[nodiscard]] int getChannels() const { return channels; } [[nodiscard]] int getWidth() const { return width; } [[nodiscard]] int getHeight() const { return height; } [[nodiscard]] const std::string& getName() { return m_Name; } ~file_texture() { stbi_image_free(m_Data); } }; struct gl_texture { protected: unsigned int textureID = 0; GLint textureBindType; GLint textureColorMode; int m_width, m_height; gl_texture( 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); // 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); 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 ~gl_texture() { glDeleteTextures(1, &textureID); } }; struct gl_texture2D : public gl_texture { public: gl_texture2D(int width, int height, GLint colorMode = GL_RGBA): gl_texture(width, height, GL_TEXTURE_2D, colorMode) { bind(); glTexStorage2D( textureBindType, std::stoi(fp::settings::get("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(file_texture* texture) const { upload(texture->data(), texture->getChannels() == 4 ? GL_RGBA : GL_RGB); } /** * 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 gl_texture { protected: int m_layers; public: gl_texture2D_array(int width, int height, int layers, GLint colorMode = GL_RGBA8): gl_texture(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 gl_texture2D { 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 ): gl_texture2D(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