#include "pch.h" #include "log.h" #include "texture.h" #include "util.h" #include "app.h" std::map TextureManager::m_textures; std::array TextureCube::m_faces_map { GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, // front GL_TEXTURE_CUBE_MAP_NEGATIVE_X, // right GL_TEXTURE_CUBE_MAP_POSITIVE_Z, // back GL_TEXTURE_CUBE_MAP_POSITIVE_X, // left GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, // top GL_TEXTURE_CUBE_MAP_POSITIVE_Y, // bottom }; TextureCube::TextureCube(TextureCube&& other) noexcept { other.m_faces = std::move(other.m_faces); m_cubetex_id = other.m_cubetex_id; other.m_cubetex_id = 0; m_resolution = other.m_resolution; other.m_resolution = 0; } TextureCube::~TextureCube() { destroy(); } void TextureCube::operator=(TextureCube&& other) noexcept { other.m_faces = std::move(other.m_faces); m_cubetex_id = other.m_cubetex_id; other.m_cubetex_id = 0; m_resolution = other.m_resolution; other.m_resolution = 0; } bool TextureCube::create(int resolution) noexcept { App::I->render_task([this, resolution] { destroy(); m_resolution = resolution; glGenTextures(1, &m_cubetex_id); if (!m_cubetex_id) return; glBindTexture(GL_TEXTURE_CUBE_MAP, m_cubetex_id); for (GLuint i = 0; i < 6; i++) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA8, m_resolution, m_resolution, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); } }); return m_cubetex_id != 0; } void TextureCube::destroy() noexcept { if (m_cubetex_id) { App::I->render_task([f=m_faces, id=m_cubetex_id] { glDeleteTextures(f.size(), f.data()); glDeleteTextures(1, &id); }); m_cubetex_id = 0; m_faces.fill(0); m_resolution = 0; } } void TextureCube::bind() const noexcept { assert(App::I->is_render_thread()); glBindTexture(GL_TEXTURE_CUBE_MAP, m_cubetex_id); } bool TextureManager::load(const char* path, bool generate_mipmaps) { uint16_t id = const_hash(path); auto t = m_textures.find(id); if (t == m_textures.end() || !m_textures[id].ready()) { if (!m_textures[id].load(path)) return false; } if (generate_mipmaps && !m_textures[id].has_mips) m_textures[id].create_mipmaps(); return true; } void TextureManager::assign(uint16_t id, GLuint tex, int w/* = -1*/, int h/* = -1*/, GLuint internal_format/* = GL_RGBA8*/, GLuint format/* = GL_RGBA*/) { m_textures[id].assign(tex, w, h, internal_format, format); } Texture2D& TextureManager::get(uint16_t id) { return m_textures[id]; } void TextureManager::invalidate() { for (auto& t : m_textures) { t.second.destroy(); } m_textures.clear(); } Image Texture2D::get_image() const noexcept { Image ret; ret.create(m_width, m_height); App::I->render_task([&] { bind(); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, ret.m_data.get()); }); return ret; } bool Texture2D::create(int width, int height, GLint internal_format, GLint format, const uint8_t* data) { App::I->render_task([=] { destroy(); m_width = width; m_height = height; m_format = format; m_iformat = internal_format; glGenTextures(1, &m_tex); //LOG("TEX create %d", m_tex); bind(); auto ifmt = GL_UNSIGNED_BYTE; if (internal_format == GL_RGBA32F) ifmt = GL_FLOAT; if (internal_format == GL_RGBA16F) ifmt = GL_HALF_FLOAT; glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, ifmt, data); unbind(); }); return true; } bool Texture2D::create(const Image& img) { static GLint formats[] = { GL_RED, GL_RG, GL_RGB, GL_RGBA }; static GLint iformats[] = { GL_R8, GL_RG8, GL_RGB8, GL_RGBA8 }; return create(img.width, img.height, iformats[img.comp - 1], formats[img.comp - 1], img.data()); } void Texture2D::create_mipmaps() { App::I->render_task([this] { bind(); glGenerateMipmap(GL_TEXTURE_2D); unbind(); has_mips = true; }); } void Texture2D::assign(GLuint tex, int w/* = -1*/, int h/* = -1*/, GLuint internal_format/* = GL_RGBA8*/, GLuint format/* = GL_RGBA*/) { m_tex = tex; m_width = w; m_height = h; m_format = format; m_iformat = internal_format; } bool Texture2D::load(std::string filename) { LOG("load texture %s", filename.c_str()); Image img; if (!img.load(filename)) return false; return create(img); } bool Texture2D::load_file(std::string filename) { LOG("load texture %s", filename.c_str()); Image img; if (!img.load_file(filename)) return false; return create(img); } void Texture2D::destroy() { if (m_tex) { App::I->render_task_async([id = m_tex] { glDeleteTextures(1, &id); }); m_tex = 0; } } void Texture2D::bind() const { assert(App::I->is_render_thread()); glBindTexture(GL_TEXTURE_2D, m_tex); } void Texture2D::unbind() const { assert(App::I->is_render_thread()); glBindTexture(GL_TEXTURE_2D, 0); } void Texture2D::update(const uint8_t* data) { App::I->render_task([this, data] { bind(); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, m_format, GL_UNSIGNED_BYTE, data); }); } glm::vec2 Texture2D::size() const { return { m_width, m_height }; } Texture2D::~Texture2D() { if (auto_destroy) { LOG("Texture2D auto destroy"); destroy(); } } bool Sampler::create(GLint filter /*= GL_LINEAR*/, GLint wrap /*= GL_CLAMP_TO_EDGE*/) { bool ret = false; App::I->render_task([this, &ret, filter, wrap] { #if USE_SAMPLER glGenSamplers(1, &id); #endif // USE_SAMPLER if (id == 0) { ret = false; return; } set(filter, wrap); ret = true; }); return ret; } void Sampler::set(GLint filter /*= GL_LINEAR*/, GLint wrap /*= GL_CLAMP_TO_EDGE*/) { App::I->render_task([=] { #if USE_SAMPLER glSamplerParameteri(id, GL_TEXTURE_WRAP_S, wrap); glSamplerParameteri(id, GL_TEXTURE_WRAP_T, wrap); glSamplerParameteri(id, GL_TEXTURE_WRAP_R, wrap); glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, filter); glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, filter); #endif // USE_SAMPLER }); } void Sampler::set_filter(GLint filter_min, GLint filter_mag) { App::I->render_task([=] { #if USE_SAMPLER glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, filter_min); glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, filter_mag); #endif // USE_SAMPLER }); } void Sampler::set_border(glm::vec4 rgba) { App::I->render_task([this, rgba] { #if USE_SAMPLER && !defined(__IOS__) && !defined(__ANDROID__) glSamplerParameterfv(id, GL_TEXTURE_BORDER_COLOR, glm::value_ptr(rgba)); #endif // USE_SAMPLER }); } void Sampler::bind(int unit) const { assert(App::I->is_render_thread()); current_unit = unit; #if USE_SAMPLER glBindSampler(unit, id); #endif // USE_SAMPLER } void Sampler::unbind() { assert(App::I->is_render_thread()); #if USE_SAMPLER glBindSampler(current_unit, 0); #endif // USE_SAMPLER }