#include "texture.h" #include "gstreamer_texture.h" #include "stb_image.h" #include struct TextureFormat { GLint internalFormat; GLenum format; GLenum type; }; // TextureFormatType => TextureFormat mapping static const TextureFormat textureFormatMap[] = { { GL_R8, GL_RED, GL_UNSIGNED_BYTE }, // R8 { GL_RG8, GL_RG, GL_UNSIGNED_BYTE }, // RG8 { GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE }, // RGB8 { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE }, // RGBA8 { GL_R16F, GL_RED, GL_HALF_FLOAT }, // R16F { GL_RG16F, GL_RG, GL_HALF_FLOAT }, // RG16F { GL_RGB16F, GL_RGB, GL_HALF_FLOAT }, // RGB16F { GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT }, // RGBA16F { GL_R32F, GL_RED, GL_FLOAT }, // R32F { GL_RG32F, GL_RG, GL_FLOAT }, // RG32F { GL_RGB32F, GL_RGB, GL_FLOAT }, // RGB32F { GL_RGBA32F, GL_RGBA, GL_FLOAT } // RGBA32F }; void Texture::Bind() const { glBindTexture(m_target, m_id); } void Texture::Unbind() const { glBindTexture(m_target, 0); } void Texture::GenerateMipmaps() const { glGenerateMipmap(m_target); } static GLenum ToGLFilter(TextureFilter filter) { switch (filter) { case TextureFilter::Nearest: return GL_NEAREST; case TextureFilter::Linear: return GL_LINEAR; case TextureFilter::NearestMipmapNearest: return GL_NEAREST_MIPMAP_NEAREST; case TextureFilter::LinearMipmapNearest: return GL_LINEAR_MIPMAP_NEAREST; case TextureFilter::NearestMipmapLinear: return GL_NEAREST_MIPMAP_LINEAR; case TextureFilter::LinearMipmapLinear: return GL_LINEAR_MIPMAP_LINEAR; } return GL_LINEAR; } static GLenum ToGLWrap(TextureWrap wrap) { switch (wrap) { case TextureWrap::Repeat: return GL_REPEAT; case TextureWrap::MirroredRepeat: return GL_MIRRORED_REPEAT; case TextureWrap::ClampToEdge: return GL_CLAMP_TO_EDGE; case TextureWrap::ClampToBorder: return GL_CLAMP_TO_BORDER; } return GL_REPEAT; } void Texture::SetFilter(TextureFilter min, TextureFilter mag) const { glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, ToGLFilter(min)); glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, ToGLFilter(mag)); } void Texture::SetWrap(TextureWrap s, TextureWrap t) const { glTexParameteri(m_target, GL_TEXTURE_WRAP_S, ToGLWrap(s)); glTexParameteri(m_target, GL_TEXTURE_WRAP_T, ToGLWrap(t)); } void Texture::SetWrap(TextureWrap s, TextureWrap t, TextureWrap r) const { glTexParameteri(m_target, GL_TEXTURE_WRAP_S, ToGLWrap(s)); glTexParameteri(m_target, GL_TEXTURE_WRAP_T, ToGLWrap(t)); glTexParameteri(m_target, GL_TEXTURE_WRAP_R, ToGLWrap(r)); } std::unique_ptr Texture::CreateTexture(uint32_t width, TextureFormatType format) { auto tex = std::make_unique(); tex->m_width = width; tex->Bind(); tex->LoadData(format, nullptr); tex->Unbind(); return tex; } std::unique_ptr Texture::CreateTexture(uint32_t width, uint32_t height, TextureFormatType format) { auto tex = std::make_unique(); tex->m_width = width; tex->m_height = height; tex->Bind(); tex->LoadData(format, nullptr); tex->Unbind(); return tex; } std::unique_ptr Texture::CreateTexture(uint32_t width, uint32_t height, uint32_t depth, TextureFormatType format) { auto tex = std::make_unique(); tex->m_width = width; tex->m_height = height; tex->m_depth = depth; tex->Bind(); tex->LoadData(format, nullptr); tex->Unbind(); return tex; } std::unique_ptr Texture::CreateCubeMap(uint32_t size, TextureFormatType format) { auto tex = std::make_unique(); tex->m_size = size; tex->Bind(); for (int i = 0; i < 6; ++i) { tex->LoadFaceData(static_cast(i), format, nullptr); } tex->Unbind(); return tex; } std::unique_ptr Texture::FromGStreamer(const std::string &pipeline) { return std::make_unique(pipeline); } std::unique_ptr Texture::FromFile(const std::string &filepath) { int width, height, channels; stbi_set_flip_vertically_on_load(true); stbi_uc* data = stbi_load(filepath.c_str(), &width, &height, &channels, 0); if (!data) { throw std::runtime_error("Failed to load texture: " + filepath); } TextureFormatType format; switch (channels) { case 1: format = TextureFormatType::R8; break; case 2: format = TextureFormatType::RG8; break; case 3: format = TextureFormatType::RGB8; break; case 4: format = TextureFormatType::RGBA8; break; default: stbi_image_free(data); throw std::runtime_error("Unsupported number of channels in texture: " + filepath); } auto tex = CreateTexture(width, height, format); tex->Bind(); tex->LoadData(format, data); tex->Unbind(); stbi_image_free(data); return tex; } Texture::Texture(GLenum target) : m_target(target) { glGenTextures(1, &m_id); glBindTexture(m_target, m_id); glTexParameteri(m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(m_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(m_target, 0); } Texture::~Texture() { if (m_id) { glDeleteTextures(1, &m_id); m_id = 0; } } void TextureCubeMap::LoadFaceData(Face face, TextureFormatType format, const void* data) const { const TextureFormat& fmt = textureFormatMap[static_cast(format)]; glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + static_cast(face), 0, fmt.internalFormat, m_size, m_size, 0, fmt.format, fmt.type, data ); } void TextureCubeMap::LoadFaceDataFromFile(Face face, const std::string &filepath) const { int width, height, channels; stbi_set_flip_vertically_on_load(false); // Cube maps typically expect unflipped images stbi_uc* data = stbi_load(filepath.c_str(), &width, &height, &channels, 0); if (!data) { throw std::runtime_error("Failed to load cube map face texture: " + filepath); } if (width != m_size || height != m_size) { stbi_image_free(data); throw std::runtime_error("Cube map face texture size mismatch: " + filepath); } TextureFormatType format; switch (channels) { case 1: format = TextureFormatType::R8; break; case 2: format = TextureFormatType::RG8; break; case 3: format = TextureFormatType::RGB8; break; case 4: format = TextureFormatType::RGBA8; break; default: stbi_image_free(data); throw std::runtime_error("Unsupported number of channels in cube map face texture: " + filepath); } LoadFaceData(face, format, data); stbi_image_free(data); } void Texture1D::LoadData(TextureFormatType format, const void *data) { const TextureFormat& fmt = textureFormatMap[static_cast(format)]; if (!m_hasInitialData) { // Initialize texture storage with null data to allocate GPU memory glTexImage1D( m_target, 0, fmt.internalFormat, m_width, 0, fmt.format, fmt.type, nullptr ); m_hasInitialData = true; } else { // Update existing texture data glTexSubImage1D( m_target, 0, 0, m_width, fmt.format, fmt.type, data ); } } void Texture2D::LoadData(TextureFormatType format, const void *data) { const TextureFormat& fmt = textureFormatMap[static_cast(format)]; if (!m_hasInitialData) { // Initialize texture storage and optionally upload data glTexImage2D( m_target, 0, fmt.internalFormat, m_width, m_height, 0, fmt.format, fmt.type, data ); m_hasInitialData = true; } else { // Update existing texture data glTexSubImage2D( m_target, 0, 0, 0, m_width, m_height, fmt.format, fmt.type, data ); } } void Texture3D::LoadData(TextureFormatType format, const void *data) { const TextureFormat& fmt = textureFormatMap[static_cast(format)]; if (!m_hasInitialData) { // Initialize texture storage with null data to allocate GPU memory glTexImage3D( m_target, 0, fmt.internalFormat, m_width, m_height, m_depth, 0, fmt.format, fmt.type, nullptr ); m_hasInitialData = true; } else { // Update existing texture data glTexSubImage3D( m_target, 0, 0, 0, 0, m_width, m_height, m_depth, fmt.format, fmt.type, data ); } }