/* * * 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 . */ #define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_RESIZE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_PERLIN_IMPLEMENTATION #include #if defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warray-bounds" #include #pragma GCC diagnostic pop #else #include #endif #include #include #include #include struct format_t { GLenum format; GLenum type; }; blt::hashmap_t make_format_table() { blt::hashmap_t map; map[GL_R8] = {GL_RED, GL_UNSIGNED_BYTE}; map[GL_R8_SNORM] = {GL_RED, GL_BYTE}; map[GL_R16F] = {GL_RED, GL_FLOAT}; map[GL_R32F] = {GL_RED, GL_FLOAT}; map[GL_R8UI] = {GL_RED_INTEGER, GL_UNSIGNED_BYTE}; map[GL_R8I] = {GL_RED_INTEGER, GL_BYTE}; map[GL_R16UI] = {GL_RED_INTEGER, GL_UNSIGNED_SHORT}; map[GL_R16I] = {GL_RED_INTEGER, GL_SHORT}; map[GL_R32UI] = {GL_RED_INTEGER, GL_UNSIGNED_INT}; map[GL_R32I] = {GL_RED_INTEGER, GL_INT}; map[GL_RG8] = {GL_RG, GL_UNSIGNED_BYTE}; map[GL_RG8_SNORM] = {GL_RG, GL_BYTE}; map[GL_RG16F] = {GL_RG, GL_FLOAT}; map[GL_RG32F] = {GL_RG, GL_FLOAT}; map[GL_RG8UI] = {GL_RG_INTEGER, GL_UNSIGNED_BYTE}; map[GL_RG8I] = {GL_RG_INTEGER, GL_BYTE}; map[GL_RG16UI] = {GL_RG_INTEGER, GL_UNSIGNED_SHORT}; map[GL_RG16I] = {GL_RG_INTEGER, GL_SHORT}; map[GL_RG32UI] = {GL_RG_INTEGER, GL_UNSIGNED_INT}; map[GL_RG32I] = {GL_RG_INTEGER, GL_INT}; map[GL_RGB8] = {GL_RGB, GL_UNSIGNED_BYTE}; map[GL_SRGB8] = {GL_RGB, GL_UNSIGNED_BYTE}; map[GL_RGB565] = {GL_RGB, GL_UNSIGNED_BYTE}; map[GL_RGB8_SNORM] = {GL_RGB, GL_BYTE}; map[GL_R11F_G11F_B10F] = {GL_RGB, GL_FLOAT}; map[GL_RGB9_E5] = {GL_RGB, GL_FLOAT}; map[GL_RGB16F] = {GL_RGB, GL_FLOAT}; map[GL_RGB32F] = {GL_RGB, GL_FLOAT}; map[GL_RGB8UI] = {GL_RGB_INTEGER, GL_UNSIGNED_BYTE}; map[GL_RGB8I] = {GL_RGB_INTEGER, GL_BYTE}; map[GL_RGB16UI] = {GL_RGB_INTEGER, GL_UNSIGNED_SHORT}; map[GL_RGB16I] = {GL_RGB_INTEGER, GL_RGB16I}; map[GL_RGB32UI] = {GL_RGB_INTEGER, GL_UNSIGNED_INT}; map[GL_RGB32I] = {GL_RGB_INTEGER, GL_INT}; map[GL_RGBA8] = {GL_RGBA, GL_UNSIGNED_BYTE}; map[GL_SRGB8_ALPHA8] = {GL_RGBA, GL_UNSIGNED_BYTE}; map[GL_RGBA8_SNORM] = {GL_RGBA, GL_BYTE}; map[GL_RGB5_A1] = {GL_RGBA, GL_UNSIGNED_BYTE}; map[GL_RGBA4] = {GL_RGBA, GL_UNSIGNED_BYTE}; map[GL_RGB10_A2] = {GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}; map[GL_RGBA16F] = {GL_RGBA, GL_FLOAT}; map[GL_RGBA32F] = {GL_RGBA, GL_FLOAT}; map[GL_RGBA8UI] = {GL_RGBA_INTEGER, GL_UNSIGNED_BYTE}; map[GL_RGBA8I] = {GL_RGBA_INTEGER, GL_BYTE}; map[GL_RGB10_A2UI] = {GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV}; map[GL_RGBA16UI] = {GL_RGBA_INTEGER, GL_UNSIGNED_SHORT}; map[GL_RGBA16I] = {GL_RGBA_INTEGER, GL_SHORT}; map[GL_RGBA32I] = {GL_RGBA_INTEGER, GL_INT}; map[GL_RGBA32UI] = {GL_RGBA_INTEGER, GL_UNSIGNED_INT}; map[GL_DEPTH_COMPONENT16] = {GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; map[GL_DEPTH_COMPONENT24] = {GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}; map[GL_DEPTH_COMPONENT32F] = {GL_DEPTH_COMPONENT, GL_FLOAT}; map[GL_DEPTH24_STENCIL8] = {GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}; map[GL_DEPTH32F_STENCIL8] = {GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV}; map[GL_STENCIL_INDEX8] = {GL_STENCIL_INDEX, GL_UNSIGNED_BYTE}; return map; } blt::hashmap_t internal_to_texture = make_format_table(); blt::gfx::texture_file::texture_file(const std::string& path, const std::string& name, blt::i32 desired_channels): m_name(name.empty() ? path : name), m_path(path), desired_channels(desired_channels) { stbi_set_flip_vertically_on_load(true); m_texture.m_data = stbi_load( m_path.c_str(), &m_texture.m_width, &m_texture.m_height, &m_texture.m_channels, desired_channels); if (desired_channels != 0) m_texture.m_channels = desired_channels; } blt::gfx::texture_file& blt::gfx::texture_file::resize(int target_width, int target_height) { if (target_width == 0) target_width = m_texture.width(); if (target_height == 0) target_height = m_texture.height(); if (target_width == m_texture.width() && target_height == m_texture.height()) return *this; // 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 * m_texture.channels() ); // resize the texture if (stbir_resize_uint8_linear( // input m_texture.m_data, m_texture.width(), m_texture.height(), 0, // output output_Data, target_width, target_height, 0, // channels static_cast(m_texture.channels()) ) == nullptr) { BLT_WARN("Error resizing block texture image!"); } // free up the old data stbi_image_free(m_texture.m_data); m_texture.m_data = output_Data; m_texture.m_width = target_width; m_texture.m_height = target_height; return *this; } void blt::gfx::texture_gl::setDefaults(GLint type, GLint wrap_type) const { glTexParameteri(textureBindType, GL_TEXTURE_WRAP_S, wrap_type); glTexParameteri(textureBindType, GL_TEXTURE_WRAP_T, wrap_type); // nearest preserves the pixely look #ifdef __EMSCRIPTEN__ glTexParameteri(textureBindType, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(textureBindType, GL_TEXTURE_MAG_FILTER, GL_NEAREST); #else // glTexParameteri(textureBindType, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); // glTexParameteri(textureBindType, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_LINEAR); glTexParameteri(textureBindType, GL_TEXTURE_MIN_FILTER, type); glTexParameteri(textureBindType, GL_TEXTURE_MAG_FILTER, type); #endif #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 } void blt::gfx::texture_gl2D::resize(int width, int height) { m_width = width; m_height = height; bind(); //glTexStorage2D(textureBindType, 4, textureColorMode, m_width, m_height); auto format = internal_to_texture[textureColorMode]; glTexImage2D(textureBindType, 0, textureColorMode, m_width, m_height, 0, format.format, format.type, nullptr); unbind(); } blt::gfx::texture_gl2D::texture_gl2D(int width, int height, GLint colorMode): texture_gl(width, height, GL_TEXTURE_2D, colorMode) { bind(); setDefaults(GL_LINEAR, GL_CLAMP_TO_EDGE); resize(width, height); //glTexStorage2D(textureBindType, 4, colorMode, width, height); } blt::gfx::texture_gl2D::texture_gl2D(const blt::gfx::texture_data& data): texture_gl(data.width(), data.height(), GL_TEXTURE_2D, data.channels() == 4 ? GL_RGBA8 : GL_RGB8) { bind(); setDefaults(GL_NEAREST); glTexStorage2D(textureBindType, 4, textureColorMode, data.width(), data.height()); upload((void*) data.data(), data.channels() == 4 ? GL_RGBA : GL_RGB, 0, 0, 0, data.width(), data.height()); bind(); generateMipmaps(); unbind(); } void blt::gfx::texture_gl2D::upload(void* data, GLint dataColorMode, int level, int x_offset, int y_offset, int sub_width, int sub_height, GLint dataMode) 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, dataMode, data); generateMipmaps(); unbind(); } void blt::gfx::texture_gl2D::upload(const blt::gfx::texture_data& file_data) const { upload((void*) file_data.data(), file_data.channels() == 4 ? GL_RGBA : GL_RGB, 0, 0, 0, file_data.width(), file_data.height()); } void blt::gfx::texture_gl2D::upload(void* data, int width, int height, GLint dataColorMode, GLint dataMode) { bind(); glTexImage2D(textureBindType, 0, textureColorMode, width, height, 0, dataColorMode, dataMode, data); generateMipmaps(); unbind(); } void blt::gfx::gl_texture2D_array::upload(void* data, int index, GLint dataColorMode, int level, int x_offset, int y_offset, int sub_width, int sub_height) 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); generateMipmaps(); unbind(); } blt::gfx::texture_gl2D_multisample::texture_gl2D_multisample(int width, int height, blt::i32 samples, GLint colorMode): texture_gl(width, height, GL_TEXTURE_2D_MULTISAMPLE, colorMode), samples(samples) { bind(); setDefaults(GL_LINEAR, GL_CLAMP_TO_EDGE); //glTexImage2DMultisample(textureBindType, samples, colorMode, width, height, GL_TRUE); glTexStorage2DMultisample(textureBindType, samples, colorMode, width, height, GL_TRUE); } void blt::gfx::texture_gl2D_multisample::resize(int width, int height) { if (m_width == width && m_height == height) return; m_width = width; m_height = height; bind(); //glTexImage2DMultisample(textureBindType, samples, textureColorMode, width, height, GL_TRUE); glTexStorage2DMultisample(textureBindType, samples, textureColorMode, width, height, GL_TRUE); } blt::gfx::gl_texture2D_array::gl_texture2D_array(int width, int height, int layers, GLint colorMode): texture_gl(width, height, GL_TEXTURE_2D_ARRAY, colorMode), m_layers(layers) { bind(); setDefaults(GL_LINEAR); // 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); }