如何在EGL中后台加载OpenGL纹理?解决启动卡顿问题
后台加载OpenGL纹理解决启动卡顿问题
问题背景
大学作业场景:基于OpenGL实现支持纹理/材质切换的3D场景,切换功能已完成,但启动时因纹理从磁盘加载耗时过长导致卡顿,希望在渲染材质场景的同时后台加载纹理,此前尝试线程加载未成功。
核心原因
OpenGL上下文是线程绑定的,后台线程无法直接调用glGenTextures、glTexImage2D这类GL API——这是你之前线程加载失败的根本原因。必须将「磁盘读取+像素解码」与「GPU纹理创建+上传」拆分为两个独立步骤:后台线程只执行CPU侧的像素加载,GPU侧操作必须在拥有GL上下文的主线程中完成。
解决方案
1. 重构Texture类,分离加载与上传逻辑
修改类结构,将像素加载与GPU上传解耦,新增状态标记跟踪纹理加载进度:
修改后的Texture.h
#ifndef TEXTURE_H #define TEXTURE_H #include <iostream> #include <GL/glew.h> #include <FreeImage.h> #include <mutex> class Texture { public: // 纹理加载状态枚举 enum class State { UNLOADED, // 未开始加载 LOADING, // 后台加载像素中 READY_TO_UPLOAD, // 像素已加载,等待GPU上传 UPLOADED // 已完成GPU上传 }; Texture() : texture(0), state(State::UNLOADED), width(0), height(0), pixels(nullptr) {} virtual ~Texture(); // 启动后台异步加载(仅处理像素读取,不调用GL API) void startAsyncLoad(const char* textureFile); // 主线程调用:将像素上传至GPU并配置纹理 void uploadToGPU(); // 获取纹理ID(仅UPLOADED状态有效) unsigned int getTexture(); // 获取当前加载状态 State getState() const { return state; } private: // 后台线程执行的像素加载逻辑 void loadPixelData(const char* textureFile); unsigned int texture; State state; mutable std::mutex mutex; // 线程安全访问成员变量 // 后台加载的像素数据缓存 unsigned int width; unsigned int height; unsigned char* pixels; // 静态像素加载方法,避免重复初始化FreeImage static unsigned char* loadTexture(const char* textureFile, unsigned int& w, unsigned int& h); }; #endif /* TEXTURE_H */
修改后的Texture.cpp
#include "Texture.h" #include <thread> Texture::~Texture() { std::lock_guard<std::mutex> lock(mutex); if (pixels) delete[] pixels; if (texture != 0) glDeleteTextures(1, &texture); } void Texture::startAsyncLoad(const char* textureFile) { std::lock_guard<std::mutex> lock(mutex); if (state != State::UNLOADED) return; state = State::LOADING; // 启动后台线程加载像素数据 std::thread([this, textureFile]() { loadPixelData(textureFile); }).detach(); } void Texture::loadPixelData(const char* textureFile) { unsigned int w, h; unsigned char* pixelData = loadTexture(textureFile, w, h); std::lock_guard<std::mutex> lock(mutex); if (pixelData) { width = w; height = h; pixels = pixelData; state = State::READY_TO_UPLOAD; } else { state = State::UNLOADED; // 加载失败,重置状态 } } void Texture::uploadToGPU() { std::lock_guard<std::mutex> lock(mutex); if (state != State::READY_TO_UPLOAD || !pixels) return; // 以下GL操作必须在主线程执行 glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); glGenerateMipmap(GL_TEXTURE_2D); // 纹理参数配置 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); float aniso; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &aniso); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso); // 上传完成后释放CPU侧像素内存 delete[] pixels; pixels = nullptr; state = State::UPLOADED; } unsigned int Texture::getTexture() { std::lock_guard<std::mutex> lock(mutex); return texture; } unsigned char* Texture::loadTexture(const char* textureFile, unsigned int& w, unsigned int& h) { // FreeImage仅初始化一次 static bool freeImageInitialized = false; if (!freeImageInitialized) { FreeImage_Initialise(TRUE); freeImageInitialized = true; } FREE_IMAGE_FORMAT format = FreeImage_GetFileType(textureFile, 0); if (format == FIF_UNKNOWN) format = FreeImage_GetFIFFromFilename(textureFile); if ((format == FIF_UNKNOWN) || !FreeImage_FIFSupportsReading(format)) { std::cerr << "纹理文件" << textureFile << "格式不支持。" << std::endl; return nullptr; } FIBITMAP* texture = FreeImage_Load(format, textureFile); if (!texture) { std::cerr << "无法打开文件" << textureFile << "。" << std::endl; return nullptr; } FIBITMAP* temp = texture; texture = FreeImage_ConvertTo32Bits(texture); FreeImage_Unload(temp); w = FreeImage_GetWidth(texture); h = FreeImage_GetHeight(texture); unsigned char* pixelsBGRA = (unsigned char*)FreeImage_GetBits(texture); unsigned char* pixelsRGBA = new unsigned char[4 * w * h]; for (int j = 0; j < w * h; j++) { pixelsRGBA[j * 4 + 0] = pixelsBGRA[j * 4 + 2]; pixelsRGBA[j * 4 + 1] = pixelsBGRA[j * 4 + 1]; pixelsRGBA[j * 4 + 2] = pixelsBGRA[j * 4 + 0]; pixelsRGBA[j * 4 + 3] = pixelsBGRA[j * 4 + 3]; } FreeImage_Unload(texture); return pixelsRGBA; }
2. 主线程集成与渲染逻辑
初始化阶段(主线程)
启动所有纹理的异步加载,同时创建占位纹理避免渲染异常:
#include <vector> #include <memory> std::vector<std::unique_ptr<Texture>> textures; Textures goldTextures; void init() { // 创建占位纹理(灰色纯色,避免渲染时无有效纹理) unsigned int placeholderTex; glGenTextures(1, &placeholderTex); glBindTexture(GL_TEXTURE_2D, placeholderTex); unsigned char grayPixel[] = {128,128,128,255}; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1,1,0, GL_RGBA, GL_UNSIGNED_BYTE, grayPixel); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // 初始化gold材质的各个纹理,启动异步加载 auto diffuseTex = std::make_unique<Texture>(); diffuseTex->startAsyncLoad("resources/textures/gold/gold_diffuse.png"); textures.push_back(std::move(diffuseTex)); auto specularTex = std::make_unique<Texture>(); specularTex->startAsyncLoad("resources/textures/gold/gold_specular.png"); textures.push_back(std::move(specularTex)); // 初始用占位纹理填充Textures结构体 goldTextures.diffuse = placeholderTex; goldTextures.specular = placeholderTex; goldTextures.shininess = 32.0f; // 其他纹理(emissive/normal)同理 }
渲染循环(主线程)
每帧检查纹理状态,完成GPU上传后更新材质结构体:
void renderLoop() { while (!shouldClose()) { // 处理窗口事件等前置操作 // 检查所有纹理,上传已准备好的像素数据 for (size_t i = 0; i < textures.size(); i++) { auto& tex = textures[i]; if (tex->getState() == Texture::State::READY_TO_UPLOAD) { tex->uploadToGPU(); // 根据索引更新对应材质纹理 if (i == 0) { goldTextures.diffuse = tex->getTexture(); } else if (i == 1) { goldTextures.specular = tex->getTexture(); } // 其他纹理类型对应索引判断 } } // 正常渲染场景,使用goldTextures中的纹理ID renderScene(goldTextures); // 交换缓冲区、处理输入等后置操作 } }
关键注意事项
- 线程安全:所有访问Texture成员变量的地方必须加互斥锁,避免数据竞争
- FreeImage生命周期:仅初始化一次,不要在后台线程中重复调用
FreeImage_DeInitialise - 占位纹理:必须保证渲染时纹理ID有效,防止OpenGL报错
- 资源清理:Texture析构函数需正确释放CPU侧像素内存与GPU侧纹理对象
内容的提问来源于stack exchange,提问作者lovethefrogs
相关产品推荐
相关产品推荐

