#include "pch.h" #include "log.h" #include "texture.h" #include "util.h" #include "app.h" #include "legacy_gl_texture_dispatch.h" #include "renderer_gl/opengl_capabilities.h" #include namespace { void gen_opengl_framebuffers(std::uint32_t count, std::uint32_t* ids) noexcept { glGenFramebuffers(static_cast(count), reinterpret_cast(ids)); } void delete_opengl_framebuffers(std::uint32_t count, const std::uint32_t* ids) noexcept { glDeleteFramebuffers(static_cast(count), reinterpret_cast(ids)); } void query_opengl_integer(std::uint32_t name, std::int32_t* value) noexcept { glGetIntegerv(static_cast(name), reinterpret_cast(value)); } void bind_opengl_framebuffer(std::uint32_t target, std::uint32_t framebuffer) noexcept { glBindFramebuffer(static_cast(target), static_cast(framebuffer)); } void attach_opengl_framebuffer_texture_2d( std::uint32_t target, std::uint32_t attachment, std::uint32_t texture_target, std::uint32_t texture, std::int32_t level) noexcept { glFramebufferTexture2D( static_cast(target), static_cast(attachment), static_cast(texture_target), static_cast(texture), static_cast(level)); } std::uint32_t check_opengl_framebuffer_status(std::uint32_t target) noexcept { return static_cast(glCheckFramebufferStatus(static_cast(target))); } void read_opengl_pixels( std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height, std::uint32_t pixel_format, std::uint32_t component_type, void* pixels) noexcept { glReadPixels( static_cast(x), static_cast(y), static_cast(width), static_cast(height), static_cast(pixel_format), static_cast(component_type), pixels); } void gen_opengl_samplers(std::uint32_t count, std::uint32_t* ids) noexcept { #if USE_SAMPLER glGenSamplers(static_cast(count), reinterpret_cast(ids)); #else for (std::uint32_t i = 0U; i < count; ++i) { ids[i] = 0U; } #endif } void set_opengl_sampler_parameter_i( std::uint32_t sampler, std::uint32_t parameter, std::int32_t value) noexcept { #if USE_SAMPLER glSamplerParameteri( static_cast(sampler), static_cast(parameter), static_cast(value)); #else (void)sampler; (void)parameter; (void)value; #endif } void set_opengl_sampler_parameter_fv( std::uint32_t sampler, std::uint32_t parameter, const float* values) noexcept { #if USE_SAMPLER && !defined(__GLES__) glSamplerParameterfv( static_cast(sampler), static_cast(parameter), values); #else (void)sampler; (void)parameter; (void)values; #endif } void bind_opengl_sampler(std::uint32_t unit, std::uint32_t sampler) noexcept { #if USE_SAMPLER glBindSampler(static_cast(unit), static_cast(sampler)); #else (void)unit; (void)sampler; #endif } } std::map TextureManager::m_textures; std::array TextureCube::m_faces_map { static_cast(pp::renderer::gl::cube_face_texture_target(0U)), static_cast(pp::renderer::gl::cube_face_texture_target(1U)), static_cast(pp::renderer::gl::cube_face_texture_target(2U)), static_cast(pp::renderer::gl::cube_face_texture_target(3U)), static_cast(pp::renderer::gl::cube_face_texture_target(4U)), static_cast(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; const auto format = pp::renderer::gl::texture_format_for_channel_count(4U); const auto texture = pp::legacy::gl_texture::allocate_texture_cube( pp::renderer::gl::OpenGlTextureCubeAllocation { .resolution = m_resolution, .internal_format = static_cast(format.internal_format), .pixel_format = format.pixel_format, .component_type = pp::renderer::gl::unsigned_byte_component_type(), }, "TextureCube::create()"); if (texture == 0U) { return; } m_cubetex_id = static_cast(texture); }); return m_cubetex_id != 0; } void TextureCube::destroy() noexcept { if (m_cubetex_id) { App::I->render_task([f=m_faces, id=m_cubetex_id] { std::array texture_ids {}; for (std::size_t i = 0U; i < f.size(); ++i) texture_ids[i] = static_cast(f[i]); texture_ids[f.size()] = static_cast(id); pp::legacy::gl_texture::delete_texture_objects( texture_ids, "TextureCube::destroy()"); }); m_cubetex_id = 0; m_faces.fill(0); m_resolution = 0; } } void TextureCube::bind() const noexcept { assert(App::I->is_render_thread()); pp::legacy::gl_texture::bind_texture_cube( static_cast(m_cubetex_id), "TextureCube::bind()"); } 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([&] { const auto readback = pp::renderer::gl::readback_opengl_texture_2d( pp::renderer::gl::OpenGlTexture2DReadback { .texture_id = static_cast(m_tex), .width = m_width, .height = m_height, .format = pp::renderer::gl::rgba8_readback_format(), .pixels = ret.m_data.get(), }, pp::renderer::gl::OpenGlTexture2DReadbackDispatch { .bind_texture = pp::legacy::gl_texture::bind_opengl_texture, .gen_framebuffers = gen_opengl_framebuffers, .get_integer = query_opengl_integer, .bind_framebuffer = bind_opengl_framebuffer, .framebuffer_texture_2d = attach_opengl_framebuffer_texture_2d, .check_framebuffer_status = check_opengl_framebuffer_status, .read_pixels = read_opengl_pixels, .delete_framebuffers = delete_opengl_framebuffers, }); if (!readback.ok()) { LOG("Texture2D::get_image() failed because: %s", readback.status().message); return; } if (!readback.value().pixels_read) { LOG("Texture2D::get_image() failed because: %s", pp::renderer::gl::framebuffer_status_name(readback.value().framebuffer_status)); } }); 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(pp::renderer::gl::rgba8_internal_format()), static_cast(pp::renderer::gl::rgba_pixel_format())); } bool Texture2D::create(int width, int height, GLint internal_format) { return create( width, height, internal_format, static_cast(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(); const auto component_type = pp::renderer::gl::texture_upload_type_for_internal_format( static_cast(internal_format)); const auto texture = pp::legacy::gl_texture::allocate_texture_2d( pp::renderer::gl::OpenGlTexture2DAllocation { .width = width, .height = height, .internal_format = internal_format, .pixel_format = static_cast(format), .component_type = component_type, .data = data, }, "Texture2D::create()"); if (texture == 0U) { return; } m_width = width; m_height = height; m_format = format; m_iformat = internal_format; m_tex = static_cast(texture); }); return true; } bool Texture2D::create(const Image& img) { const auto format = pp::renderer::gl::texture_format_for_channel_count(static_cast(img.comp)); if (format.channel_count == 0U) return false; return create( img.width, img.height, static_cast(format.internal_format), static_cast(format.pixel_format), img.data()); } void Texture2D::create_mipmaps() { App::I->render_task([this] { if (!pp::legacy::gl_texture::generate_texture_2d_mipmaps( static_cast(m_tex), "Texture2D::create_mipmaps()")) { return; } 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] { pp::legacy::gl_texture::delete_texture_2d( static_cast(id), "Texture2D::destroy()"); }); m_tex = 0; } } void Texture2D::bind() const { assert(App::I->is_render_thread()); pp::legacy::gl_texture::bind_texture_2d( static_cast(m_tex), "Texture2D::bind()"); } void Texture2D::unbind() const { assert(App::I->is_render_thread()); pp::legacy::gl_texture::bind_texture_2d( 0U, "Texture2D::unbind()"); } void Texture2D::update(const uint8_t* data) { App::I->render_task([this, data] { pp::legacy::gl_texture::update_texture_2d( pp::renderer::gl::OpenGlTexture2DUpdate { .texture_id = static_cast(m_tex), .width = m_width, .height = m_height, .pixel_format = static_cast(m_format), .component_type = pp::renderer::gl::unsigned_byte_component_type(), .data = data, }, "Texture2D::update()"); }); } 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] { const auto parameters = pp::renderer::gl::sampler_parameters_for_filter_wrap( static_cast(filter), static_cast(wrap)); const auto sampler = pp::renderer::gl::create_opengl_sampler( parameters, pp::renderer::gl::OpenGlSamplerCreateDispatch { .gen_samplers = gen_opengl_samplers, .sampler_parameter_i = set_opengl_sampler_parameter_i, }); if (!sampler.ok()) { ret = false; LOG("Sampler::create() failed because: %s", sampler.status().message); return; } id = static_cast(sampler.value()); ret = true; }); return ret; } bool Sampler::create() { return create( static_cast(pp::renderer::gl::linear_texture_filter()), static_cast(pp::renderer::gl::clamp_to_edge_texture_wrap())); } bool Sampler::create(GLint filter) { return create(filter, static_cast(pp::renderer::gl::clamp_to_edge_texture_wrap())); } void Sampler::set(GLint filter, GLint wrap) { App::I->render_task([=] { const auto parameters = pp::renderer::gl::sampler_parameters_for_filter_wrap( static_cast(filter), static_cast(wrap)); const auto status = pp::renderer::gl::set_opengl_sampler_parameters( static_cast(id), parameters, pp::renderer::gl::OpenGlSamplerParameterDispatch { .sampler_parameter_i = set_opengl_sampler_parameter_i, }); if (!status.ok()) LOG("Sampler::set() failed because: %s", status.message); }); } void Sampler::set() { set( static_cast(pp::renderer::gl::linear_texture_filter()), static_cast(pp::renderer::gl::clamp_to_edge_texture_wrap())); } void Sampler::set(GLint filter) { set(filter, static_cast(pp::renderer::gl::clamp_to_edge_texture_wrap())); } void Sampler::set_filter(GLint filter_min, GLint filter_mag) { App::I->render_task([=] { const auto parameters = pp::renderer::gl::sampler_filter_parameters( static_cast(filter_min), static_cast(filter_mag)); const auto status = pp::renderer::gl::set_opengl_sampler_parameters( static_cast(id), parameters, pp::renderer::gl::OpenGlSamplerParameterDispatch { .sampler_parameter_i = set_opengl_sampler_parameter_i, }); if (!status.ok()) LOG("Sampler::set_filter() failed because: %s", status.message); }); } void Sampler::set_border(glm::vec4 rgba) { App::I->render_task([this, rgba] { const auto status = pp::renderer::gl::set_opengl_sampler_border_color( static_cast(id), pp::renderer::gl::sampler_border_color_parameter_name(), glm::value_ptr(rgba), pp::renderer::gl::OpenGlSamplerBorderDispatch { .sampler_parameter_fv = set_opengl_sampler_parameter_fv, }); if (!status.ok()) LOG("Sampler::set_border() failed because: %s", status.message); }); } void Sampler::bind(int unit) const { assert(App::I->is_render_thread()); current_unit = unit; const auto status = pp::renderer::gl::bind_opengl_sampler_object( static_cast(unit), static_cast(id), pp::renderer::gl::OpenGlSamplerBindDispatch { .bind_sampler = bind_opengl_sampler, }); if (!status.ok()) LOG("Sampler::bind() failed because: %s", status.message); } void Sampler::unbind() { assert(App::I->is_render_thread()); const auto status = pp::renderer::gl::bind_opengl_sampler_object( static_cast(current_unit), 0U, pp::renderer::gl::OpenGlSamplerBindDispatch { .bind_sampler = bind_opengl_sampler, }); if (!status.ok()) LOG("Sampler::unbind() failed because: %s", status.message); }