Russian Qt Forum
Апрель 26, 2024, 03:22 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 2 [3] 4   Вниз
  Печать  
Автор Тема: Использование QOpenGLBuffer  (Прочитано 27781 раз)
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #30 : Ноябрь 23, 2016, 02:41 »

А тут надо определиться, чье время дороже, программиста или юзера.
Имею в виду, есть ли смысл тратиться на мегаоптимизации, чтобы у юзера все летало...
Или же сделать обычным пересчетом данных каждый раз, заставив юзера несколько ожидать.
Обычно истина где то посередине...
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #31 : Ноябрь 23, 2016, 10:49 »

А тут надо определиться, чье время дороже, программиста или юзера.
Имею в виду, есть ли смысл тратиться на мегаоптимизации, чтобы у юзера все летало...
Или же сделать обычным пересчетом данных каждый раз, заставив юзера несколько ожидать.
Обычно истина где то посередине...
Во как,  "мегаоптимизации" Улыбающийся  Вообще задача довольно стандартная, во всяком случае я столкнулся с подобным далеко не первый раз.

Всем известно как работает стандартная шара. Копии указывают на один объект пока не случился detach(). Вумные указатели - то же самое, произошел reset() - все, у этого клиента уже новые данные, остальные клиенты как себе хотят. На этом стандартные приемы заканчиваются.

Но по жизни нередко возникает ситуация когда велика вероятность что новые данные уже существуют, зачем же их еще раз создавать раcходуя ресурсы? Естественно выглядит хранить мапу(ы) созданных вариантов и шарить значения этой мапы. Понятно что надо иметь ключ, напр если detach произошел для массива/имеджа - ничего не выйдет, захлебнемся с ключом. Однако, если имеем ограниченный набор опций (в исходной постановке вообще лишь 1 int - "уровень деталировки"), то вот он и ключ.

Правда как организовать такую мапу - не так уж очевидно, об этом я и хотел поговорить. Увы, по каким-то необъяснимым причинам, чего-то типа "мап/хеш" не прозвучало вообще Плачущий  Возможно сбила с толку предметная часть, мол, нужно знать многочисленные подробности OpenGL, вот тогда.. Это совсем не так, да и глянуть тот же QOpenGLBuffer в букваре - дело нескольких минут. Опять заметим как мало помогает усердное изучение std/boost/паттернов и всякой всячины - вот банальная, заурядная задачка и... нiчого нема  Плачущий
Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #32 : Ноябрь 23, 2016, 11:29 »

А тут получается палка с двумя концами.
Хотим экономить ресурсы? Никаких шар, формируем только нужные данные в нужное время и фигачим на карту.
Хотим экономить время? Тогда создаем всевозможные кэши, половина из которых будет тупо ждать своего часа.
Конечно, тут можно гибрид сделать.
Но неясно, что в итоге хочется получить - "быстро" или "дешево".
Я бы не мучился и создавал бы нужные данные "on the fly". Конечно, если позволяет память, их можно положить в уголок, посчитав какое-нибудь crс. То есть достаточно будет единого глобального кэша, чтоб хранить посчитанные фрагменты. Но тогда придется гемороиться с инвалидацией, "устареванием" и прочими присущими вещами...
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #33 : Ноябрь 23, 2016, 12:09 »

А тут получается палка с двумя концами.
Хотим экономить ресурсы? Никаких шар, формируем только нужные данные в нужное время и фигачим на карту.
Хотим экономить время? Тогда создаем всевозможные кэши, половина из которых будет тупо ждать своего часа.
Конечно, тут можно гибрид сделать.
Но неясно, что в итоге хочется получить - "быстро" или "дешево".
Типичный ответ "на основании (богатого) опыта" Улыбающийся Да, так часто бывает, но это не тот случай. О кеше здесь речь не идет, никакой альтернативы быстро/дешево нет, грамотно шарить данные и быстрее и дешевле.

Пример 1: юзверь назначил объекту деталировку 1 и затем скопировал его N раз. Простое использование Qt имплисит шары или вумных указателей - и мы имеем одну копию данных. Это совершенно нормально и ожидаемо

Пример 2: юзверь сначала скопировал объект N раз, а уж потом назначил объекту деталировку 1 для всех копий. Очевидно рез-т должен быть тот же что и в примере 1, но почему-то мы создаем N копий данных. Глупо, не так ли? Почему это мы не можем "зашарить взад"?

Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #34 : Ноябрь 24, 2016, 10:20 »

Как минимум надо делать различие между копией и инстанциями.
А если юзер склонировал объект а в б и потом поменял детальность б, должен ли измениться а?
А если а был отредактирован на уровне модели? Должны обновиться копии?
Если а убить, что с клонами будет?
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #35 : Ноябрь 25, 2016, 17:32 »

Но по жизни нередко возникает ситуация когда велика вероятность что новые данные уже существуют, зачем же их еще раз создавать раcходуя ресурсы? Естественно выглядит хранить мапу(ы) созданных вариантов и шарить значения этой мапы. Понятно что надо иметь ключ, напр если detach произошел для массива/имеджа - ничего не выйдет, захлебнемся с ключом. Однако, если имеем ограниченный набор опций (в исходной постановке вообще лишь 1 int - "уровень деталировки"), то вот он и ключ.

Правда как организовать такую мапу - не так уж очевидно, об этом я и хотел поговорить. Увы, по каким-то необъяснимым причинам, чего-то типа "мап/хеш" не прозвучало вообще Плачущий  Возможно сбила с толку предметная часть, мол, нужно знать многочисленные подробности OpenGL, вот тогда.. Это совсем не так, да и глянуть тот же QOpenGLBuffer в букваре - дело нескольких минут. Опять заметим как мало помогает усердное изучение std/boost/паттернов и всякой всячины - вот банальная, заурядная задачка и... нiчого нема  Плачущий

Оффтоп
Тему не читал, но мы использовали QPixmapCache в делегате таблицы с ключом от QStyleOption + data ячейки. Давало бешеное ускорение отрисовки.
Оффтоп
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #36 : Ноябрь 25, 2016, 17:36 »

Пример 2: юзверь сначала скопировал объект N раз, а уж потом назначил объекту деталировку 1 для всех копий. Очевидно рез-т должен быть тот же что и в примере 1, но почему-то мы создаем N копий данных. Глупо, не так ли? Почему это мы не можем "зашарить взад"?



QExplicitlySharedDataPointer?Улыбающийся
Типа - какие-то операции над объектом детачат, а какие-то - нет. Но тут придется попрощаться с многопоточностью.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #37 : Ноябрь 26, 2016, 13:57 »

Как минимум надо делать различие между копией и инстанциями.
Для юзера существует одно понятие "скопировать", копия имеет те же права что и оригинал
А если юзер склонировал объект а в б и потом поменял детальность б, должен ли измениться а?
Нет, с какой стати?
Если а убить, что с клонами будет?
Ничего, живут себе как жили
А если а был отредактирован на уровне модели? Должны обновиться копии?
Тоже нет, с точки зрения юзера все независимо, что там шарится или нет под капотом его не волнует. Однако вполне реально что он нахрюкает сотню-другую копий, и тогда производительность будет сильно зависеть от числа используемых буферов.

Ладно, допустим пока не знаем что делать, не пойти ли "от использования"? Есть объект (CRenderObject), может он уникален, а может одна копия из многих - хз. Но по-любому мы его собрались рисовать, а значит надо получить буфер(а). Как бум это делать? Предлагаю такой набросок
Код
C++ (Qt)
typedef QSharedPoiinter<QOpenGLBuffer> TBufVBO;
 
struct CVerArray : public QVector<QVector3D> {
CBufControl  m_control;
};
 
// код рисования
void CRenderObject::Draw( ... )
{
// получаем буфер позиций вертексов
 CVerArray * ver = GetVerArray();
 TBufVBO vbo = ver->m_control.GetBuf(this);
 
// отдаем его шейдеру
 vbo->bind();
 glVertAttribPointer(...);
 vbo->release();
 
// то же с др буферами  
 ...
}
 
Ну то есть всю нагрузку переложили на (пока мифический) CBufControl::GetBuf, вот пусть он разбирается, если надо запускает расчет деталировки и в конце-концов вернет нам нужный буфер. Да, так надо делать по каждому буферу - это вызывает сомнения.

Критикуем/предлагаем активнее, товарищи  Улыбающийся 
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #38 : Ноябрь 26, 2016, 19:06 »


Ладно, допустим пока не знаем что делать, не пойти ли "от использования"? Есть объект (CRenderObject), может он уникален, а может одна копия из многих - хз. Но по-любому мы его собрались рисовать, а значит надо получить буфер(а). Как бум это делать? Предлагаю такой набросок

Мне не нравится, что объект, который суть копируемые данные, умеет себя рисовать.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #39 : Ноябрь 27, 2016, 02:14 »

В общем, прокурил всю тему

Верно я понимаю, что нужно что-то типа:
Код:
class CRenderObject 
{
    int level; // уровень деталировки
    std::shared_pointer<QOpenGLBuffer> buffer; // текущий буффер
    std::shared_pointer<InnerData> data;
};

struct InnerData
{
    std::vector<QVector3D> indexes; // сырые данные, не шарю в ОГЛ
    std::map<int /*level*/, std::weak_pointer<QOpenGLBuffer> buffers; // либо тут тоже shared_pointer
};

То есть имеем что CRenderObject держит какой-то один буффер, который быстро рисует. При изменении уровня детализации лезет в общий для всех *таких* (построенных на одних данных) объектов, лезем в общую дату и достаем новый буфер из кэща (если его там нет, создаем). Дальше вариант - если 2 копии имели деталировку 2, а потом обе стали не 2 (например, 1 и 3 или 1 и 1), то можно дропать буффер для 2 (тогда в InnerData надо что-то типа weak_ptr), либо не дропать (тогда нужен shared).
У этого решения есть минус, что он не учитывает, что 2 *таких* объекта могут быть созданы отдельно:
Код:
std::string s1("hello");
std::string s2("hello"); // данные не шарятся так как нет возможности узнать что они одинаковые
Тогда нужно еще общее хранилище InnerData которое будет доставать даты по сложному ключу от массивов. Но это плохое решение и надеюсь, это не ваш случай. Иначе надо подумать ещё.
Также я не очень понял про различные буфера. CRenderObject должен хранить несколько буферов? Детализация для буферов одинаковая при этом? Или разные CRenderObject имеют разное "предназначение" (вообще в огл не шарю, ничего кроме массива вершин придумать не могу) и строятся на разных данных?

Upd: ну это решение плохое тем, что не держит многопоточность, но думаю она и не нужна. Иначе придется накрутить детачей где-то.
Upd2: Если API объекта позволяет менять сырые данные то вот тут придется детачить InnerData и пересоздавать буффер.
« Последнее редактирование: Ноябрь 27, 2016, 02:21 от Авварон » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #40 : Ноябрь 27, 2016, 10:18 »

Повторим матчасть. Вот общие прынцыпы
- Есть исходные данные стандартные для OpenGL - вертексы и индексы, число индексов на фейс (могут быть не только тр-ки) + всякая опциональная всячина (нормали, цвет, UV(s)  др). Хранятся они в массивах/векторах, в общем у любого пользующего OpenGL практически то же самое. Данные могут шариться разными объектами, напр N объектов может использовать одни и те же вертексы и индексы, или только индексы. Ну и конечно все данные буферированы с использованием QOpenGLBuffer чтобы не гонять их CPU -> GPU при каждом рисовании
Вот структуры данных (пока без буферов, нам и надо их организовать)
Код
C++ (Qt)
typedef QVector<QVector3D> TVerArray;  
typedef QVector<int> TIndArray;
 
struct CRenderObject {
...
// данные рисования, могут шариться любым кол-вом CRenderObject
QSharedPointer<CGeometry> m_geometry;
 
// уровень деталировки, 0 = исходная геометрия
int m_detailLevel;      
 
// Если к объекту применены нелинейные преобразования
// то его "личные" вертексы и нормали должны использоваться
// (вместо тех же данных в m_geomretry)
TVerArray m_vertex, m_normal;
};
 
// класс данных рисования
struct CGeometry {
...
 TVerArray m_vertex, m_normal, m_color, m_uv0, m_uv1;
 TIndArray m_indices, m_ver_per_face;
};
Как уже говорилось, все это везде примерно одинаково. Ну у меня 2 массива UV, у кого-то может быть другое число, или доп массивы напр для бампа - но в любом случае это должны быть "линейные" контейнеры (данные подряд) и число эл-тов в каждом в точности такое как у m_vertex

Без буферов и деталировок структура выше шарится простейшими средствами - скопировали шаред m_geometry, вот и все. Если "объект изменился", тоже все работает, 2 варианта

1) Изменилась "топология" (расклад полигонов). Объект получает новую m_geometry (т.е. все с нуля)
2) Объект может менять свои вертексы (а значит и нормали). Они записываются отдельно от m_geometry (см выше) и на рисовании используются они. Эти 2 контейнера мы не шарим, считаем что слишком сложно отследить что к 2 объектам были применены одни и те же действия (хотя возможно это и не так). Однако остальные данные рисования (индексы, UV и др) по-прежнему берутся из m_geometry.

Буфер (класс QOpenGLBuffer) свой для каждого контейнера данных, по существу просто хендл данных сохраненных на карте. Перед вызовом рисования (glDrawElements) мы должны сообщить OpenGL какие буфера должны быть использованы, подробности API опускаем. Создание буфера: в простейшем случае мы просто копируем данные контейнеров на видео чтобы рисовало шустрее. В случае "деталировок" (SDS по-нашему) мы хотим хранить hi-poly данные на видео и рисовать их. В любом случае один объект (CRenderObject) имеет лишь один набор буферов, по одному на каждый из контейнеров.

Вот об этом идет речь.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #41 : Ноябрь 27, 2016, 18:16 »

Код:
typedef QVector<QVector3D> TVerArray;  
typedef QVector<int> TIndArray;
 
struct CRenderObject {
...
// данные рисования, могут шариться любым кол-вом CRenderObject
 QSharedPointer<CGeometry> m_geometry;
 
// уровень деталировки, 0 = исходная геометрия
 int m_detailLevel;      
 
// Если к объекту применены нелинейные преобразования
// то его "личные" вертексы и нормали должны использоваться
// (вместо тех же данных в m_geomretry)
 bool m_has_own_data;
 TVerArray m_vertex, m_normal;

  // если m_has_own_data == false то шарим все буффера из мапы m_buffers_cache
  // если m_has_own_data == true то храним два своих уникальных буфера, остальные шарим
  Buffers m_buffers;
};

struct Buffers
{
    QSharedPointer<QOpenGLBuffer> m_vertex_buffer;
    QSharedPointer<QOpenGLBuffer> m_color_buffer;
  // итд
};
 
// класс данных рисования
struct CGeometry {
...
  TVerArray m_vertex, m_normal, m_color, m_uv0, m_uv1;
  TIndArray m_indices, m_ver_per_face;
  QMap<int /*level*/, Buffers> m_buffers_cache;
};

Ну что, не пойдёт?
« Последнее редактирование: Ноябрь 28, 2016, 16:19 от Авварон » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #42 : Ноябрь 28, 2016, 16:32 »

Код:
  QMap<int /*level*/, Buffers> m_buffers_cache;
};
Такая мапа выдаст набор буферов по ключу "уровень деталировки" - но ничего более. Напр CRenderObject удаляется. Возможно он был единственным кто пользовал напр уровень 2. Как буфера этого уровня будут убиты?

Также мне кажется что связь между контейнером и его буфером куда более тесная. Хотя бы
Код
C++ (Qt)
m_vertex.clear();
// базовые данные изменились, буфер "no longer valid". Кто его прибьет?
 
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #43 : Ноябрь 28, 2016, 16:44 »

Такая мапа выдаст набор буферов по ключу "уровень деталировки" - но ничего более. Напр CRenderObject удаляется. Возможно он был единственным кто пользовал напр уровень 2. Как буфера этого уровня будут убиты?

Также мне кажется что связь между контейнером и его буфером куда более тесная. Хотя бы
Код
C++ (Qt)
m_vertex.clear();
// базовые данные изменились, буфер "no longer valid". Кто его прибьет?
 

1) ну храните в мапе не структуру а weak_pointer на неё. В основной структуре будет шаред + 2 буфера придётся вынести отдельно.
2) тот же, кто зовёт clear(). Придётся сделать ОБОЖЕ дополнительную ф-ию clearVertexes() в CGeometry, сделать массив приватным, а также добавить свойство "isValid" у буфера (например, в виде std::optional или еще одного указателя, который равен 0 когда буфер "пуст"/"не валиден"). Ну а что вы хотели, если надо синхронно менять 2 переменные, без обертки не обойтись. К сожалению (или к счастью) давно прошли времена, когда можно было сделать обычную структуру и руками лезть в её внутренности.

Можно на самом деле пойти дальше и объединить буфер и вершины в один класс (что вполне логично, раз они парные). И хранить уже указатели на пары буфер+данные.
Задача чисто техническая.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #44 : Ноябрь 29, 2016, 06:21 »

1) ну храните в мапе не структуру а weak_pointer на неё. В основной структуре будет шаред + 2 буфера придётся вынести отдельно.
Тогда что будет на копировании? Сейчас я шарю всю структуру m_geometry (что вполне логично), переходить на "почленное" как-то не резон. И вообще, стоит ли буферами засорять основные структуры (CGeometry и др)? Буфера используются только на рисовании - вот пусть рисование их и достает как шаред пойнтеры.

Да, а куда подевались все эти любители итераторов и.т.п.?  Улыбающийся

Записан
Страниц: 1 2 [3] 4   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.209 секунд. Запросов: 22.