Warning: Undefined array key 0 in /var/www/tgoop/function.php on line 65

Warning: Trying to access array offset on value of type null in /var/www/tgoop/function.php on line 65
224 - Telegram Web
Telegram Web
Запись с последнего события про UI.Windows:
https://youtu.be/3Yn2BxORQO4

#record #ui #event
Вместе с https://ragon.io/ и @edmand46 ME.BECS теперь очень дружит, реализован транспорт, можно из коробки использовать тестовый транспорт, Photon или Ragon.

#ecs #becs #network
Есть CPU, есть GPU.

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

Между ними существует промежуточный буфер. Буфер команд, очередь если точнее.
CPU наполняет этот буфер. а GPU забирает из него и удаляет команду, которую забрал.

Если CPU заполняет буфер быстрее, чем GPU потребляет, то рано или поздно он упрется в максимальный размер буфера. В таком случае мы говорим, что у нас GPU bound, т.к. центральный процессор простаивает по вине GPU.

И наоборот - если GPU слишком быстро потребляет команды, а CPU не успевает наполнять буфер - то простаивает GPU и у нас CPU bound.
(это, разумеется, не все причины почему у нас может возникнуть баунд).

Любое сообщение CPU и GPU - это команда.

В понимании графического ускорителя не существует такого понятия как drawcall и setpass call. Об этом ниже.

Есть "состояние пайплайна" - "state" и команды отрисовки (нарисуй просто, нарисуй с буфером индексов, нарисуй индирект, нарисуй инстансингом, итд).
(Причём на разных графических API есть разная "детализация" этого состояния).

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

В старых API - DX11 и OpenGL / GL ES - стейт один на весь GPU. В новых - можно их на CPU собирать, кэшировать и экономить на сборке.

Допустим, говорим про GL.
Там ты стейт собираешь как колоду карт, по очереди, отдельными командами, каждый кадр заново:

- Вот текстурка в этот слот.
- Вот буфер в этот слот.
- Вот шейдерная программа для вертексов
...
- Вызов команды отрисовки.

Можно воспринимать этот вызов отрисовки как нажатие кнопки на блендере. Нажал - и всё применилось.

При этом разные команды для изменения состояния GPU - имеют разную "стоимость".
Стоимость эта применяется дважды - сначала CPU надо её выполнить - т.е. подготовить для неё данные, провалидировать и поместить команду и данные в буфер.

Затем, при считывании, драйвер GPU должен взять эту команду и применить её в свой стейт.

Дроуколл - это термин программистов, обобщение, для вызова отрисовки + набор команд на изменение стейта, которые надо доприменять после прошлого дроуколла.

- | тут-старый-дроуколл |

-блабла
-блаблабла
-отрисовка.

Вот эти три команды - дроуколл.

Чтобы минимизировать оверхед на вот эти мотыляния стейта туда-сюда, программисты пытаются "батчить" команды. Ведь после вызова команды отрисовки, драйвер не сбрасывает стейт. И если есть команды, которые можно выполнить с одним и тем же стейтом - мы можем здорово сэкономить.

Такая пачка команд называется "батч".
А самый первый и самый длинный, долгий, тяжелый дроуколл, когда у нас выставляется весь стейт - это SetPass Call.

"Идеальный" батч можно посмотреть рендердоком на партикл системе (которая шурикен). Там один сетпассколл и много дроуколлов из одной команды отрисовки.

Обычно у нас таких идеальных батчей больше нет нигде :))

Даже в том же SRP батчере у нас в дроуколл'е приходится мапить небольшие участки буферов, вертексные-индексные буферы, текстуры.

Но это всё равно изрядно дешевле, чем каждый drawcall выстраивать стейт с нуля.

p.s. и на десерт :)
Современные драйверы умеют анализировать загрузку GPU и могут выполнять несколько разных дроуколлов одновременно. Важный фактор - чтобы они не зависели друг от друга. Так, к примеру, GPU может считать вертексный шейдер одного меша в одном дк и фрагментный другого в другом.

Автор: @shiko_q
Источник: https://www.tgoop.com/unity_cg/52328

#cpu #gpu #rendering
Всем привет! Мы добрались до релиза открытой беты с одной из наших игр. Буду рад, если вы посмотрите наш проект.
Скоро будет еще анонс другого проекта;)

🌲 Wild Forest 🌲 Сезон 0 Открытая Бета уже доступна! 🚀

🎮 Погрузитесь в эпические сражения в самой захватывающей бете года.

Исследуйте и сражайтесь⚔️ в мире 🌲Wild Forest🌲 на Android и iOS!

🌐 Просто посетите playwildforest.io, чтобы начать играть прямо сейчас!

Ваш прогресс во время открытой беты будет преобразован в эксклюзивные награды NFT. 🏆

Готовы начать свое путешествие?🔥 БЕСПЛАТНЫЙ ПРЕМИУМ БАТЛ ПАСС уже ждет вас, инструкции внутри игры.

#wildforest #game #release
Большинство ПК рендерят IMR - Immediate Mode Rendering.
Что и как подали на вход - так и отрендерили, в том же порядке. Стандартный графический апи - примитив, растеризация, шейдинг фрагмента.

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

Но наиболее эффективный - это использовать хардварный Z-Buffer, он же буфер глубины.
(Вот именно за этим он и нужен в первую очередь, а не чтоб пенку у острова рисовать, блин.)

Нюанс IMR в том, что при каждой отрисовке фрагмента, ты должен взять и подать из глобальной памяти (VRAM) глубину и стенсиль для этого фрагмента.
VRAM хоть и быстра, но это не кэш, фетч из неё, по ритму жизни обычных инструкций (типа сложения-умножения на гпу) может занимать месяцы если не годы.
Любой чих-пых на шине (это мостик между памятью и кэшем на гпу) стоит энергии, сиречь электричества. Где электричество, там теплопотери, нагрев и необходимость всю эту радость охлаждать.

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

TBDR - Tile-Based Deferred Rendering.

Все инструкции что ведут до SV_Position + разбиение по примитивам выделяются в отдельную часть шейдера и выполняются здесь и сейчас.

А затем нужно выполнить для позиций примитивов биннинг (Qualcomm) или же тайлинг (Mali, PowerVR). Разница между ними не так уж велика.

Весь бэкбуфер разделен, хардварно, на небольшие участки. Ты мог иногда видеть артефакты биннинга, когда у тебя на экране есть крупные (относительно) прямоугольники, в которых изображение норм, а есть прямоугольники, где не норм, так вот это оно.
Работу эту выполняется отдельный хардварный модуль - тайлер. У него тоже есть предел нагрузки и если у тебя много геометрии, или она распределена так, чтобы покрывать множество бинов - ты вполне можешь достичь предела его способностей (у меня были такие кейсы в практике).

Вся геометрия после геометрического этапа распределяется по этим участкам-бинам и отправляется в системную память. И только потом по каждому отдельному бину/тайлу начинаются операции по растеризации-интерполяции-фрагментному-шейдингу.

Вся геометрия - в смысле вообще вся. Т.е. у тебя сначала все вертексные шейдеры ВСЕХ твоих дроуколлов выполняются (точнее, позиционная часть вертексных шейдеров) и только потом все остальные вычисления, в т.ч. шейдинг фрагментов

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

Синхронизация - смена рендертаргета. Т.е. в рамках одного таргета - всё так. Меняем таргет - всё по новой.

Цель сих манипуляций в том, что на гпу есть специальный кэш - тайловая память, где обычно и лежат данные глубины и стенсиля. Кэш маленький, зато свой. Размером под тайлик.
Когда мы растеризацию-интерполяцию делаем, мы в кэш этот глубину и стенсиль per-fragment и складываем, и с ней же сравниваем. И там же цвет пикселя лежит для операций блендинга.
После резолва текстуры - результат (т.е. те данные что ты в начале пометил как нужные для резолва, как правило - цвет, реже - глубина + стенсиль) отправляются в системную память. Если резолв не требуется - едут в бэкбуфер и далее - на экран девайса.

На этот же принцип (бины/тайлы, кэш на гпу) опирается рендерпасс апи в вулкане, чтобы делать деферред рендеринг и не гонять промежуточные данные между гпу и шареной мобильной памятью.

p.s. подробнее тут:

https://developer.samsung.com/galaxy-gamedev/resources/articles/gpu-framebuffer.html

https://developer.apple.com/documentation/metal/tailor_your_apps_for_apple_gpus_and_tile-based_deferred_rendering

Пост: https://www.tgoop.com/unity_cg/57772
Автор: @shiko_q

#rendering #graphics
Поговорим про RenderBufferLoadAction и RenderBufferStoreAction

В обычном растер пайплайне на гпу, пожалуй самым важным и обязательным условием является установка рендертагрета.

Это так называемый бэкбуфер, куда будет записан (или не записан) результат работы всех этих вертекс-фрагмент (и прочих) операций.

Есть условная команда "установить рендертаргет".
При установке, тебе нужно задать несколько параметров:

- Color буфер (их может быть и несколько)
- Depth/stencil буфер (это один буфер, глубина и стенсиль просто лежат в разных битах)
- Нужно ли чистить буферы и если да - то каким значением (типа цвет - черный, белый, итд, глубина - 0, 1, или иное, аналогично для стенсиля).
- Вьюпорт
- Scissor rect (обычно никто его не трогает, ограниченно полезный лишь на пк)
- Load/Store действия.

Вот про них мы и поговорим.
Каковы последствия, если неправильно проставил?
Либо получишь некорректный результат на экране (с артефактами, например), либо удвоишь нагрузку на шину и получишь просадку производительности на ровном месте.

Load:

Отрендерил ты кубик в экран, но этот кубик рисуется же не на пустом месте - он рисуется на участке текстуры. А что делать с теми пикселями, которые были до кубика в этой текстуре?

Допустим, у тебя кубик полупрозрачный, а текстура не пустая - там картинка какая-то.
Чтобы альфабленд сработал - тебе нужны данные пикселей этой текстуры, где у тебя пиксели кубика нарисуются, чтобы их сблендить и результат получить. Для этого и нужен LoadAction.Load

Поэтому ЕСЛИ текстура не пустая И либо у тебя альфабленд, либо ты не все пиксели перерисуешь (типа будут участки не затронутые ни одним мешем - такого не бывает при блите, конечно, но вдруг у тебя не фуллскрин блит) - то тебе нужен
LoadAction.Load.

Иначе - DontCare + обязательно после установки таргета сделать cmd.ClearRenderTarget. Иначе магия может не сработать.

Store:

Обычно тебе нужно сохранять результат рендера в текстуру, иначе как бы, нафига рендер нужен, правда?
Но не всегда.

Скажем, тебе обычно не нужна глубина и стенсиль. Т.е. они тебе нужны на то время, пока ты рендеришь всякие кубики, партиклы. Но в это время глубина-и-стенсиль лежат в тайловой памяти на гпу и прекрасно выполняют свою функцию сравнения. А вот после окончания рендера тебе, допустим, только цвет и нужен. Значит для глубины можно поставить StoreAction.DontCare.
Для шедоумапы, к примеру, тебе цвет не нужен. Значит там - StoreAction.DontCare, а глубина - нужна. Там - Store.

Отдельный геморрой это MSAA. И если он включен, то с ним StoreAction'ы работают чуть иначе и менее очевидно. Но это уже другая история.

Вот так это чудо можно дебажить:
https://docs.unity3d.com/ScriptReference/Rendering.LoadStoreActionDebugModeSettings.html

Либо через рендердок

Автор: @shiko_q
Источник: https://www.tgoop.com/unity_cg/62577

#rendering #graphics
Между работой и работой решил написать небольшой пост :)

Почти закончил с поиском пути.
В этот раз я подошел основательно:
Во-первых, я соединил несколько подходов в один: тут и A* и FF, да и придумал как это все соединить, чтобы было производительно;
Во-вторых, это все под burst ессно и будет частью ME.BECS;
В-третьих, помимо самого поиска пути я сделал NavAgentов, которые умеют в steering и базовый rvo.

Скоро напишу больше подробностей, сейчас много работы.

stay tuned
https://www.unsafecsharp.com/blog/%D0%9F%D0%BE%D0%B8%D1%81%D0%BA-%D0%BF%D1%83%D1%82%D0%B8

Написал про то как работает поиск пути. Буду рад вопросам и комментам. В посте есть картинки (возможно, что ван нужен впн).

#pathfinding #unity #flowfield #astar
UnityEngine.Object == null

Мне не так давно написал один из разработчиков на проекте, который застрял на проблеме зомби объектов.

Зомби объект — жив потому что GC не может его собрать, так как в стэке есть ссылка на него, но сам объект уже Destroyed.

И дело тут не в том, что GC.Collect еще не вызвался, а в том что мы храним указатель на объект, который уже уничтожен.

Такую ситуацию очень легко получить:
🔹Берем любой класс наследник MonoBehaviour
🔹 Реализуем в нем любой интерфейс
🔹 Instantiate'им объект, сохраняем в переменную
🔹 Во вторую переменную cast'им наш объект к интерфейсу
🔹 Уничтожаем MonoBehaviour
Через Object.Destroy или Object.DestroyImmediate
🔹 Пытаемся вызвать поле или метод интерфейса

Вопросы:
🔸 Будет ли MonoBehaviour == null?
🔸 Будет ли переменная интерфейса == null?
🔸 Будет ли ошибка при вызове любого member'а интерфейса?

Подсказка

Ответы:
👍 Да
👎 Нет
И да и нет.
Если вызваемый мембер не часть MonoBehavior имплементации - ошибки не будет
В остальных случаях будет MissingRefere
nceException

Почему так?
Потому что любой UnityEngine.Object имеет 2 runtime части:
🔹Одна на стороне unity (написанная на C++)
🔹Вторая на стороне CLR (Common Language Runtime) — класс/структура C#

Это значит:
🔸 CLRObject может смотреть уже на уничтоженный UnityObject ровно столько, сколько мы храним указатель на него
🔸 Если UnityObject уничтожен, мы все еще можем обратиться к его CLR части и взять оттуда любые данные, которые не является частью UnityObject
🔸 Момент сборки данного объекта GC может быть отложен на сколько угодно по времени

Это ведет к проблемам:
♦️ Утечки памяти по всему проекту.
UnityObject уничтожен и нам нужно удалить объект из коллекции, а мы не можем, потому что interface != null
♦️MissingReferenceException, в неожиданных местах со вкусом фрустрации и сложной отладки

4 возможных решения:
1️⃣ Проверять все экземпляры типа интерфейса методом:

bool IsNullUniversal<T>(T instance)
{
  if (instance is Object unityObject)
    return unityObject == null;

  return instance == null;
}

2️⃣ Для абстракции ВСЕХ монобехов использовать только abstract классы
3️⃣ Наследовать все интерфейсы от IEquatable<T> и использовать везде метод Equals вместо ==
4️⃣ (самый быстрый, самый дерзкий)
Через UnsafeUtility читать m_CachedPtr и m_InstanceID и через рефлексию дергать метод DoesObjectWithInstanceIDExist

Почему именно так:
🔸UnityObject содержит перегрузку оператора == и != которая проверят что нативная (C++ часть) "жива"
🔻Но в CLR части == транслируется в операцию seq, которая в после IL2CPP будет выглядеть так:
((((RuntimeObject*)instance) == ((RuntimeObject*)NULL))? 1 : 0)

Или проще говоря в обычное сравнение 2ух указателей.

Такой проверки в случае реализации интерфейса в UnityObject не достаточно.
Потому мы получается false-negative результат при сравнение на null, который потенциально ведет к утечкам памяти

Я создал Gist в котором расписал подробно TestCase'ы и решение.
Не стесняйтесь его дополнить или предложить свой вариант в комментариях 😎

Об архитектуре проектов на unity я пишу в своем канале, подписывайся❤️‍🔥

Специально для @unsafecsharp
https://www.unsafecsharp.com/blog/mebecs-пишем-простую-систему-перезарядки

Написал небольшой пост о том как выглядит простая система на ME.BECS.
Хотелось бы от вас услышать комменты что не понятно, что переусложнено, что можно сделать проще.

#mebecs #tutorial
https://youtu.be/EGHdtjmG0Jw

Записал небольшой ролик про то как работает сейчас мой прототип RTS. Тут есть и поиск целей, и туман войны, и поиск пути. Ну и пара тысяч юнитов 🙂
Нет никакой оптимизации графики, на написание и тестирование логики это никак не влияет.

#mebecs #ecs #rts #prototype
Сделал супербыстрые pixel-perfect health-бары для юнитов :)

За реф брал бары из Starcraft II. В принципе получилось по рендеру очень быстро и в 1 проход :) Правда с кастомизацией придется повозиться. Если интересно - задавайте вопросы.

На текущий момент готово:
- Поиск целей
- Поиск пути
- Туман войны
- Отрисовка health-баров
- Группы юнитов

Stay tuned ;)

#rendering #mebecs #ecs
https://youtu.be/w10n9d-uO7k?si=UoifmwqVfr1pRqMr

Разбирались тут со @Mefodei как устроен демо проект. Он уже довольно далеко ушел от того, что в видео, но все равно должно быть интересно;)

#record #mebecs #ecs #demo
В общем, мы тут с @shiko_q подумали как можно сделать джобу с поддержкой зависимостей на мейн треде. Ну чтобы можно было бы просто делать что-нибудь в главном потоке, но при этом не делать Complete джоб перед этим. И вот что вышло:

https://github.com/chromealex/Unity.MainThreadJob

#jobs #unity #api #hacks
А вы знали, что юнити из коробки поддерживает дебаг пикселя по клику?)

Для URP надо включить дефайн ENABLE_SHADER_DEBUG_PRINT в проекте (для HDRP, кажется, оно и так будет работать).

Затем добавить в шейдер строчку
// #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/ShaderDebugPrint.hlsl"

А в самом шейдере вызвать
ShaderDebugPrintMouseButtonOver(int2(input.positionSS.xy), ShaderDebugTag('S','m','o', 't'), fragData.smoothness);

И тогда при клике ЛКМ по какому-либо месту на экране, в консоль будет написано
Frame #270497: Smot float4(0.1f, 0.2f, 0.3f, 0.4f) - ну, или что у вас там в вашем цвете.

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

Так можно дебажить свой сложный кастомный шейдер.
Особенно хорошо работает в сочетании с окном Rendering Debugger.

Пост: https://www.tgoop.com/unity_cg/74386

#rendering #urp
int Method(IInterface obj) {
...
return obj.Calc();
}

public struct S1 : IInterface {…}
public struct S2 : IInterface {…}

void Update() {
Method(new S1());

Method(new S2());
}

interface IInterface {
int Calc();
}


Чего я только не слышу про этот код на собесах. Тут 2 вопроса:
1. Что не так с этим кодом? Может быть и все так.
2. Как исправить?

И знаете, я вот думаю этот вопрос сделать самым первым на собесе, т.к. я слышу такие ответы:
1. Я бы сделал базовую структуру...
2. Можно сделать ref IInterface
3. Можно поменять struct на class
4. Можно сделать IInterface obj, out int...
5. Поменять struct на class + хранить их в static полях, оттуда забирать когда надо
6. Придумайте свой идиотский вариант

#interview #unity
Как перемещать персонажа по точкам правильно

Многие из вас делали перемещение персонажа по нодам. Например, когда поиск пути вернул массив точек, а нам нужно персонажем пройти по ним.
Обычно такой код выглядит примерно так:


var nextPoint = points[index];
if ((nextPoint - position).sqrMagnitude <= MIN_DISTANCE_SQR) {
++index;
if (index >= points.Length) {
// Мы дошли до конца пути
return;
}
}

position = Vector3.MoveTowards(position, nextPoint, dt * speed);


Логика понятная: дошли до точки - берем следующую и идем к ней, и так пока не дойдем до конца.

Но в таком подходе кроется одна проблема: если персонаж проходит за кадр 1 метр, а расстояние до точки 0.5 метра, то персонаж будет проходить на самом деле меньшее расстояние, чем должен был:


-[]--[]--[]--[]--[]
---------------[] // Этот персонаж дойдет до конца быстрее, чем первый


Что делать?

На самом деле нужно использовать примерно такую логику:


var distance = speed * dt;
while (distance > 0f) {
var nextNodePos = points[index];
var distanceToNextNode = (nextNodePos - currentPos).magnitude;
if (distance >= distanceToNextNode) {
distance -= distanceToNextNode;
currentPos = nextNodePos;
++index;
if (index >= points.Length) break;
continue;
}
var direction = (nextNodePos - currentPos).normalized;
currentPos = direction * distance;
break;
}


Метод HasReached должен проверять "перешли ли мы точку или еще нет". Таким образом, мы "перебрасываем" часть длины, которую мы прошли на новую точку, а если перешли и ее, то еще раз и так пока либо не закончится этот хвост, либо мы не дойдем до конца.
Грубо говоря, если персонаж будет двигаться со скоростью 1000 метров в секунду, а контрольных точек на пути будет много (например, каждый метр), то за секунду он пройдет ровно 1000 метров, а в первом варианте намного меньше.

#unity #algorithms #movement
2025/07/05 14:00:22
Back to Top
HTML Embed Code: