Когда ты инди-разработчица, то приходится делать абсолютно все: продумывать геймдизайн, разрабатывать геймплейные фичи, инструменты, оптимизировать свой же код и графику, рисовать текстуры и моделировать объекты. Направлений просто куча! 😧
Каждое из этих направлений интересно по своему: где-то можно прокачаться в сохранении байтиков и получении более маленьких циферок в Profiler, а где-то можно почувствовать себя художником (может даже не от слова "худо"🤭 ) и воплотить свои идеи в реальность в виде различных красивостей.
Последнюю неделю я изучала Adobe Substance 3D Designer: очень мощный инструмент для создания разнообразных текстур. Правда оказался достаточно сложным для меня: после тысячи часов за кодом снова вернуться к арту - задачка та еще)
Зато этот инструмент довольно близок ко мне по духу: шум, много шума, очень много шума и различные операции над ним. Тяп-ляп, и текстура готова😄 Процесс почти такой же, что и у меня в генераторе мира)
#texturing
Каждое из этих направлений интересно по своему: где-то можно прокачаться в сохранении байтиков и получении более маленьких циферок в Profiler, а где-то можно почувствовать себя художником (может даже не от слова "худо"
Последнюю неделю я изучала Adobe Substance 3D Designer: очень мощный инструмент для создания разнообразных текстур. Правда оказался достаточно сложным для меня: после тысячи часов за кодом снова вернуться к арту - задачка та еще)
Зато этот инструмент довольно близок ко мне по духу: шум, много шума, очень много шума и различные операции над ним. Тяп-ляп, и текстура готова
#texturing
Please open Telegram to view this post
VIEW IN TELEGRAM
❤16🔥5😱2🥰1
Нас уже 300 человек! Удивительно ✨ Спасибо вам всем за поддержку! ❤️
Я немного отдыхала в последнее время и не работала над проектом, поэтому пока нового не покажу. Но, я скоро вернусь за работу и там будет очень много интересного по графике, по сетевой части, по геймплею и многому другому!
Я немного отдыхала в последнее время и не работала над проектом, поэтому пока нового не покажу. Но, я скоро вернусь за работу и там будет очень много интересного по графике, по сетевой части, по геймплею и многому другому!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥28❤7💋7🎉2⚡1
Написала статью по новому этапу разработки 👏 Будет серия постов и статей.
К сожалению, telegraph больше не поддерживается telegram, поэтому пришлось переехать на teletype🧂 Надеюсь вам будет удобно читать! 🥰
Русская версия: вот тут.
English: here.
#game_design #demo_scene
К сожалению, telegraph больше не поддерживается telegram, поэтому пришлось переехать на teletype
Русская версия: вот тут.
English: here.
#game_design #demo_scene
Please open Telegram to view this post
VIEW IN TELEGRAM
Teletype
Демо сцена. Концепт.
Любое начинание должно происходить с записывании мыслей на бумагу. Таким образом, они смогут помочь собрать из осколков сознания образ...
🔥14💋5❤2😍2
Entities (DOTS) и Wireframe режимы
Когда я только начала использовать DOTS и использовать BatchRendererGroup (BRG), то столкнулась с тем, что посмотреть сетку мешей в окне сцены невозможно. Переключалка в Scene View работает только для GameObject и MeshRenderer, а для сущностей, которых на сцене фактически то и нет, уже не работает. 😭
Учитывая, что вся геометрия у меня процедурно генерируется, посмотреть получившуюся сетку было жизненно необходимо. Я спасалась тем, что был отдельный debug MonoBehaviour, который умел генерировать отдельные чанки и запихивать их в MeshRenderer. Неудобно, скажите вы, и будете абсолютно правы. 😔
Что же делать? Использовать Rendering Debugger! Подробное описание можно найти вот тут. В Unity его можно найти в меню Window -> Analysis -> Rendering Debugger. Там на самом деле множество полезных инструментов для дебага графики, но что меня порадовало в свое время - есть wireframe режимы, которые работают и с сущностями!
Чтобы переключить wireframe режим, надо найти выпадающий список по пути Rendering -> Rendering Debug -> Additional Wireframe Modes в окошке Rendering Debugger. Вдруг кому-нибудь полезно будет ☺️
#dots
Когда я только начала использовать DOTS и использовать BatchRendererGroup (BRG), то столкнулась с тем, что посмотреть сетку мешей в окне сцены невозможно. Переключалка в Scene View работает только для GameObject и MeshRenderer, а для сущностей, которых на сцене фактически то и нет, уже не работает. 😭
Учитывая, что вся геометрия у меня процедурно генерируется, посмотреть получившуюся сетку было жизненно необходимо. Я спасалась тем, что был отдельный debug MonoBehaviour, который умел генерировать отдельные чанки и запихивать их в MeshRenderer. Неудобно, скажите вы, и будете абсолютно правы. 😔
Что же делать? Использовать Rendering Debugger! Подробное описание можно найти вот тут. В Unity его можно найти в меню Window -> Analysis -> Rendering Debugger. Там на самом деле множество полезных инструментов для дебага графики, но что меня порадовало в свое время - есть wireframe режимы, которые работают и с сущностями!
Чтобы переключить wireframe режим, надо найти выпадающий список по пути Rendering -> Rendering Debug -> Additional Wireframe Modes в окошке Rendering Debugger. Вдруг кому-нибудь полезно будет ☺️
#dots
🔥19❤4👀4💋2
Демо-сцена. Форма ландшафта.
Продолжаю писать по разработке демо-сцены. В этот раз коснулась моментов генерации ландшафта и разработки его базовой формы. Это не окончательная форма ландшафта, она будет дорабатываться в дальнейшем, так как разработка - итеративный процесс.
Русская версия: вот тут.
English: here.
Приятного чтения!🥰
#generation #demo_scene
Продолжаю писать по разработке демо-сцены. В этот раз коснулась моментов генерации ландшафта и разработки его базовой формы. Это не окончательная форма ландшафта, она будет дорабатываться в дальнейшем, так как разработка - итеративный процесс.
Русская версия: вот тут.
English: here.
Приятного чтения!
#generation #demo_scene
Please open Telegram to view this post
VIEW IN TELEGRAM
Teletype
Демо сцена. Форма ландшафта.
Вторым этапом создания демо сцены является определение базовой формы ландшафта, которая будет исходной точкой для дальнейшей проработки окружения.
🔥10❤4🥰2💋1
Custom properties в DOTS Instancing
Как и в GPU Instancing, в DOTS Instancing есть определенные правила работы с полями, которые должны быть per instance.🧐
Если рассматривать как в DOTS Instancing устроено хранение данных для каждого инстанса, то стоит обратиться к Batch Renderer Group. А там, фактически, один буфер, где под каждое поле (будь то матрица или просто цвет) выделяется определенный кусок памяти. Дальше, с помощью оффсетов, которые можно вычислить, зная размер данных поля и количество интансов, можно получить доступ к конкретному значению конкретного поля для конкретного инстанса.
В шейдере же, необходимо использовать макросы, как для определения поля, так и для получения значения.
Определение поля:
И получение значения:
Но как же указать значение для инстансов?🤔
Когда мы работаем с GPU Instancing, то необходимо передать буфер per property, который мы можем как-то заполнить на стороне CPU. В случае же с DOTS Instancing, мы должны работать с буфером BRG и заполнять его. Это немного сложновато, но у меня есть небольшая реализация, которая упрощает этот процесс.
Но также есть и другой способ, если используется пакет Entities. Там мы можем указывать значения через компоненты!✨
Для этого необходимо создать небольшой компонент и повесить его на сущность, все остальное за нас сделает Unity:
#dots
Как и в GPU Instancing, в DOTS Instancing есть определенные правила работы с полями, которые должны быть per instance.
Если рассматривать как в DOTS Instancing устроено хранение данных для каждого инстанса, то стоит обратиться к Batch Renderer Group. А там, фактически, один буфер, где под каждое поле (будь то матрица или просто цвет) выделяется определенный кусок памяти. Дальше, с помощью оффсетов, которые можно вычислить, зная размер данных поля и количество интансов, можно получить доступ к конкретному значению конкретного поля для конкретного инстанса.
В шейдере же, необходимо использовать макросы, как для определения поля, так и для получения значения.
Определение поля:
#ifdef UNITY_DOTS_INSTANCING_ENABLED
UNITY_DOTS_INSTANCING_START(MaterialPropertyMetadata)
UNITY_DOTS_INSTANCED_PROP(float4, _ChunkDebugColor)
UNITY_DOTS_INSTANCING_END(MaterialPropertyMetadata)
#else
int _ChunkDebugColor;
#endif
И получение значения:
float4 debug_color = (float4)0;
#ifdef UNITY_DOTS_INSTANCING_ENABLED
debug_color = UNITY_ACCESS_DOTS_INSTANCED_PROP(float4, _ChunkDebugColor);
#else
debug_color = _ChunkDebugColor;
#endif
Но как же указать значение для инстансов?
Когда мы работаем с GPU Instancing, то необходимо передать буфер per property, который мы можем как-то заполнить на стороне CPU. В случае же с DOTS Instancing, мы должны работать с буфером BRG и заполнять его. Это немного сложновато, но у меня есть небольшая реализация, которая упрощает этот процесс.
Но также есть и другой способ, если используется пакет Entities. Там мы можем указывать значения через компоненты!
Для этого необходимо создать небольшой компонент и повесить его на сущность, все остальное за нас сделает Unity:
[Serializable]
[MaterialProperty("_ChunkDebugColor")]
public struct DebugChunkColorProperty : IComponentData
{
public float4 Value;
public static implicit operator DebugChunkColorProperty(float4 value)
{
return new DebugChunkColorProperty
{
Value = value
};
}
}
#dots
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12❤4👏3👍1
This media is not supported in your browser
VIEW IN TELEGRAM
Что такое Biome?
Биом - это правила генерации ландшафта, правила выбора вокселей, правила генерации окружения и многое другое.
Так как воксели генерируются у меня на стороне GPU (использую вычислительные шейдеры), то правила генерации ландшафта и выбор типов вокселей описываются на языке HLSL.
Фактически, это просто математическое уравнение. Довольно большое и сложное, но все же уравнение: на входе позиция в world space и зерно (seed) мира, а на выходе - дистанция для Signed Distance Field и вектор типов вокселей, которые могут быть в указанной точке пространства. Вот и все👏
#generation
Биом - это правила генерации ландшафта, правила выбора вокселей, правила генерации окружения и многое другое.
Так как воксели генерируются у меня на стороне GPU (использую вычислительные шейдеры), то правила генерации ландшафта и выбор типов вокселей описываются на языке HLSL.
Фактически, это просто математическое уравнение. Довольно большое и сложное, но все же уравнение: на входе позиция в world space и зерно (seed) мира, а на выходе - дистанция для Signed Distance Field и вектор типов вокселей, которые могут быть в указанной точке пространства. Вот и все
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17🥰3😍2❤1💋1
Демо-сцена. Базовые цвета ландшафта. Часть 1.
На этот раз затрагиваю тему материалов вокселей и базовых цветов ландшафта для демо сцены.
Это первая часть статьи, так как для продолжения необходимо вначале настроить освещение👏
Русская версия: вот тут.
English: here.
Приятного чтения!🥰
#generation #demo_scene
На этот раз затрагиваю тему материалов вокселей и базовых цветов ландшафта для демо сцены.
Это первая часть статьи, так как для продолжения необходимо вначале настроить освещение
Русская версия: вот тут.
English: here.
Приятного чтения!
#generation #demo_scene
Please open Telegram to view this post
VIEW IN TELEGRAM
Teletype
Демо сцена. Базовые цвета. Часть 1.
Telegram: Infinity World
❤10💋4🔥1😢1
Please open Telegram to view this post
VIEW IN TELEGRAM
❤18🔥12❤🔥4💋1
Начала работать над блокингом сцены: расставляю вначале крупные детали, потом перейду к средним, и в конце уже к мелким. 👏
Заодно дописываю генерацию, так как появляются разные небольшие моменты, о которых раньше и не предполагала) Например, для будущих камушков и скал на склонах надо учитывать нормаль, а не просто рандомно поворачивать🤔
#generation #demo_scene
Заодно дописываю генерацию, так как появляются разные небольшие моменты, о которых раньше и не предполагала) Например, для будущих камушков и скал на склонах надо учитывать нормаль, а не просто рандомно поворачивать
#generation #demo_scene
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤3🔥2💋2🆒2
Когда настраиваешь освещение/пост-процессинг/цвета, то очень быстро глаза "замыливаются" 🧂 Я с этим борюсь тем, что переключаюсь на другую задачу, и через некоторое время снова возвращаюсь к настройке.
Что получилось настроить на текущий момент внутренними средствами Unity по освещению и пост-процессингу можно увидеть на скриншотах🙌
Где:
1️⃣ Совсем без освещения и пост-процессинга.
2️⃣ Промежуточный вариант освещения и пост-процессинга. Уже лучше, но цвета тусклые + оттенок слишком сильно уходит в зеленый.
3️⃣ Текущий вариант. Цвета стали сочнее, зеленый оттенок стал меньше.
Это все еще не окончательный вариант, через некоторое время вернусь и попробую получить результат еще лучше и ближе к тому, что я хочу)
#demo_scene
Что получилось настроить на текущий момент внутренними средствами Unity по освещению и пост-процессингу можно увидеть на скриншотах
Где:
1️⃣ Совсем без освещения и пост-процессинга.
2️⃣ Промежуточный вариант освещения и пост-процессинга. Уже лучше, но цвета тусклые + оттенок слишком сильно уходит в зеленый.
3️⃣ Текущий вариант. Цвета стали сочнее, зеленый оттенок стал меньше.
Это все еще не окончательный вариант, через некоторое время вернусь и попробую получить результат еще лучше и ближе к тому, что я хочу)
#demo_scene
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍7🔥6👏2💅2
Детерминированность окружения
С этой взрослой жизнью и взрослыми делами совсем не получается выделить время на проект😭 Но дела рано или поздно заканчиваются, и часок-другой я все равно стараюсь найти)
Во-первых, ¡Hola! всем, кто меня читает, и muchas gracias!👋
Во-вторых, я столкнулась с небольшим нюансом, о котором хочу рассказать. Ранее я уже показывала немного видосиков и картиночек генерации мира с объектами окружения. Тогда мне казалось, что я решила задачу, но как это обычно бывает - я ошибалась.
Проблема:
При генерации объектов окружения самая сложная задача - это правильно вычислить уникальный детерминированный идентификатор объекта, по которому можно идентифицировать этот самый объект как в сохранениях мира (если есть изменения), так и между инстансами игры в мультиплеере.
Я привязывалась к мешу ландшафта в чанках: переводила координаты вершин в пространство вокселей и от этого считала идентификатор. Это будет работать, если во всех чанках мира только один уровень детализации (LOD), в ином случае - получаем недетерминированность. Из-за того, что на разных LOD меш содержит разные треугольники, можно получить некоторую вариативность в позиции в пространстве вокселей. Что приводит к неправильному положению объектов при смене LOD, неправильному идентификатору и дублированию объектов, невозможности связать данные сохранения и текущего мира и кучка других проблем.
Как решить?
Я пришла только к одному решению, которое будет работать всегда: считать позицию объектов окружения только в пространстве вокселей, причем всегда в LOD0. Это означает, что необходимо "догенеривать" воксели для чанков, у которых LOD выше 0. Ну, что ж, это плата за детерминированность.🧂
На данный момент тестирую этот подход, посмотрим чуть позже, что получится😶
#generation
С этой взрослой жизнью и взрослыми делами совсем не получается выделить время на проект
Во-первых, ¡Hola! всем, кто меня читает, и muchas gracias!
Во-вторых, я столкнулась с небольшим нюансом, о котором хочу рассказать. Ранее я уже показывала немного видосиков и картиночек генерации мира с объектами окружения. Тогда мне казалось, что я решила задачу, но как это обычно бывает - я ошибалась.
Проблема:
При генерации объектов окружения самая сложная задача - это правильно вычислить уникальный детерминированный идентификатор объекта, по которому можно идентифицировать этот самый объект как в сохранениях мира (если есть изменения), так и между инстансами игры в мультиплеере.
Я привязывалась к мешу ландшафта в чанках: переводила координаты вершин в пространство вокселей и от этого считала идентификатор. Это будет работать, если во всех чанках мира только один уровень детализации (LOD), в ином случае - получаем недетерминированность. Из-за того, что на разных LOD меш содержит разные треугольники, можно получить некоторую вариативность в позиции в пространстве вокселей. Что приводит к неправильному положению объектов при смене LOD, неправильному идентификатору и дублированию объектов, невозможности связать данные сохранения и текущего мира и кучка других проблем.
Как решить?
Я пришла только к одному решению, которое будет работать всегда: считать позицию объектов окружения только в пространстве вокселей, причем всегда в LOD0. Это означает, что необходимо "догенеривать" воксели для чанков, у которых LOD выше 0. Ну, что ж, это плата за детерминированность.
На данный момент тестирую этот подход, посмотрим чуть позже, что получится
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16❤5👏2⚡1🔥1
Иии, всем привет! 💃
Наверное многие уже решили, что я забыла про канал и больше не будет постов🫥 Спешу обрадовать - это не так! 🥔
Последние несколько месяцев были очень тяжелыми и насыщенными, и я просто не успевала заниматься еще и проектом. Иногда такое случается, но все это время я все равно старалась хотя бы подумать над тем, что и как должно работать и выглядеть.🙌
И, с января, я продолжу разработку и начну, пусть не очень часто, но снова писать посты💃 Планов полно, идей море, багов, которые ждут, что их пофиксят, еще больше! Заняться точно есть чем)
Всех с наступающими праздниками и хорошего начала года!🥰
Наверное многие уже решили, что я забыла про канал и больше не будет постов
Последние несколько месяцев были очень тяжелыми и насыщенными, и я просто не успевала заниматься еще и проектом. Иногда такое случается, но все это время я все равно старалась хотя бы подумать над тем, что и как должно работать и выглядеть.
И, с января, я продолжу разработку и начну, пусть не очень часто, но снова писать посты
Всех с наступающими праздниками и хорошего начала года!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥26🎄16❤6❤🔥2💋1
Всем привет! 💃
Я вернулась к коду, и первое, что я поняла: как же сложно вспоминать, на чем остановилась, спустя несколько месяцев!🔥
Но ничего, свежий взгляд, многое становится более явным, заодно какая никакая, но проверка временем🥔 Некоторые решения в коде теперь кажутся какими-то…странными что ли? 😛
Как вы помните, и как я вспомнила, я остановилась на проверке варианта «догенерации» вокселей в биоме по требованию, чтобы можно было правильно посчитать положение объектов на ландшафте. Самая большая проблема в том, что там много-много unsafe кода, и часть даже не обернуто во что-то более понятное😤
Ах, сама виновата🧂
#generation
Я вернулась к коду, и первое, что я поняла: как же сложно вспоминать, на чем остановилась, спустя несколько месяцев!
Но ничего, свежий взгляд, многое становится более явным, заодно какая никакая, но проверка временем
Как вы помните, и как я вспомнила, я остановилась на проверке варианта «догенерации» вокселей в биоме по требованию, чтобы можно было правильно посчитать положение объектов на ландшафте. Самая большая проблема в том, что там много-много unsafe кода, и часть даже не обернуто во что-то более понятное
Ах, сама виновата
#generation
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥25😁8❤7💯5🆒3
Всем привет! 🥰
Столкнулась тут с интересной штуковинкой: многие знают такой замечательный инструмент, как Profiler Analyzer, который жизненно необходим во время оптимизирования кода.
Так вот, давайте представим, что мы запускаем какую-то параллельную джобу (IJobFor). Нуу, допустим, 1000 раз:
Причем, мы хотим, чтобы вызовы Execute у нас не батчились, поэтому ставим циферку 1 вторым аргументом.
И что же я вижу в Profiler Analyzer? Count 15? И как это считать?🤔
Ваши идеи?🚪
Столкнулась тут с интересной штуковинкой: многие знают такой замечательный инструмент, как Profiler Analyzer, который жизненно необходим во время оптимизирования кода.
Так вот, давайте представим, что мы запускаем какую-то параллельную джобу (IJobFor). Нуу, допустим, 1000 раз:
var jobHandle = job.ScheduleParallel(1000, 1, default);
Причем, мы хотим, чтобы вызовы Execute у нас не батчились, поэтому ставим циферку 1 вторым аргументом.
И что же я вижу в Profiler Analyzer? Count 15? И как это считать?
Ваши идеи?
Please open Telegram to view this post
VIEW IN TELEGRAM
🦄7❤5🤔4⚡1❤🔥1
Ну, что ж, пришло время расти над собой 👏
В начале своего пути разработки генерации мира я не понимала многих концепций и часто считала одно лучше другого не понимая до конца какие-либо детали.
Например, я считала, что AoS (array of structures) хорошо подходит для хранения вокселей. Это утверждение правильно только в одном случае: если работа с вокселями происходит сразу со всеми данными в этой структуре одновременно.
Конечно, изначально в структурке Voxel я хранила только значение SDF. Но, код меняется, а значит пора пересмотреть некоторые вещи.
Я начала хранить больше информации на каждый воксель: добавился тип вокселя, веса для материалов, дополнительные флаги и прочее. И, как оказалось в итоге, каждое поле из этой структуры необходимо только для конкретных алгоритмов моей генерации. Я никогда не читаю целый воксель из памяти для того, чтобы поработать со всеми данными.
А что это значит? Пора переходить на SoA! Да! Structure of Arrays становится лучше для моей задачи! Но почему?
Давайте обратимся к циферкам.📝
1️⃣ Представим, что у нас есть чанк с 32³ вокселями (32 768 всего).
2️⃣ Размер кэш линии в среднем сейчас 64 байта (есть отличия между AMD и Intel в некоторых линейках CPU, но пренебрегаем).
3️⃣ Нам необходимо прочитать все воксели и взять их значение SDF.
🆘 AoS (Array of Structures), когда у нас одна структура , например SDF и тип вокселя:
- на один воксель 8 байт: 4 байта (SDF) + 1 байт (тип) + 3 байта для выравнивания
- один cache line помещает 8 вокселей: 64 байта делим на 8
- что в худшем случае дает нам 4 096 кэш миссов! (32 768 делим на 8) Худший случай - нам каждый раз нужен воксель из другого места.
✅ SoA (Structure of Arrays), когда мы значения храним в разных массивах:
- у нас отдельно лежит SDF, это 4 байта
- cache line вмещает 16 вокселей: 64 делим на 4
- что в худшем случае дает нам примерно 2000 кэш миссов: 32 768 делим на 16.
Разница большая!🗣
Конечно это просто цифры. В реальности ситуация не настолько плоха, мы часто читаем ближайшие элементы, из текущего кэш лайна. Да и кэш лайнов не одна штука. Но прикинуть "в худшем случае" бывает полезно, чтобы оценить, на сколько эффективно хранение данных у нас.
В начале своего пути разработки генерации мира я не понимала многих концепций и часто считала одно лучше другого не понимая до конца какие-либо детали.
Например, я считала, что AoS (array of structures) хорошо подходит для хранения вокселей. Это утверждение правильно только в одном случае: если работа с вокселями происходит сразу со всеми данными в этой структуре одновременно.
Конечно, изначально в структурке Voxel я хранила только значение SDF. Но, код меняется, а значит пора пересмотреть некоторые вещи.
Я начала хранить больше информации на каждый воксель: добавился тип вокселя, веса для материалов, дополнительные флаги и прочее. И, как оказалось в итоге, каждое поле из этой структуры необходимо только для конкретных алгоритмов моей генерации. Я никогда не читаю целый воксель из памяти для того, чтобы поработать со всеми данными.
А что это значит? Пора переходить на SoA! Да! Structure of Arrays становится лучше для моей задачи! Но почему?
Давайте обратимся к циферкам.
1️⃣ Представим, что у нас есть чанк с 32³ вокселями (32 768 всего).
2️⃣ Размер кэш линии в среднем сейчас 64 байта (есть отличия между AMD и Intel в некоторых линейках CPU, но пренебрегаем).
3️⃣ Нам необходимо прочитать все воксели и взять их значение SDF.
🆘 AoS (Array of Structures), когда у нас одна структура , например SDF и тип вокселя:
- на один воксель 8 байт: 4 байта (SDF) + 1 байт (тип) + 3 байта для выравнивания
- один cache line помещает 8 вокселей: 64 байта делим на 8
- что в худшем случае дает нам 4 096 кэш миссов! (32 768 делим на 8) Худший случай - нам каждый раз нужен воксель из другого места.
✅ SoA (Structure of Arrays), когда мы значения храним в разных массивах:
- у нас отдельно лежит SDF, это 4 байта
- cache line вмещает 16 вокселей: 64 делим на 4
- что в худшем случае дает нам примерно 2000 кэш миссов: 32 768 делим на 16.
Разница большая!
Конечно это просто цифры. В реальности ситуация не настолько плоха, мы часто читаем ближайшие элементы, из текущего кэш лайна. Да и кэш лайнов не одна штука. Но прикинуть "в худшем случае" бывает полезно, чтобы оценить, на сколько эффективно хранение данных у нас.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥26👏7🥰6
Неделя работы с памятью объявляется открытой! 🙌
У Алекса появился пост про память. Очень полезный в контексте Unity для разработчиков всех уровней.🥹 Кто не видел - очень советую глянуть его презентацию.
Я же хотела рассказать про такой аспект памяти, как ее фрагментация.
О том, как аллоцируются наши кусочки памяти, откуда они берутся и как потом возвращаются - мы очень редко задумываемся. Часто нас интересует сам факт аллоцирования, не больше. Но что происходит, когда мы "отпускаем" ненужный блок памяти? Он просто помечается как неиспользуемый.
Что будет, если мы захотим аллоцировать кусочек побольше? Мы не получим блок, который "освободили" на предыдущем шаге, так как он слишком маленький. Мы получим другой, более большой блок.🤔
А как же тот, маленький?😭 А он остается, и совсем не факт, что когда либо будет использован. И такие блоки, разного размера, которые "чуть-чуть" не подходят могут постоянно появляться, создавая множество дырок. Память становится как "решето", мы начинаем требовать все больше физической памяти RAM, а найти нужный свободный блок становится все тяжелее.
Это та самая фрагментация. И с этим надо что-то делать.📝
1️⃣ Например, использовать пулы памяти.
Выделяем большой кусок памяти, режем его на нужные нам кусочки одинакового размера и переиспользуем. Самый простой способ, который при этом очень эффективный.
2️⃣ Аллокаторы. Можно реализовать свои аллокаторы (arena, slab и другие) или взять готовые. Они фактически тоже пулы "под капотом", только более умные и позволяют использовать блоки памяти разного размера.
3️⃣ Аллокаторы с слиянием (coalescing). Еще более умная штуковина с этапом слияния свободных участков и последующим переиспользованием. Используется не так часто, потому что требует дополнительного прохода "упаковки" (да, кое-кого напоминает).
А как же аллокаторы от Unity?
- Temp и Temp Job: обычные arena аллокаторы.
- Persistent: это уже malloc/free и частые аллоцирования приводят к фрагментации.😔
P.S.: в комментариях уточнили, что persistent - это TLSF аллокатор
У Алекса появился пост про память. Очень полезный в контексте Unity для разработчиков всех уровней.
Я же хотела рассказать про такой аспект памяти, как ее фрагментация.
О том, как аллоцируются наши кусочки памяти, откуда они берутся и как потом возвращаются - мы очень редко задумываемся. Часто нас интересует сам факт аллоцирования, не больше. Но что происходит, когда мы "отпускаем" ненужный блок памяти? Он просто помечается как неиспользуемый.
Что будет, если мы захотим аллоцировать кусочек побольше? Мы не получим блок, который "освободили" на предыдущем шаге, так как он слишком маленький. Мы получим другой, более большой блок.
А как же тот, маленький?
Это та самая фрагментация. И с этим надо что-то делать.
1️⃣ Например, использовать пулы памяти.
Выделяем большой кусок памяти, режем его на нужные нам кусочки одинакового размера и переиспользуем. Самый простой способ, который при этом очень эффективный.
2️⃣ Аллокаторы. Можно реализовать свои аллокаторы (arena, slab и другие) или взять готовые. Они фактически тоже пулы "под капотом", только более умные и позволяют использовать блоки памяти разного размера.
3️⃣ Аллокаторы с слиянием (coalescing). Еще более умная штуковина с этапом слияния свободных участков и последующим переиспользованием. Используется не так часто, потому что требует дополнительного прохода "упаковки" (да, кое-кого напоминает).
А как же аллокаторы от Unity?
- Temp и Temp Job: обычные arena аллокаторы.
- Persistent: это уже malloc/free и частые аллоцирования приводят к фрагментации.
P.S.: в комментариях уточнили, что persistent - это TLSF аллокатор
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
Unity: Всё, что вы не знали о разработке
https://docs.google.com/presentation/d/1LlxyWOz88aXjAQVVJdN3Fss08E8Rnu8k/edit?usp=drivesdk&ouid=113802151767136274653&rtpof=true&sd=true
Делюсь с вами моей презой для одного из закрытых ивентов. Тут собрана база, но вполне возможно, что в вашей компании/команде…
Делюсь с вами моей презой для одного из закрытых ивентов. Тут собрана база, но вполне возможно, что в вашей компании/команде…
❤17👀4👍2
Всем привет! 👋
Продолжим тему с аллокаторами. Как я чуть раньше писала, аллокаторы решают часть проблем при работе с памятью.
Но какие аллокаторы бывают?🤔 Приведу несколько самых распространенных типов, которые часто используют при разработке игр.
1️⃣ Arena Allocator
Арена-аллокатор - это такой аллокатор, при котором вся память выделяется большим единым куском. А потом она разрезается на кусочки, которые уже используются.
Пример псевдокода:
Когда используется:
🔘 когда все элементы живут одинаково долго
🔘 когда можно разом выделить память и освободить ее
🔘 когда не надо индивидуальное освобождение объектов
TempJob в Unity является именно Arena-аллокатором.
2️⃣ Slab Allocator
Slab-аллокатор, он же пул памяти, при котором память тоже выделяется большим куском, но нарезается сразу на N участков одинакового размера. Это позволяет переиспользовать участки, если какие-то объекты больше их не используют.
Пример псевдокода для аллоцирования:
Псевдокод для возврата:
Когда используется:
🔘 когда много объектов одного размера
🔘 когда важно выделять для них память
🔘 когда важно при этом освобождать память и ее переиспользовать
Пост уже получился достаточно большим. Расскажу о других видах в следующий раз!🥔
#csharp #allocators
Продолжим тему с аллокаторами. Как я чуть раньше писала, аллокаторы решают часть проблем при работе с памятью.
Но какие аллокаторы бывают?
1️⃣ Arena Allocator
Арена-аллокатор - это такой аллокатор, при котором вся память выделяется большим единым куском. А потом она разрезается на кусочки, которые уже используются.
Пример псевдокода:
byte* memory = memalloc(1024*1024); // выделяем сразу большой кусок
byte* current = memory; // сохраняем указатель
...
byte* block = current;
current = current + blockSize; // сдвигаем указатель для следующей аллокации
return block;
Когда используется:
🔘 когда все элементы живут одинаково долго
🔘 когда можно разом выделить память и освободить ее
🔘 когда не надо индивидуальное освобождение объектов
TempJob в Unity является именно Arena-аллокатором.
2️⃣ Slab Allocator
Slab-аллокатор, он же пул памяти, при котором память тоже выделяется большим куском, но нарезается сразу на N участков одинакового размера. Это позволяет переиспользовать участки, если какие-то объекты больше их не используют.
Пример псевдокода для аллоцирования:
byte* memory = memalloc(1024*1024); // выделяем сразу большой кусок
int* freeList; // список свободных элементов
int freeCount = 0;
int allocatedCount = 0;
...
if(freeCount > 0)
{
int idx = freeList[--freeCount];
return memory + idx * blockSize; // выдаем из пула
}
if(allocatedCount < capacity)
return memory + (allocatedCount++) * blockSize; // выдаем следующий не аллоцированный
return null; // блоков больше нет
Псевдокод для возврата:
int idx = (ptr - memory) / blockSize; // вычисляем индекс блока по адресу памяти
freeList[freeCount++] = idx; // записываем индекс как свободный
Когда используется:
🔘 когда много объектов одного размера
🔘 когда важно выделять для них память
🔘 когда важно при этом освобождать память и ее переиспользовать
Пост уже получился достаточно большим. Расскажу о других видах в следующий раз!
#csharp #allocators
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
Infinity World (Дневники разработчицы)
Неделя работы с памятью объявляется открытой! 🙌
У Алекса появился пост про память. Очень полезный в контексте Unity для разработчиков всех уровней. 🥹 Кто не видел - очень советую глянуть его презентацию.
Я же хотела рассказать про такой аспект памяти, как…
У Алекса появился пост про память. Очень полезный в контексте Unity для разработчиков всех уровней. 🥹 Кто не видел - очень советую глянуть его презентацию.
Я же хотела рассказать про такой аспект памяти, как…
🔥24❤5💋4🥰2💘2
Всем привет! 👋
В прошлый раз я рассказала о нескольких типах аллокаторов, которые часто используются в геймдеве. Сегодня хочу продолжить эту тему и рассказать немного еще об аллокаторах, вернее об одном, который уже затрагивали в комментариях.📝
Two-Level Segregate Fit (TLSF) аллокатор
Он же Persistent аллокатор в Unity в DOTS. Из названия понятно, что этот аллокатор двухуровневый. В чем это проявляется?🚪
Идея
Основная идея TLSF - классифицировать все запросы на блоки памяти по размеру этих блоков и хранить их на двух уровнях.
Первый уровень, он же First Level Index (FLI), это грубое приближение классификации по размеру. Например, если нам нужен блок размером 60 байт, то FLI будет равен 5 (он же индекс корзины).
Как же считается этот FLI?
По размеру!🗣 Берется размер, вот эти 60 байт, переводится в двоичный вид (111100) и берется старший бит (32). Это определяет диапазон [32...63]. Или же степень двойки 2^5 = 32.
Следующим шагом, необходимо уточнить, в какой подкорзине относится наш запрос. Такая подкорзина называется Second Level Index (SLI). Для наших 60 байт это будет 14.
Как считается SLI?
На основе FLI и нашего размера!✨ В начале вычисляется шаг. Формула простая:
А потом и сам SLI:
Алгоритм
Для выделения куска памяти необходимого размера алгоритм можно описать вот так:
1️⃣ Смотрим на размер блока, который нам нужен и вычисляем FLI и SLI.
2️⃣ Ищем свободный блок по вычисленным FLI и SLI. Тут применяется bitmap для быстрого поиска (O(1)).
3️⃣ Если по указанным FLI/SLI нет свободного блока, идем в следующий SLI (или FLI, если в текущем закончились SLI), в котором есть, пусть и более большой по размеру.
4️⃣ Если мы взяли блок не из своего FLI (то есть он большой для нас), то режем его. А остаток возвращаем.
5️⃣ Обновляем bitmap: указываем, что FLI/SLI пусты.
6️⃣ Возвращаем выбранный блок пользователю.
Поговорим немного про bitmap.
У каждого уровня (FLI/SLI) есть своя bitmap (обычно unsigned int). Каждый бит в bitmap показывает полно/пусто в конкретном FLI/SLI. Причем для FLI каждый бит показывает именно статус конкретных FLI, а для SLI - SLI (запутанно как-то🤔 )
Когда мы аллоцируем или возвращаем, то зная FLI/SLI, можем установить или очистить нужные биты:
Чтобы найти свободный блок, нам надо вычислить маску по FLI/SLI. Например, FLI = 5, а SLI = 7:
В mask будут только биты ≥ 7. Если маска не равна нулю, то берем первый установленный бит. Если равна - ищем дальше, причем тоже за O(1).😛
А что делать с остатком, если мы взяли кусок из более большого FLI и поделили его?🫥
Остаток в данном кейсе уходит в FLI/SLI по своему размеру как свободный. Например, нужно было 60 байт, а ближайший свободный блок только из корзины (FLI/SLI) в 128 байт. 60 байт мы взяли себе, а 68 байт поместили в FLI/SLI для корзины с диапазоном [64, 128], так как FLI/SLI вычисляется точно по размеру. И для 68 байт это будет FLI = 6 (диапазон [64..127]), а SLI = 1.
Уф, хотела кратенько, а получилось довольно объемно🫥
Пишите в комментариях, а что вы используете?🥰
#csharp #allocators
В прошлый раз я рассказала о нескольких типах аллокаторов, которые часто используются в геймдеве. Сегодня хочу продолжить эту тему и рассказать немного еще об аллокаторах, вернее об одном, который уже затрагивали в комментариях.
Two-Level Segregate Fit (TLSF) аллокатор
Он же Persistent аллокатор в Unity в DOTS. Из названия понятно, что этот аллокатор двухуровневый. В чем это проявляется?
Идея
Основная идея TLSF - классифицировать все запросы на блоки памяти по размеру этих блоков и хранить их на двух уровнях.
Первый уровень, он же First Level Index (FLI), это грубое приближение классификации по размеру. Например, если нам нужен блок размером 60 байт, то FLI будет равен 5 (он же индекс корзины).
Как же считается этот FLI?
По размеру!
Следующим шагом, необходимо уточнить, в какой подкорзине относится наш запрос. Такая подкорзина называется Second Level Index (SLI). Для наших 60 байт это будет 14.
Как считается SLI?
На основе FLI и нашего размера!
step = (верхняя граница диапазона - нижняя + 1) / 16
А потом и сам SLI:
SLI = (size - base) / step
Алгоритм
Для выделения куска памяти необходимого размера алгоритм можно описать вот так:
1️⃣ Смотрим на размер блока, который нам нужен и вычисляем FLI и SLI.
2️⃣ Ищем свободный блок по вычисленным FLI и SLI. Тут применяется bitmap для быстрого поиска (O(1)).
3️⃣ Если по указанным FLI/SLI нет свободного блока, идем в следующий SLI (или FLI, если в текущем закончились SLI), в котором есть, пусть и более большой по размеру.
4️⃣ Если мы взяли блок не из своего FLI (то есть он большой для нас), то режем его. А остаток возвращаем.
5️⃣ Обновляем bitmap: указываем, что FLI/SLI пусты.
6️⃣ Возвращаем выбранный блок пользователю.
Поговорим немного про bitmap.
У каждого уровня (FLI/SLI) есть своя bitmap (обычно unsigned int). Каждый бит в bitmap показывает полно/пусто в конкретном FLI/SLI. Причем для FLI каждый бит показывает именно статус конкретных FLI, а для SLI - SLI (запутанно как-то
Когда мы аллоцируем или возвращаем, то зная FLI/SLI, можем установить или очистить нужные биты:
slBitmap[fli] |= (1u << sli); // ставим бит для SLI
flBitmap |= (1u << fli);
...
slBitmap[fli] &= ~(1u << sli); // сбрасываем бит для SLI
if (slBitmap[fli] == 0)
flBitmap &= ~(1u << fli); // если весь FLI пуст, сбросить и его бит
Чтобы найти свободный блок, нам надо вычислить маску по FLI/SLI. Например, FLI = 5, а SLI = 7:
uint mask = slBitmap[5] & (~0u << 7);
В mask будут только биты ≥ 7. Если маска не равна нулю, то берем первый установленный бит. Если равна - ищем дальше, причем тоже за O(1).
uint maskFL = flBitmap & (~0u << (5+1)); // находим другой свободный FLI
if (maskFL != 0) {
int newFli = BitOperations.TrailingZeroCount(maskFL);
int newSli = BitOperations.TrailingZeroCount(slBitmap[newFli]);
...
}
А что делать с остатком, если мы взяли кусок из более большого FLI и поделили его?
Остаток в данном кейсе уходит в FLI/SLI по своему размеру как свободный. Например, нужно было 60 байт, а ближайший свободный блок только из корзины (FLI/SLI) в 128 байт. 60 байт мы взяли себе, а 68 байт поместили в FLI/SLI для корзины с диапазоном [64, 128], так как FLI/SLI вычисляется точно по размеру. И для 68 байт это будет FLI = 6 (диапазон [64..127]), а SLI = 1.
Уф, хотела кратенько, а получилось довольно объемно
Пишите в комментариях, а что вы используете?
#csharp #allocators
Please open Telegram to view this post
VIEW IN TELEGRAM
1❤19💘5😱4🤩3🍓3