Files
panopainter/src/texture.cpp

480 lines
12 KiB
C++

#include "pch.h"
#include "log.h"
#include "texture.h"
#include "util.h"
#include "app.h"
#include "renderer_gl/opengl_capabilities.h"
#include <cstdint>
namespace {
[[nodiscard]] GLenum texture_2d_target() noexcept
{
return static_cast<GLenum>(pp::renderer::gl::texture_2d_target());
}
[[nodiscard]] GLenum texture_cube_map_target() noexcept
{
return static_cast<GLenum>(pp::renderer::gl::texture_cube_map_target());
}
[[nodiscard]] GLenum framebuffer_target() noexcept
{
return static_cast<GLenum>(pp::renderer::gl::framebuffer_target());
}
[[nodiscard]] GLenum draw_framebuffer_binding_query() noexcept
{
return static_cast<GLenum>(pp::renderer::gl::draw_framebuffer_binding_query());
}
}
std::map<uint16_t, Texture2D> TextureManager::m_textures;
std::array<int, 6> TextureCube::m_faces_map {
static_cast<int>(pp::renderer::gl::cube_face_texture_target(0U)),
static_cast<int>(pp::renderer::gl::cube_face_texture_target(1U)),
static_cast<int>(pp::renderer::gl::cube_face_texture_target(2U)),
static_cast<int>(pp::renderer::gl::cube_face_texture_target(3U)),
static_cast<int>(pp::renderer::gl::cube_face_texture_target(4U)),
static_cast<int>(pp::renderer::gl::cube_face_texture_target(5U)),
};
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(texture_cube_map_target(), m_cubetex_id);
const auto format = pp::renderer::gl::texture_format_for_channel_count(4U);
const auto component_type = static_cast<GLenum>(pp::renderer::gl::unsigned_byte_component_type());
for (GLuint i = 0; i < 6; i++)
{
glTexImage2D(
static_cast<GLenum>(pp::renderer::gl::cube_map_allocation_face_texture_target(i)),
0,
static_cast<GLint>(format.internal_format),
m_resolution,
m_resolution,
0,
static_cast<GLenum>(format.pixel_format),
component_type,
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(static_cast<GLsizei>(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(texture_cube_map_target(), 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, int h, GLuint internal_format, GLuint format)
{
m_textures[id].assign(tex, w, h, internal_format, format);
}
void TextureManager::assign(uint16_t id, GLuint tex, int w, int h)
{
assign(
id,
tex,
w,
h,
pp::renderer::gl::rgba8_internal_format(),
pp::renderer::gl::rgba_pixel_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();
GLuint fboID;
glGenFramebuffers(1, &fboID);
if (fboID == 0)
return;
GLint oldFboID;
glGetIntegerv(draw_framebuffer_binding_query(), &oldFboID);
glBindFramebuffer(framebuffer_target(), fboID);
glFramebufferTexture2D(
framebuffer_target(),
static_cast<GLenum>(pp::renderer::gl::framebuffer_color_attachment()),
texture_2d_target(),
m_tex,
0);
GLenum status = glCheckFramebufferStatus(framebuffer_target());
if (status == static_cast<GLenum>(pp::renderer::gl::framebuffer_complete_status()))
{
const auto readback = pp::renderer::gl::rgba8_readback_format();
glReadPixels(
0,
0,
m_width,
m_height,
static_cast<GLenum>(readback.pixel_format),
static_cast<GLenum>(readback.component_type),
ret.m_data.get());
}
else
{
LOG("Texture2D::get_image() failed because: %s",
pp::renderer::gl::framebuffer_status_name(static_cast<std::uint32_t>(status)));
}
glBindFramebuffer(framebuffer_target(), oldFboID);
glDeleteFramebuffers(1, &fboID);
});
return ret;
}
Texture2D::Texture2D(Texture2D&& other) noexcept
{
m_tex = other.m_tex;
m_width = other.m_width;
m_height = other.m_height;
m_format = other.m_format;
m_iformat = other.m_iformat;
has_mips = other.has_mips;
other.m_tex = false;
}
void Texture2D::operator=(Texture2D&& other) noexcept
{
m_tex = other.m_tex;
m_width = other.m_width;
m_height = other.m_height;
m_format = other.m_format;
m_iformat = other.m_iformat;
has_mips = other.has_mips;
other.m_tex = false;
}
bool Texture2D::create(int width, int height)
{
return create(
width,
height,
static_cast<GLint>(pp::renderer::gl::rgba8_internal_format()),
static_cast<GLint>(pp::renderer::gl::rgba_pixel_format()));
}
bool Texture2D::create(int width, int height, GLint internal_format)
{
return create(
width,
height,
internal_format,
static_cast<GLint>(pp::renderer::gl::rgba_pixel_format()));
}
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();
const auto ifmt = static_cast<GLenum>(
pp::renderer::gl::texture_upload_type_for_internal_format(static_cast<std::uint32_t>(internal_format)));
glTexImage2D(texture_2d_target(), 0, internal_format, width, height, 0, format, ifmt, data);
unbind();
});
return true;
}
bool Texture2D::create(const Image& img)
{
const auto format = pp::renderer::gl::texture_format_for_channel_count(static_cast<std::uint32_t>(img.comp));
if (format.channel_count == 0U)
return false;
return create(
img.width,
img.height,
static_cast<GLint>(format.internal_format),
static_cast<GLint>(format.pixel_format),
img.data());
}
void Texture2D::create_mipmaps()
{
App::I->render_task([this]
{
bind();
glGenerateMipmap(texture_2d_target());
unbind();
has_mips = true;
});
}
void Texture2D::assign(GLuint tex, int w, int h, GLuint internal_format, GLuint format)
{
m_tex = tex;
m_width = w;
m_height = h;
m_format = format;
m_iformat = internal_format;
}
void Texture2D::assign(GLuint tex, int w, int h)
{
assign(
tex,
w,
h,
pp::renderer::gl::rgba8_internal_format(),
pp::renderer::gl::rgba_pixel_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(texture_2d_target(), m_tex);
}
void Texture2D::unbind() const
{
assert(App::I->is_render_thread());
glBindTexture(texture_2d_target(), 0);
}
void Texture2D::update(const uint8_t* data)
{
App::I->render_task([this, data]
{
bind();
glTexSubImage2D(
texture_2d_target(),
0,
0,
0,
m_width,
m_height,
m_format,
static_cast<GLenum>(pp::renderer::gl::unsigned_byte_component_type()),
data);
});
}
glm::vec2 Texture2D::size() const
{
return { m_width, m_height };
}
Texture2D::~Texture2D()
{
destroy();
}
bool Sampler::create(GLint filter, GLint wrap)
{
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;
}
bool Sampler::create()
{
return create(
static_cast<GLint>(pp::renderer::gl::linear_texture_filter()),
static_cast<GLint>(pp::renderer::gl::clamp_to_edge_texture_wrap()));
}
bool Sampler::create(GLint filter)
{
return create(filter, static_cast<GLint>(pp::renderer::gl::clamp_to_edge_texture_wrap()));
}
void Sampler::set(GLint filter, GLint wrap)
{
App::I->render_task([=]
{
#if USE_SAMPLER
for (const auto parameter : pp::renderer::gl::sampler_parameters_for_filter_wrap(
static_cast<std::uint32_t>(filter),
static_cast<std::uint32_t>(wrap)))
{
glSamplerParameteri(id, static_cast<GLenum>(parameter.name), static_cast<GLint>(parameter.value));
}
#endif // USE_SAMPLER
});
}
void Sampler::set()
{
set(
static_cast<GLint>(pp::renderer::gl::linear_texture_filter()),
static_cast<GLint>(pp::renderer::gl::clamp_to_edge_texture_wrap()));
}
void Sampler::set(GLint filter)
{
set(filter, static_cast<GLint>(pp::renderer::gl::clamp_to_edge_texture_wrap()));
}
void Sampler::set_filter(GLint filter_min, GLint filter_mag)
{
App::I->render_task([=]
{
#if USE_SAMPLER
for (const auto parameter : pp::renderer::gl::sampler_filter_parameters(
static_cast<std::uint32_t>(filter_min),
static_cast<std::uint32_t>(filter_mag)))
{
glSamplerParameteri(id, static_cast<GLenum>(parameter.name), static_cast<GLint>(parameter.value));
}
#endif // USE_SAMPLER
});
}
void Sampler::set_border(glm::vec4 rgba)
{
App::I->render_task([this, rgba]
{
#if USE_SAMPLER && !defined(__GLES__)
glSamplerParameterfv(
id,
static_cast<GLenum>(pp::renderer::gl::sampler_border_color_parameter_name()),
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
}