суббота, 5 ноября 2011 г.

Машина состояний OpenGL. Часть вторая, кеширование.

Чтоб понять что не так с кешированием в сцене (и чтоб не допустить тех же ошибок) нужно разобраться как оно там устроено, для этого достаточно заглянуть в файл "glState.pas", давайте заглянем.



Далеко идти не придется, все что нас интересует находится в секции инициализации, а именно - наборы констант OpenGL: TGLStateTypes, TGLStates, TComparisonFunction и т.д. Их очень много, потому все перечислять не буду. Идея их использования очень простая - вместо того, чтоб написать "glEnable(GL_BLEND)" мы пишем: "TGLStateCache.Enable(stBlend)", в свою очередь процедура "TGLStateCache.Enable" выполняет три действия:
procedure TGLStateCache.Enable(const aState: TGLState);
begin
  if not (aState in FStates) then begin
    Include(FStates, aState);
    glEnable(cGLStateToGLEnum[aState].GLConst);
  end;
end;
Тоесть проверяет текущее значение состояния, и если оно не совпадает с заданным (или отсутствует в списке активных, как в этом примере) то мы вызываем команду OpenGL для установки этого состояния и сохраняем его значения для следующей проверки. И так со всеми состояниями (напомню, их сотни).


Идеологически все верно, но что же тогда не так? Почему у нас так много промахов?
Причин тут две, одна техническая, вторая социальная.

Для начала разберем социальную проблему, так как она проще - многим программистам (а у сцены были сотни разработчиков разных мастей), особенно хорошо знакомым с OpenGL, было намного проще написать "glEnable(BL_BLEND)" чем найти что установка стэйтов находится в "TRenderContextInfo.GLStates", потом найти как до этого "TRenderContextInfo" добраться, потом найти какая команда отвечает за установку нужного стэйта, потом найти какая константа отвечает нужному стэйту и лишь потом написать: "TRenderContextInfo.GLStates.Enable(stBlend)". Ну а так как программисты народ ленивый, то как говорится "маємо те, що маємо"...

С первой причиной разобрались - бить по рукам программистов, что использовали то что надо, а не то, что хочется. Какая же вторая причина? Неужели вся сцена написана такими криворукими программистами и никто из тех, кто коммитил изменения не исправлял такие ляпы? Исправляли, причем долгое время за качеством изменений следили очень пристально. Так что же не так? Причина кроется гораздо глубже, в самом сердце GLScene, в ее архитектуре.

Вся архитектура GLScene основана на дисплейных списках, как вы помните из предыдущих частей (или знали до этого), дисплейные списки позволяют перенести выполнение ряда команд прямо на сторону видеокарты, таким образом, команды установки состояний, попавшие в дисплейный список, больше не будут выполняться на стороне CPU и как следствие - не будут использовать значения из кэша и не будут оказывать никакого влияния на значения хранимые в кэше. В лучшем случае это приводит к неэффективному переключению состояний OpenGL, в худшем - к нарушению логики работы программы, так, читая значение из кэша перед установкой стэйта, вы не можете знать что оно было перед этим изменено дисплейным списком, и, будучи после проверки абсолютно уверенным в том, что устанавливаемое значение совпадает со значением в кэше, с чистой совестью пропускаете установку этого состояния, чтоб избежать лишнего переключения, в результате следующий объект рисуется с набором состояний с предыдущего объекта.

Проблема была очень неприятной, фактически это означало что результат рендеринга зависел от порядка рендеринга, к счастью спасали все те же дисплейные списки, которые игнорируя всякий CPU-кэш принудительно устанавливали все свойства. И так длилось очень долго, но справедливости ради замечу, что не смотря на все пакости, причиненные последним разработчиком сцены, он все же проделал огромную работу, в том числе и исправив этот многолетний баг и убрав из кода сцены все прямые установки состояний OpenGL, что сделало код намного стабильнее и предсказуемее. Но это будет потом, а мы пока вернемся к первым дням существования VBOMesh.

И так, первая сцена, первый кубик - все отлично! Два кубика - отлично, добавим к сцене стандартную фриформу - что-то не то, материал в редакторе материалов смотрелся как-то не так... третий кубик с тем же материалом что и у первых - ой, чего это зеленый кубик покраснел...
Как вы уже догадались - причина была в том, что я не использовал дисплейные списки, а просто применял материалы сцены, а они в свою очередь проверяли свое состояние через кэш, который бережно хранил устаревшую информацию...
Но это сейчас так просто, спустя столько лет, а в тот день я был готов рвать волосы на голове, пытаясь найти у себя ошибку. Откуда именно росли ноги у этой ошибки я догадался намного позже, но решать проблему нужно было еще тогда. Провозившись неделю с материалами, я все же понял что ошибка была на стороне Сцены и я никак не мог на нее повлиять со стороны своего модуля. Пришлось искать обходные пути, путь был только один - перед рисованием объекта сбросить все текущие состояния на значения по умолчанию, в этом случае во-первых обнулялся кэш, во-вторых - устанавливались верные состояния OpenGL, в результате чего материалы применялись адекватно. Но радость моя была не долгой, тот самый, последний разработчик сцены, взял да и убрал все функции Reset* из управления кэшем... Можете представить мое "разочарование", после стольких-то поисков и адаптаций... Пришлось переписывать все эти функции, но сам прецедент, совместно с последующим отказом от модуля OpenGL1x и множества других мелких "доработок", стал причиной отказа от дальнейших обновлений сцены и отказа от модулей сцены, в которых разработчика поджидали сюрпризы на каждом шагу (в последующих статьях я еще вернусь к этим историям).


Ну а теперь о главном, отказавшись от механизма кеширования GLScene, нужно было придумать что-то свое, так как без кеширования и дисплейных списков падение производительности было катастрофическим. И придумал, и без лишней скромности - считаю это предметом своей гордости и не имеющего аналогов, но об этом в следующей статье :)

2 комментария:

  1. Что же ты прям Яра так сильно пинаешь)) У него должны быть свои веские причины для подобной архитектуры стейтов. Наверное, хочет чтобы программирование на сцене было более "высокоуровневым". ))

    ОтветитьУдалить
  2. Яр птица гордая: пока не пнешь, не полетит! :)
    Ну а если серьезно - я пинаю не Яра (он хоть пофиксил баг со стейтами, висевший 11 лет) а архитектуру сцены, завязанную на дисплейных списках.

    Мне просто надоело каждый раз отвечать, почему я не хочу дорабатывать сцену, вот я тут и популярно, щаг за шагом, объясняю почему заниматься сценой нет смысла. И таких сообщений в блоге будет еще минимум десяток))

    Как уже наверное понятно, в сцене косметическими исправлениями не обойтись, нужно переписать само ядро сцены, кардинально поменять архитектуру и переписать с нуля 60% кода и заставить все это работать, да еще и без потери обратной совместимости.

    Собственно именно это я и сделал в VBOMesh, взял от сцены 30% и дописал недостающие 50% (10% это VCL, еще 10% это вспомогательные классы).

    ОтветитьУдалить