Cheb's Game Engine

Планы, идеология, архитектура и т.п.

Модератор: Модераторы

Re: Cheb's Game Engine

Сообщение Cheb » 03.08.2022 13:46:45

Обновление парадигмы

прим.: «эмо» - это исполняемый модуль, может быть как частью релизного екзешника, так и дебажной длл. Для простоты, всегда использует API матки, заточенный под работу с эмо в отдельной длл.

Крутил так и эдак, прикидывал, как же ёжики трахаются - окончательная форма немного неэлегантна, как любой компромисс, но имплементация будет дёшева.

1. Урезал осетра хешей до 128 бит.

2. Вижуалы на стороне матки - это классы. Эмо, однако, знает их как безтиповые указатели. Таким образом, связь объекта физики с вижуалом - это пара хеш + указатель. Хеши сохраняются при сериализации БД, указатели - нет.

3. Все потроха вижуала, что должны быть доступны со стороны эмо - собираются в запись, и уже эта запись пихается в класс в качестве поля. Эмо получает доступ через функцию API матки
ExposeInnards(visual: pointer): pointer;
, а затем вслепую тайп-кастит полученный указатель к конкретному типу указателя на запись. Неэлегантно, но я не ожидаю, что видов вижуалов будет больше дюжины.

4. Когда есть семейтво классов вижуалов - они используют один и тот же тип записи, в который напиханы все поля, специфичные для каждого. Ненужное просто не используется. Опять, неэлегантно, но классов этих - кот наплакал.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 958
Зарегистрирован: 06.06.2005 15:54:34

Re: Cheb's Game Engine

Сообщение Cheb » 08.08.2022 11:57:35

По ходу анализировать, какая модель оглобального свещения использовалась в игре-вдохновителе...
И испытал потрясение, осознав, что его *нет*!
http:\\chebmaster.com\_share\aos-brought-down-mountain.mkv (30 Мб)
Да, бывали блоки-лампочки, освещавшие на 4, кажется, блока вокруг. Но глобальная модель освещения сводится к градиенту от направления. Все блоки, независимо от положения, освещены одним и тем-же амбиентным светом, идущим откуда-то сбоку-сверху.
Всё остальное - мошенничество с цветом блоков, все мягкие тени - запечены в цвет блоков. На видео в одном месте даже видно тень между скалами от давно разрушенного моста.
Внутренние блоки земли и скал - темнее, чем блоки поверхности. Что создаёт иллюзию затемнения, когда вкапываешься.
И на этом - всё.
Но иллюзия получается настолько мощщная, что надо тысячу часов наиграть, чтобы этот мухлёж заметить :shock:
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 958
Зарегистрирован: 06.06.2005 15:54:34

Re: Cheb's Game Engine

Сообщение Cheb » 13.08.2022 17:41:03

:shock: На... Какого ЛЕШЕГО я дельту времени в микросекундах считаю в даблах?!!!

Код: Выделить всё
  function UsecDelta (ptsc: pqword): double;
var k: timeval;
begin
   fpgettimeofday(@k, NIL); //one microsecond resolution
  {$push}
   {$overflowchecks off}
   {$rangechecks off}
   Result:= 1000000.0 * longint(k.tv_sec - ptimeval(ptsc)^.tv_sec)
     + 1.0 * longint(k.tv_usec - ptimeval(ptsc)^.tv_usec);
  {$pop}
   if Result < 0.0 then Result:= 0.0;
end;

function UsecDelta (ptsc: pqword): double; nostackframe;
   var q: qword;
   begin
     asm
       rdtsc
       xor rax, rax
       shl rdx, 32
       or rdx, rax
       mov qword[q], rdx
     end ['rax', 'rdx'];
     if q < ptsc^ then q:= ptsc^;
     Result:= (q - ptsc^) * Mother^.Timer.RDTSCFactor;
   end; 


ЗАЧЕМ?! :evil:
Я ещё понимаю, когда в вычислениях участвовала Now(), она - дабл по естественным причинам. Но дельту-то?! Которая должна вычисляться дофига часто, и дофига быстро, для чего я даже ассемблер применил.

Рука, знакомься: лицо.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 958
Зарегистрирован: 06.06.2005 15:54:34

Re: Cheb's Game Engine

Сообщение Cheb » 22.08.2022 11:45:46

Изыскания пути

Прежде, чем пилить графическую субсистему (окончательный вариант, где опыт - сын ошибок трудных) - решил придумать ей название https://gamedev.ru/flame/forum/?id=270983&m=5598127#m12

Какан
(果敢 [kakan] ~na кн. решительный, смелый, отважный)

https://vkguide.dev/docs/extra-chapter/multithreading/ говорит:
The first and most classic way of multithreading a game engine is to make multiple threads, and have each of them perform their own task.

For example, you have a Game Thread, which runs all of the gameplay logic and AI. Then you have a Render Thread that handles all the code that deals with rendering, preparing objects to draw and executing graphics commands.

An example we can see of this is Unreal Engine 2, 3, and 4.


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

Дано:
1. СУБД слишком удобная, чтобы что-то делать без неё - но, архитектурно, абсолютно однотредовая. Ширше не будет - я и так почти год убил, пытаясь сидеть на трёх стульях (вышло бы как с огнелисом - который, сцуко, плодится и размножается, пока вся память не окажется зажрана дюжиной процессов firefox.exe). Хватит. Фтопку.
2. Изпользуемый гапи - OpenGL / GL ES, что налагает ограничение: все команды на рендер - только в основном потоке.

Из этого - физика:
1. Физика - свободноинтервальная с частотой кадров 1000. То есть, каждый объект тикает с той частотой, которой пожелает, просчитывая себя *вперёд*. Например, 500 мс для прямолинейно летящих снарядов. Остальные объекты, когда просчитывают свою реакцию с ним - используют интерполяцию между его прошлой и будущей точками. В случае столкновения - будят его, приводят его текущее состояние к текущему тику и лишь после этого сталкиваются.
Для более сложных объектов (монстр по буеракам) просчёт вперёд может содержать массив из позиций капсулы.
Как следствие - очень дороги массовые столкновения "куча мала", но былинно дёшевы отдельные, не взаимодействующие объекты. Представьте себе десять тысяч летящих фаерболов, каждый из которых пересчитывается два раза в секунду, а всё остальное время - спит.
Следствие следствия - приемлемо дешёвой становится лагокомпенсация всего мира.
2. Лагокомпенсация всего мира на основе расслоённой реальности (объект нижнего слоя при взаимодействии с ним в вышележащих слоях клонируется по принципу copy-on-write, а при взаимодействии в своём слое - аналогично клонируется в вышележащий слой)

Из такой физики - следствие: физику можно продвинуть вперёд на нужное число милисекунд, подстраиваясь под кадры рендера. Т.е (упрощённо, сингл):

1. Основной тред: вангует момент следующего кадра.
2. Основной тред: собрал инпуты и отправил событие побудки логическому треду

3. Логический тред: проснулся, применил инпуты, продвинул физику до текущей милисекунды.
4. Логический тред: сочиняет задание (job) какану, на основе списка вижуалов, создавая новые по необходимости
5. Логический тред: отправляет какану задание.
6. Логический тред: продвигает физику до <навангованный момент следущего кадра минус две милисекунды>
7. Логический тред: баиньки.

3. Основной тред: какан окучивает отложимые вижуалы с прошлого кадра (дорисовывает лайтмапы, обновляет скины игроков и прочая) выделяя на это столько милисекунд, сколько логический поток в среднем думал над физикой и заданием в прошлые N кадров.
4. Основной тред: если делать нечего - какан баиньки до получения задания, таймаут - конец кадра из расчёта 30 фпс
5. Основной тред: если тайм-аут (логический поток задумался) - какан берёт старое задание с предыдущего кадра, если ответа нет дольше полсекунды - сам составляет себе задание на синий экран.
6. Основной тред: какан приступает к обработке задания. Первый этап: распараллеливаемое окучивание специфических вижуалов. Выковыривает из задания все анимации и генерации мешей по воксельным чанкам, будит вспомогательные треды и вместе с ними окучивает.
7. Основной тред: когда буфер распараллеливемых задач исчерпан - какан приступает к выполнению задания, что сводится к переводу из древовидной стрктуры объектов в последовательность команд OpenGL.
8. Основной тред: закончив отправку команд - какан делает SwapBuffers, сопровождаемые принудительной синхронизацией цпу и гпу (например, glReadPixels из фронт буфера). Причём, динамически, часть кадров с включённы Vsync, часть - без.
9. Основной тред: балансировщик нагрузки анализирует тайминги на последние надцать кадров: сколько занял SwapBuffers без Vsync'а, на какие моменты приходился конец операции с VSync'ом и какой частоте монитора это соответствует - чтобы на основании этого ванговать момент следующего кадра и подбирать управляющий коэффициент DRR.
10. Основной тред: если, паче чаяния, до следующего кадра ещё дофига времени - баиньки.
11. Основной тред: goto 1

====================================================================================

Какан, воплощение

Встал вопрос, где имплементировать:

А) На стороне эмо: удобство составления задания (может быть деревом из объектов), не нужен пространный АПИ, крайне неудобно работать с вижуалами.
Б) На стороне матки: удобство работы с вижуалами, удобство сопряжения с ГАПИ (убирается необходимость транслировать ГАПИ в отладочную dll), крайне неудобно сочинять задания

В конечном итоге принял Б). Потребный новый АПИ будет минимизирован за счёт использования универсальных записей:

Код: Выделить всё
type
  TVisLink = {$ifdef devmodedll} pointer {$else} TVisual {$endif};

  PKakaJob = ^TKakaJob;
  PKakaState = ^TKakaState;
  PKakaTarget = ^TKakaTarget;

  TKakaTarget = packed record
    Buffer: TVisLink; // texture. NIL means the back buffer.
    // The order of targets is auto-determined based on which states use this same texture as texture
    // Target with no jobs in all its states would be ignored
    PState: PKakaState;
  end;

  TKakaJobFlagsEnum = (kjf_Skippable)
  TKakaJobFlagsSet = set of TKakaJobFlagsEnum;

  TKakaJob = record
    Visual: TVisLink;
    Flags: TKakaJobFlagsSet; //
    Priority, // if skippable, sorted by.
    QFDropOnSkip // if skipped, load balancer drops Quality Factor by this much
      : float;
  end;

  TKakaState = record
    Jobs: array of TKakaJob;
      // the same job can be shared between several states, possibly of different targets
      // state with no jobs would not execute but merge with the next one instead

    // all below: if NIL, inherit from previous
    Shader: TVisLink;
    Components: TRenderComponentSet; // uniforms & attributes used -- see the shader visual

    Matrix: PMatrix4f;
    Blend: PKakaBlend;
    Depth: PKakaDepth;
    Scissor: PKakaScissor;
    Texture: PKakaTexture;

    // ...

    Next: PKakaState;
  end;

  function NewKakaTarget: PKakaTarget; // adds one default state to the targrt
  procedure KakaTarget(ptarget: PKakaTarget); // switches current target
  function NewKakaJob: PKakaJob;
  function NewKakState: PKakaState;
  function GetCurrentKakaState: PKakaState;
  function PushKakaState: PKakaState; // pushes existing one down stack, creates new one and returns it
  function PopKakaState: PKakaState; // creates a CLONE of the state it removes from the stack


Технические решения:
1. Анимация - это *отдельный* объект, связанный с объектом моба, но тикающий на своей частоте (10..60 кадров в секунду, зависит от частоты ключевых кадров и наличия симуляции ткани). Вижуал у них - один на двоих, анимируемая модель, но задание для неё составляют на пару.
Главный профит - анимация становится подвержена лагокомпенсации.
2. Симуляция ткани (и волос) *не* честная. В физике просчитываются специальные «тряпочные кости», столкновимые с очень ограниченным набором примитивов, потом анимированная этими костями ткань дополнительно сталкивается, мнётся и апплятся прочие констрейнты алгоритмами, «родства не помнящими» (т. е. не зависящими только от текущего состояния).
3. Механизм «пропускабельных» заданий с сортировкой по приоритету (например, анимаций) их можно дропать, если не хватило времени, но это понижает автобалансируемое качество и наполняет в основном вижуале «чашу терпения». То есть, если модель не анимировали несколько кадров и чаша терпения переполнилась - следующее задание на анимацию объект выставит обязательным к исполнению.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 958
Зарегистрирован: 06.06.2005 15:54:34

Re: Cheb's Game Engine

Сообщение runewalsh » 22.08.2022 16:52:07

Cheb писал(а):Изпользуемый гапи - OpenGL / GL ES, что налагает ограничение: все команды на рендер - только в основном потоке.

В теории, shared contexts для того и задуманы, чтобы это обойти, они позволяют заниматься вещами вроде загрузки шейдеров и других ресурсов в отдельном потоке. Сам не пробовал...
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 571
Зарегистрирован: 27.04.2010 00:15:25

Re: Cheb's Game Engine

Сообщение Cheb » 23.08.2022 02:00:52

Не хочу связываться, нет смысла. Уровень графики, расчитанный на малину - на пк будет летать по любому. А для малины есть автомасштабирование.

Анимированная модель создаётся так: самый низкий лод собирается по одной вершине, по одному тетрокселю - а потом сабдивижен и коррекция формы, ещё сабдивижен и ещё коррекция. Лодам - быть. Автомасштабирование сделает остальное.

В идеале, самый нижний лод - 5к вершин. Учитывая симметрию - это 2.5 к вершин расставить вручную. Вполне задача по силам.

Добавлено спустя 7 минут 6 секунд:
З.Ы.
Шейдеров едва дюжина на всю игру будет.
Все текстуры из файлов - на старте, сразу и целиком: их тоже ничтожно. Текстурирование моделей - патчами и декалями, которые натягиваются на скин на лету, в динамике создавая его в нужном разрешении. Сами патчи и декали - 256х256 или меньше.
Текстурирование ландшафта - фракталями и теми же патчами.

То есть, в первой игре на движке "традиционных" ресурсов хорошо, если наберётся на десяток мегабайт.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 958
Зарегистрирован: 06.06.2005 15:54:34

Re: Cheb's Game Engine

Сообщение Alex2013 » 23.08.2022 11:48:31

Cheb писал(а):Какан
(果敢 [kakan] ~na кн. решительный, смелый, отважный)

А? Яндекс перевел как "жирный" ... :wink: (А я спросони вообще прочел как "канкан" :D )
Но в принципе интересно. Плюс если юзается то OpenGL ES то по идее напрашивается порт на Андроид .
(Может смартфоны с планшетами и полудохлая платформа для графического движка, но уже понятно что следующее поколение носимых гаджетов и замена дисплеев (в основном разные очки "расширенной реальности" и "проецируемые интерфейсы" ) точно будет в основном на андройде )
Alex2013
долгожитель
 
Сообщения: 2510
Зарегистрирован: 03.04.2013 11:59:44

Re: Cheb's Game Engine

Сообщение Cheb » 26.08.2022 09:59:35

Alex2013 писал(а):Может смартфоны с планшетами и полудохлая платформа для графического движка,

Полудохлая?.. Ну, я же целю на Raspberry Pi 2, как дно системных требований.
Получится ли? Увидим. Если не получится - повышу до Raspberry Pi 4 / Orange Pi 3. Они на али со скидкой сейчас.

Проблема с планшетами - в адаптации управления. (и освоении андроида с нуля, тоже) Прежде, чем смотреть в том направлении - игра должна быть, быть вылизанной, оттестированной и популярной.
Управление будет заточено по клавиатуру и мышь. И только потом портировано на геймпады и прочую экзотику.
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 958
Зарегистрирован: 06.06.2005 15:54:34

Re: Cheb's Game Engine

Сообщение Alex2013 » 02.09.2022 11:24:05

Cheb писал(а):Проблема с планшетами - в адаптации управления. (и освоении андроида с нуля, тоже) Прежде, чем смотреть в том направлении - игра должна быть, быть вылизанной, оттестированной и популярной.
Управление будет заточено по клавиатуру и мышь. И только потом портировано на геймпады и прочую экзотику.


Адаптация управления для планшета не настько страшная штука как может показаться . ( Я уже пробовал делать адаптацию к сенсорному экрану в своих программах - получается неплохо )
Изображение
В принципе это просто чуть допиленное управление мышкой + несколько виджетов с виртуальными кнопками (нужно только учитывать что там будет не кратковременный клик, а "удержание нажатия" ). Ну и нужно учивать особенности сенсорного управления при вводе данных . А экзотика вроде акселерометра или гиродатчика вещь хорошая но к использованию необязательная .
Что касается всяких джойстиков и геймпадов то они давно легкого подключаются через банальный директ-плей .
Alex2013
долгожитель
 
Сообщения: 2510
Зарегистрирован: 03.04.2013 11:59:44

Re: Cheb's Game Engine

Сообщение Cheb » 03.10.2022 19:59:09

Alex2013 писал(а):это просто чуть допиленное управление мышкой + несколько виджетов с виртуальными кнопками

Ну, вот чтобы это запилить - надо иметь готовый, отлаженный геймплей, чтобы 100500 раз не переделывать. А ковыряться уже конкретно с удобством размещения этих кнопок.

Всё время отвлекаюсь, то одно, то другое, то нервы отпаивать приходится...

Потихоньку вырисовывается структура апи. По ходу переделываю, когда получается *слишком* криво.
Очень многого поначалу тупо не будет, поскольку у планируемой игры ресурсов - кот наплакал, на милисекунды загрузки. В частности:
- параллельная фоновая загрузка ресурсов
- экран загрузки с индикатором загрузки
Экран загрузки - это будет просто штатный рендер консоли с включённой заливкой фоновой статической картинкой. Пока логика тормозит, парся модели - рисуется консоль. Пока задание на рендер проваливается (т.к. штатно отрабатывает ограниченное по времени количество ресурсов за кадр) - консоль продолжает рисовать фон поверх этой неготовой картинки. И только с первого успешно прорисованного кадра снимается флажок "консоли рисовать фон" а логика снимается с принудительной паузы.

Образчик использования (пока не компилировался):
Для простоты пришлось очистку сделать вижуалом (как, например, текстура, только временный)
Код: Выделить всё
class procedure TKakan.ClearWindowboxing; // adds states and jobs to clear windowboxing margins around the virtual rect;
  c: float;
  s: PKakanState;
  j: PKakanJob;
  ql, qr, qt, qb: integer;
  procedure OneClear(ax, ay, aw, ah: integer);
  var
    r: PClearInnards;
  begin
    s:= NewState;
    s^.Scissor(ax, ay, aw, ah);
    j:= NewJob(Mother^.Visual.Create(aki_Clear, nil)); // transient
    r:= Mother^.Visual.ExposeInnards(j^.Visual);
    with r^ do begin
      Color:= true;
      r:= c;
      g:= c;
      b:= c;
      a:= float(1.0);
      Depth:= true;
      DepthVal:= 1.0;
      Stencil:= true;
      StencilVal:= 0;
    end;
  end;
begin
  if Mother^.Kakan.GAPI = gapi_None then Exit;

  with Mother^.Display.ProjectionOfVirtualRectToClientRect do begin
    qt:= Top;
    ql:= left;
    qr:= Mother^.Display.ClientRect.Width - Width;
    qb:= Mother^.Display.ClientRect.Height - Height;
  end;

  if (ql> 0) or (qr > 0) or (qt > 0) or (qb > 0) then begin
    s:= NewState;
    s^.SetStatesForGUI;
    with Mother^.Display.ClientRect do s^.SetViewport(Left, Top, Width, Height);
    c:= 1.0 * Mother^.Display.FadeIn * Mother^.Display.WindowBoxingBrightness;
    if ql > 0 then OneClear(0, 0, ql, Mother^.Display.ClientRect.Height);
    if qt > 0 then OneClear(max(ql - 1, 0), 0, Mother^.Display.ClientRect.Width - max(qr + ql - 1, 0), qt);
    if qr > 0 then OneClear(
      Mother^.Display.ProjectionOfVirtualRectToClientRect.Left
        + Mother^.Display.ProjectionOfVirtualRectToClientRect.Width,
      0, qr, Mother^.Display.ClientRect.Height);
    if qb > 0 then OneClear(max(ql - 1, 0),
      Mother^.Display.ProjectionOfVirtualRectToClientRect.Top
        + Mother^.Display.ProjectionOfVirtualRectToClientRect.Height,
      Mother^.Display.ProjectionOfVirtualRectToClientRect.Width - max(qr + ql - 1, 0), qb);
  end;
end;
Аватара пользователя
Cheb
энтузиаст
 
Сообщения: 958
Зарегистрирован: 06.06.2005 15:54:34

Пред.

Вернуться в Разработки на нашем сайте

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 2

Рейтинг@Mail.ru