#include #include #include #define STB_IMAGE_IMPLEMENTATION #include #include #include #include #include namespace extra2d { // ============================================================================ // KTX 文件头结构 // ============================================================================ #pragma pack(push, 1) struct KTXHeader { uint8_t identifier[12]; uint32_t endianness; uint32_t glType; uint32_t glTypeSize; uint32_t glFormat; uint32_t glInternalFormat; uint32_t glBaseInternalFormat; uint32_t pixelWidth; uint32_t pixelHeight; uint32_t pixelDepth; uint32_t numberOfArrayElements; uint32_t numberOfFaces; uint32_t numberOfMipmapLevels; uint32_t bytesOfKeyValueData; }; #pragma pack(pop) // KTX 文件标识符 static const uint8_t KTX_IDENTIFIER[12] = {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A}; // ============================================================================ // DDS 文件头结构 // ============================================================================ #pragma pack(push, 1) struct DDSPixelFormat { uint32_t size; uint32_t flags; uint32_t fourCC; uint32_t rgbBitCount; uint32_t rBitMask; uint32_t gBitMask; uint32_t bBitMask; uint32_t aBitMask; }; struct DDSHeader { uint32_t magic; uint32_t size; uint32_t flags; uint32_t height; uint32_t width; uint32_t pitchOrLinearSize; uint32_t depth; uint32_t mipMapCount; uint32_t reserved1[11]; DDSPixelFormat pixelFormat; uint32_t caps; uint32_t caps2; uint32_t caps3; uint32_t caps4; uint32_t reserved2; }; struct DDSHeaderDXT10 { uint32_t dxgiFormat; uint32_t resourceDimension; uint32_t miscFlag; uint32_t arraySize; uint32_t miscFlags2; }; #pragma pack(pop) static constexpr uint32_t DDS_MAGIC = 0x20534444; // "DDS " static constexpr uint32_t DDPF_FOURCC = 0x04; /** * @brief 生成四字符代码(FourCC) * @param a 第一个字符 * @param b 第二个字符 * @param c 第三个字符 * @param d 第四个字符 * @return 组合后的32位无符号整数 */ static uint32_t makeFourCC(char a, char b, char c, char d) { return static_cast(a) | (static_cast(b) << 8) | (static_cast(c) << 16) | (static_cast(d) << 24); } // ============================================================================ // GLTexture 实现 // ============================================================================ /** * @brief 从像素数据构造纹理对象 * @param width 纹理宽度(像素) * @param height 纹理高度(像素) * @param pixels 像素数据指针,可为nullptr创建空纹理 * @param channels 颜色通道数(1=R, 3=RGB, 4=RGBA) */ GLTexture::GLTexture(int width, int height, const uint8_t *pixels, int channels) : textureID_(0), width_(width), height_(height), channels_(channels), format_(PixelFormat::RGBA8), dataSize_(0) { // 保存像素数据用于生成遮罩 if (pixels) { pixelData_.resize(width * height * channels); std::memcpy(pixelData_.data(), pixels, pixelData_.size()); } createTexture(pixels); } /** * @brief 从文件路径构造纹理对象 * @param filepath 纹理文件路径,支持普通图片格式和压缩格式(KTX/DDS) */ GLTexture::GLTexture(const std::string &filepath) : textureID_(0), width_(0), height_(0), channels_(0), format_(PixelFormat::RGBA8), dataSize_(0) { // 检查是否为压缩纹理格式 std::string ext = filepath.substr(filepath.find_last_of('.') + 1); if (ext == "ktx" || ext == "KTX") { loadCompressed(filepath); return; } if (ext == "dds" || ext == "DDS") { loadCompressed(filepath); return; } // 不翻转图片,保持原始方向 stbi_set_flip_vertically_on_load(false); uint8_t *data = stbi_load(filepath.c_str(), &width_, &height_, &channels_, 0); if (data) { // 保存像素数据用于生成遮罩 pixelData_.resize(width_ * height_ * channels_); std::memcpy(pixelData_.data(), data, pixelData_.size()); createTexture(data); stbi_image_free(data); } else { E2D_LOG_ERROR("加载纹理失败: {}", filepath); } } GLTexture::~GLTexture() { if (textureID_ != 0) { // 检查 GPU 上下文是否仍然有效 // 如果 OpenGL 上下文已销毁,则跳过 glDeleteTextures 调用 if (GPUContext::get().isValid()) { glDeleteTextures(1, &textureID_); } // VRAM 跟踪: 释放纹理显存(无论上下文是否有效都需要更新统计) if (dataSize_ > 0) { VRAMMgr::get().freeTexture(dataSize_); } } } void GLTexture::setFilter(bool linear) { bind(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear ? GL_LINEAR : GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST); } void GLTexture::setWrap(bool repeat) { bind(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE); } void GLTexture::bind(unsigned int slot) const { glActiveTexture(GL_TEXTURE0 + slot); glBindTexture(GL_TEXTURE_2D, textureID_); } /** * @brief 解绑当前纹理 */ void GLTexture::unbind() const { glBindTexture(GL_TEXTURE_2D, 0); } /** * @brief 创建OpenGL纹理对象并上传像素数据 * @param pixels 像素数据指针 */ void GLTexture::createTexture(const uint8_t *pixels) { GLenum format = GL_RGBA; GLenum internalFormat = GL_RGBA8; int unpackAlignment = 4; if (channels_ == 1) { format = GL_RED; internalFormat = GL_R8; unpackAlignment = 1; format_ = PixelFormat::R8; } else if (channels_ == 3) { format = GL_RGB; internalFormat = GL_RGB8; unpackAlignment = 1; format_ = PixelFormat::RGB8; } else if (channels_ == 4) { format = GL_RGBA; internalFormat = GL_RGBA8; unpackAlignment = 4; format_ = PixelFormat::RGBA8; } glGenTextures(1, &textureID_); bind(); GLint prevUnpackAlignment = 4; glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlignment); glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlignment); glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width_, height_, 0, format, GL_UNSIGNED_BYTE, pixels); glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlignment); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // 使用 NEAREST 过滤器,更适合像素艺术风格的精灵 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glGenerateMipmap(GL_TEXTURE_2D); // VRAM 跟踪 dataSize_ = static_cast(width_ * height_ * channels_); VRAMMgr::get().allocTexture(dataSize_); } // ============================================================================ // 压缩纹理加载 // ============================================================================ bool GLTexture::loadCompressed(const std::string &filepath) { std::string ext = filepath.substr(filepath.find_last_of('.') + 1); if (ext == "ktx" || ext == "KTX") { return loadKTX(filepath); } if (ext == "dds" || ext == "DDS") { return loadDDS(filepath); } E2D_LOG_ERROR("不支持的压缩纹理格式: {}", filepath); return false; } /** * @brief 加载KTX格式压缩纹理 * @param filepath KTX文件路径 * @return 加载成功返回true,失败返回false */ bool GLTexture::loadKTX(const std::string &filepath) { std::ifstream file(filepath, std::ios::binary); if (!file.is_open()) { E2D_LOG_ERROR("打开 KTX 文件失败: {}", filepath); return false; } KTXHeader header; file.read(reinterpret_cast(&header), sizeof(header)); if (!file) { E2D_LOG_ERROR("读取 KTX 文件头失败: {}", filepath); return false; } // 验证标识符 if (std::memcmp(header.identifier, KTX_IDENTIFIER, 12) != 0) { E2D_LOG_ERROR("无效的 KTX 标识符: {}", filepath); return false; } width_ = static_cast(header.pixelWidth); height_ = static_cast(header.pixelHeight); channels_ = 4; // 压缩纹理通常解压为 RGBA // 确定压缩格式 GLenum glInternalFormat = header.glInternalFormat; switch (glInternalFormat) { case GL_COMPRESSED_RGB8_ETC2: format_ = PixelFormat::ETC2_RGB8; channels_ = 3; break; case GL_COMPRESSED_RGBA8_ETC2_EAC: format_ = PixelFormat::ETC2_RGBA8; break; case GL_COMPRESSED_RGBA_ASTC_4x4: format_ = PixelFormat::ASTC_4x4; break; case GL_COMPRESSED_RGBA_ASTC_6x6: format_ = PixelFormat::ASTC_6x6; break; case GL_COMPRESSED_RGBA_ASTC_8x8: format_ = PixelFormat::ASTC_8x8; break; default: E2D_LOG_ERROR("不支持的 KTX 内部格式: {:#06x}", glInternalFormat); return false; } // 跳过 key-value 数据 file.seekg(header.bytesOfKeyValueData, std::ios::cur); // 读取第一个 mipmap level uint32_t imageSize = 0; file.read(reinterpret_cast(&imageSize), sizeof(imageSize)); if (!file || imageSize == 0) { E2D_LOG_ERROR("读取 KTX 图像大小失败: {}", filepath); return false; } std::vector compressedData(imageSize); file.read(reinterpret_cast(compressedData.data()), imageSize); if (!file) { E2D_LOG_ERROR("读取 KTX 图像数据失败: {}", filepath); return false; } // 创建 GL 纹理 glGenTextures(1, &textureID_); bind(); glCompressedTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width_, height_, 0, static_cast(imageSize), compressedData.data()); GLenum err = glGetError(); if (err != GL_NO_ERROR) { E2D_LOG_ERROR("KTX 纹理上传失败: {:#06x}", err); glDeleteTextures(1, &textureID_); textureID_ = 0; return false; } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // VRAM 跟踪 dataSize_ = imageSize; VRAMMgr::get().allocTexture(dataSize_); E2D_LOG_INFO("已加载 KTX 压缩纹理: {} ({}x{}, 格式={:#06x})", filepath, width_, height_, glInternalFormat); return true; } /** * @brief 加载DDS格式压缩纹理 * @param filepath DDS文件路径 * @return 加载成功返回true,失败返回false */ bool GLTexture::loadDDS(const std::string &filepath) { std::ifstream file(filepath, std::ios::binary); if (!file.is_open()) { E2D_LOG_ERROR("打开 DDS 文件失败: {}", filepath); return false; } DDSHeader header; file.read(reinterpret_cast(&header), sizeof(header)); if (!file) { E2D_LOG_ERROR("读取 DDS 文件头失败: {}", filepath); return false; } if (header.magic != DDS_MAGIC) { E2D_LOG_ERROR("无效的 DDS 魔数: {}", filepath); return false; } width_ = static_cast(header.width); height_ = static_cast(header.height); channels_ = 4; GLenum glInternalFormat = 0; // 检查 DX10 扩展头 if ((header.pixelFormat.flags & DDPF_FOURCC) && header.pixelFormat.fourCC == makeFourCC('D', 'X', '1', '0')) { DDSHeaderDXT10 dx10Header; file.read(reinterpret_cast(&dx10Header), sizeof(dx10Header)); if (!file) { E2D_LOG_ERROR("读取 DDS DX10 文件头失败: {}", filepath); return false; } // DXGI_FORMAT 映射到 GL 格式 switch (dx10Header.dxgiFormat) { case 147: // DXGI_FORMAT_ETC2_RGB8 glInternalFormat = GL_COMPRESSED_RGB8_ETC2; format_ = PixelFormat::ETC2_RGB8; channels_ = 3; break; case 148: // DXGI_FORMAT_ETC2_RGBA8 glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; format_ = PixelFormat::ETC2_RGBA8; break; default: E2D_LOG_ERROR("不支持的 DDS DX10 格式: {}", dx10Header.dxgiFormat); return false; } } else { E2D_LOG_ERROR("DDS 文件未使用 DX10 扩展,不支持: {}", filepath); return false; } // 计算压缩数据大小 size_t blockSize = (glInternalFormat == GL_COMPRESSED_RGB8_ETC2) ? 8 : 16; size_t blocksWide = (width_ + 3) / 4; size_t blocksHigh = (height_ + 3) / 4; size_t imageSize = blocksWide * blocksHigh * blockSize; std::vector compressedData(imageSize); file.read(reinterpret_cast(compressedData.data()), imageSize); if (!file) { E2D_LOG_ERROR("读取 DDS 图像数据失败: {}", filepath); return false; } // 创建 GL 纹理 glGenTextures(1, &textureID_); bind(); glCompressedTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width_, height_, 0, static_cast(imageSize), compressedData.data()); GLenum err = glGetError(); if (err != GL_NO_ERROR) { E2D_LOG_ERROR("DDS 纹理上传失败: {:#06x}", err); glDeleteTextures(1, &textureID_); textureID_ = 0; return false; } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // VRAM 跟踪 dataSize_ = imageSize; VRAMMgr::get().allocTexture(dataSize_); E2D_LOG_INFO("已加载 DDS 压缩纹理: {} ({}x{})", filepath, width_, height_); return true; } void GLTexture::generateAlphaMask() { if (pixelData_.empty() || width_ <= 0 || height_ <= 0) { E2D_LOG_WARN("无法生成透明遮罩: 没有可用的像素数据"); return; } alphaMask_ = std::make_unique(AlphaMask::createFromPixels( pixelData_.data(), width_, height_, channels_)); E2D_LOG_DEBUG("已为纹理生成透明遮罩: {}x{}", width_, height_); } PixelFormat GLTexture::getFormat() const { return format_; } /** * @brief 静态工厂方法,创建指定格式的空纹理 * @param width 纹理宽度 * @param height 纹理高度 * @param format 像素格式 * @return 创建的纹理智能指针 */ Ptr GLTexture::create(int width, int height, PixelFormat format) { int channels = 4; switch (format) { case PixelFormat::R8: channels = 1; break; case PixelFormat::RG8: channels = 2; break; case PixelFormat::RGB8: channels = 3; break; case PixelFormat::RGBA8: channels = 4; break; default: channels = 4; break; } return makePtr(width, height, nullptr, channels); } } // namespace extra2d