main
Brett 2023-12-17 19:37:11 -05:00
parent 6a5b06ac70
commit e04f4b555d
5 changed files with 8591 additions and 103 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -19,168 +19,190 @@
#ifndef BLT_TEXTURE_H #ifndef BLT_TEXTURE_H
#define BLT_TEXTURE_H #define BLT_TEXTURE_H
#ifdef __EMSCRIPTEN__ #include <blt/gfx/gl_includes.h>
#include <blt/gfx/stb/stb_image.h>
#else #include <blt/std/logging.h>
#include <string>
#endif #include <vector>
#include <utility>
namespace blt::gfx namespace blt::gfx
{ {
class file_texture { class texture_file;
class texture_data
{
friend texture_file;
private: private:
std::string m_Name; unsigned char* m_data = nullptr;
std::string m_Path; int m_width = 0, m_height = 0, m_channels = 0;
int width = 0, height = 0, channels = 0; public:
unsigned char* m_Data = nullptr; 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<unsigned char*>(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: public:
/** /**
* @param path path to the texture file * @param path path to the texture file
* @param name reference name for this texture. If empty the texture will use path as its identifier * @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 = ""): explicit texture_file(const std::string& path, const std::string& name = "");
m_Name(name.empty() ? path : name), m_Path(path) {}
static file_texture* load(file_texture*& texture) { texture_file& resize(int target_width, int target_height);
// we want to load every texture as if it has transparency,
// otherwise textures won't be correctly resized and loaded to the gpu texture_data& texture() const
constexpr int channel_count = 4; {
texture->m_Data = stbi_load( return m_texture;
texture->m_Path.c_str(), &texture->width, &texture->height,
&texture->channels, channel_count
);
texture->channels = channel_count;
return texture;
} }
static file_texture* resize( [[nodiscard]] int channels() const
file_texture* texture, int target_width, int target_height {
) { return m_texture.m_channels;
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 [[nodiscard]] int width() const
stbi_image_free(texture->m_Data); {
texture->m_Data = output_Data; return m_texture.m_width;
texture->width = target_width;
texture->height = target_height;
return texture;
} }
unsigned char* data() { [[nodiscard]] int height() const
return m_Data; {
return m_texture.m_height;
} }
[[nodiscard]] int getChannels() const { [[nodiscard]] const std::string& getName() const
return channels; {
} return m_name;
[[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 { struct texture_gl
{
protected: protected:
unsigned int textureID = 0; unsigned int textureID = 0;
GLint textureBindType; GLint textureBindType;
GLint textureColorMode; GLint textureColorMode;
int m_width, m_height; int m_width, m_height;
gl_texture( texture_gl(
int width, int height, GLint bind_type = GL_TEXTURE_2D, int width, int height, GLint bind_type = GL_TEXTURE_2D,
GLint color_mode = GL_RGBA GLint color_mode = GL_RGBA
): ):
textureBindType(bind_type), textureColorMode(color_mode), m_width(width), textureBindType(bind_type), textureColorMode(color_mode), m_width(width),
m_height(height) { m_height(height)
{
glGenTextures(1, &textureID); glGenTextures(1, &textureID);
} }
public: public:
inline void bind() const { inline void bind() const
{
glBindTexture(textureBindType, textureID); glBindTexture(textureBindType, textureID);
} }
inline void unbind() const { inline void unbind() const
{
glBindTexture(textureBindType, 0); glBindTexture(textureBindType, 0);
} }
void setDefaults() const { void setDefaults() const
{
bind(); bind();
glTexParameteri(textureBindType, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(textureBindType, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(textureBindType, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(textureBindType, GL_TEXTURE_WRAP_T, GL_REPEAT);
// nearest preserves the pixely look // nearest preserves the pixely look
glTexParameteri(textureBindType, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); glTexParameteri(textureBindType, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(textureBindType, GL_TEXTURE_MAG_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 // Anisotropy helps preserve textures at oblique angles
float a = 0; float a = 0;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &a); glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &a);
glTexParameterf(textureBindType, GL_TEXTURE_MAX_ANISOTROPY_EXT, a); glTexParameterf(textureBindType, GL_TEXTURE_MAX_ANISOTROPY_EXT, a);
#endif
unbind(); unbind();
} }
inline void generateMipmaps() const { 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 // it's a little inefficient binding and unbinding for these small calls, they really should be done in the constructor or data upload
bind(); bind();
glGenerateMipmap(textureBindType); glGenerateMipmap(textureBindType);
unbind(); unbind();
} }
[[nodiscard]] inline unsigned int getTextureID() const { [[nodiscard]] inline unsigned int getTextureID() const
{
return textureID; return textureID;
} }
virtual ~gl_texture() { virtual ~texture_gl()
{
glDeleteTextures(1, &textureID); glDeleteTextures(1, &textureID);
} }
}; };
struct gl_texture2D : public gl_texture { struct texture_gl2D : public texture_gl
{
public: public:
gl_texture2D(int width, int height, GLint colorMode = GL_RGBA): texture_gl2D(int width, int height, GLint colorMode = GL_RGBA):
gl_texture(width, height, GL_TEXTURE_2D, colorMode) { texture_gl(width, height, GL_TEXTURE_2D, colorMode)
{
bind(); bind();
// TODO:
const int MIPMAP_LEVELS = 4;
glTexStorage2D( glTexStorage2D(
textureBindType, std::stoi(fp::settings::get("MIPMAP_LEVELS")), colorMode, textureBindType, MIPMAP_LEVELS, colorMode,
width, height width, height
); );
} }
void upload( void upload(void* data, GLint dataColorMode = GL_RGBA, int level = 0, int x_offset = 0, int y_offset = 0, int sub_width = -1,
void* data, GLint dataColorMode = GL_RGBA, int level = 0, int x_offset = 0, int sub_height = -1) const
int y_offset = 0, int sub_width = -1, {
int sub_height = -1
) const {
if (sub_width < 0) if (sub_width < 0)
sub_width = m_width; sub_width = m_width;
if (sub_height < 0) if (sub_height < 0)
@ -193,14 +215,16 @@ namespace blt::gfx
unbind(); unbind();
} }
void upload(file_texture* texture) const { void upload(const texture_file& tex_file) const
upload(texture->data(), texture->getChannels() == 4 ? GL_RGBA : GL_RGB); {
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 * Resizes the internal memory for the texture but does NOT resize the texture image stored
*/ */
inline void resize(int width, int height) { inline void resize(int width, int height)
{
m_width = width; m_width = width;
m_height = height; m_height = height;
bind(); bind();
@ -209,23 +233,23 @@ namespace blt::gfx
} }
}; };
struct gl_texture2D_array : public gl_texture { struct gl_texture2D_array : public texture_gl
{
protected: protected:
int m_layers; int m_layers;
public: public:
gl_texture2D_array(int width, int height, int layers, GLint colorMode = GL_RGBA8): 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) { texture_gl(width, height, GL_TEXTURE_2D_ARRAY, colorMode), m_layers(layers)
{
bind(); bind();
// 6+ mipmaps is about where I stop noticing any difference (size is 4x4 pixels, so that makes sense) // 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); glTexStorage3D(textureBindType, 6, colorMode, width, height, layers);
BLT_DEBUG("Creating 2D Texture Array with ID: %d", textureID); BLT_DEBUG("Creating 2D Texture Array with ID: %d", textureID);
} }
void upload( 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,
void* data, int index, GLint dataColorMode = GL_RGBA, int level = 0, int sub_height = -1) const
int x_offset = 0, int y_offset = 0, int sub_width = -1, {
int sub_height = -1
) const {
if (sub_width < 0) if (sub_width < 0)
sub_width = m_width; sub_width = m_width;
if (sub_height < 0) if (sub_height < 0)
@ -239,7 +263,8 @@ namespace blt::gfx
} }
}; };
class gl_buffer_texture : public gl_texture2D { class gl_buffer_texture : public texture_gl2D
{
private: private:
public: public:
@ -247,7 +272,8 @@ namespace blt::gfx
gl_buffer_texture( gl_buffer_texture(
int width, int height, GLint format = GL_RGB32F, int width, int height, GLint format = GL_RGB32F,
GLint colorAttachment = GL_COLOR_ATTACHMENT0 GLint colorAttachment = GL_COLOR_ATTACHMENT0
): gl_texture2D(width, height, format) { ): texture_gl2D(width, height, format)
{
bind(); bind();
// no mipmaping and no interpolation to position textures! // no mipmaping and no interpolation to position textures!
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

View File

@ -15,3 +15,54 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_PERLIN_IMPLEMENTATION
#include <blt/gfx/stb/stb_image_write.h>
#include <blt/gfx/stb/stb_image_resize2.h>
#include <blt/gfx/stb/stb_perlin.h>
#include <blt/gfx/texture.h>
blt::gfx::texture_file::texture_file(const std::string& path, const std::string& name): m_name(name.empty() ? path : name), m_path(path)
{
m_texture.m_data = stbi_load(
m_path.c_str(),
reinterpret_cast<int*>(&m_texture.m_width), reinterpret_cast<int*>(&m_texture.m_height),
reinterpret_cast<int*>(&m_texture.m_channels),
0
);
}
blt::gfx::texture_file& blt::gfx::texture_file::resize(int target_width, int target_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<stbir_pixel_layout>(m_texture.channels())
))
{
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;
}