#c #multithreading #opengl #raii
Вопрос:
Учитывая несколько особенностей OpenGL, многопоточность с помощью OpenGL-довольно сложная тема. Хотя общие контексты существуют, нет никакой гарантии, что ресурс OpenGL правильно инициализирован, загружен и разделен между контекстами, и отслеживание того, какой тип ресурса может быть общим, само по себе является небольшой головной болью (да, возможно ведение списка из (в)официальных документов, по-прежнему нет единого способа обработки ВСЕХ типов ресурсов, не прибегая к рассматриваемому решению).
Одна из рекомендаций, которую я получил, заключалась в том, чтобы заниматься загрузкой ресурсов с диска в отдельном потоке при загрузке на графический процессор в основном потоке. Прекрасная возможность для экрана загрузки.
Поэтому я хочу сделать несколько шагов, чтобы оптимизировать этот процесс. Поэтому я разделяю данные, которые хочу загрузить на графический процессор, на дескриптор графического процессора (класс текстуры) и данные на процессоре (класс TextureData).
class TextureData { // Data can be copied without a problem
GLuint width;
GLuint height;
GLvoid *data;
public:
friend void swap(TextureData amp;a, TextureData amp;b) noexcept {
using std::swap;
swap(a.width, b.width);
swap(a.height, b.height);
swap(a.data, b.data);
}
TextureData() : width(0), height(0), data(nullptr) {}
TextureData(std::string filename);
TextureData(GLuint width, GLuint height, GLvoid* data) : width(width), height(height), data(data) {}
// There is a conceptual problem with ownership of the data pointer about this constructor though
TextureData(TextureData amp;other) : width(other.width), height(other.height), data(other.data) {}
// Similarily here ...
TextureData(TextureData amp;amp;other) : TextureData()
{
using std::swap;
swap(*this, other);
}
TextureData amp; operator=(TextureData other)
{
using std::swap;
swap(*this, other);
return *this;
}
~TextureData() {
delete data; // I expect the pointer isn't needed anymore after uploading the data but if there is a better way to make ownership of the data pointer more clear, feel free to elaborate
}
GLuint get_width() {
return this->width;
}
GLuint get_height() {
return this->height;
}
GLvoid *get_data() {
return this->data;
}
};
С фактической текстурой, которая имеет отношение ко всему остальному, просто так:
class Texture { // Handles a resource handle (the numerical ID of the texture) and therefore is not copiable
GLuint texture;
GLuint width;
GLuint height;
public:
Texture();
Texture(TextureData amp;data); // In theroy, data can be freed right after construction
Texture(GLuint width, GLuint height, GLvoid* data); // Similar here
Texture(Texture amp;amp;temp); // Move Constructor
~Texture();
Texture amp; operator=(Texture amp;amp;temp); // Move Assignment
#ifdef ALLOW_TEXTURE_COPY
Texture(const Texture amp;copy);
Texture amp;operator=(const Texture amp;copy);
#endif
void bind();
GLuint get_name() const { return this->texture; }
GLuint get_width() const { return this->width; }
GLuint get_height() const { return this->height; }
void read_size(GLuint amp;width, GLuint amp;height) const;
};
Итак, если я хочу нарисовать изображение на сцене, например:
class Scene {
struct TextureInfo {
TextureData *data;
Texture amp;resource;
};
Texture image;
std::mutex load_mutex;
std::vector<TextureInfo> loaded;
public:
Scene(ThreadPool pool) {
pool.task([this](){
TextureInfo info;
info.data = load_image("res/image.png");
info.resource = this->image;
std::unique_lock<std::mutex> lock(this->load_mutex);
this->loaded.emplace_back(info);
});
}
void draw(Renderer r) {
if(!all_resources_loaded) { // however I find that out
draw_loading_screen(); // spinning wheels wheeeeeee
} else {
r.draw_image(0, 0, image);
}
}
void update(double time) { // gets called in the main thread the OpenGL context is made current in ; also: double time *drums the beat*
{
std::unique_lock<std::mutex> lock(this->load_mutex);
for(TextureInfo info : this->loaded) {
info.resource = Texture(*info.data);
//glFinish();
/* I remember a case where the upload itself is actually off-thread and
* therefore the deletion of the data in the next line can corrupt the data
* that openGL fetches in the meantime, but I have no idea if that was
* because of the shared context I used before or because of some other
* reason */
delete info.data;
}
this->loaded.clear();
}
if(!all_resources_loaded) {
update_loading_screen();
} else {
update_scene();
}
}
Так что мой вопрос в основном таков … это нормально? Есть ли альтернативы? Верны ли предположения о том, что является и чем не является ресурсом? Будут ли какие-то стратегии, которые приведут к серьезным улучшениям?
Комментарии:
1. да, в этом есть смысл. Но почему вы загружаете ресурс, а они перемещают его? Поскольку вы ждете завершения загрузки, не могли бы вы просто загрузить их на место?
2. Я мог бы, но в то время, когда это произойдет, экран будет не реагировать. Я хочу предотвратить это, отобразив какой-нибудь интерактивный загрузочный экран или что-нибудь еще, но я отчаянно хочу избежать старых добрых дней панели загрузки, которая медленно заполняет неразличимые интервалы, и вместо этого просто хочу убедиться, что какая -то анимация разрешена, чтобы визуализировать, что на самом деле происходит.
3. Эту часть я понимаю. Я был сбит с толку, так как ваш фрагмент начинается с
TextureData amp; operator=(TextureData other)
того, что это действительно было необходимо. Но, похоже, ваш груз действительно загружается в объект. Итак, nvm4. » отслеживание того, какой тип ресурсов вообще может быть общим, само по себе является небольшой головной болью». Все зависит от того, что вы подразумеваете под «типом ресурса». Единственные вещи, которые я бы назвал «ресурсами», — это буферы или текстуры, которые являются общими. Вы не загружаете файлы FBO из файла, не запрашиваете объекты или что-то еще. Даже VAOs не следует рассматривать как «ресурсы».
5. Все эти вещи нуждаются в идентификаторе, полученном функциями glCreateX, который служит дескриптором доступа к этим объектам, и поэтому я бы рассматривал их как ресурсы, которые не следует копировать. Я не уверен в «состоянии» в VAOs, мне всегда нужно перечитывать документы о том, как они должны использоваться и как они на самом деле функционируют в фоновом режиме, но даже у VAOs есть идентификатор дескриптора GLuint. Как таковой класс-оболочка, представляющий их, также должен отвечать за их уничтожение / выгрузку.