...

суббота, 19 декабря 2020 г.

Обфускация как метод защиты программного обеспечения

Или то, почему вы не можете издать свою улучшенную версию Counter Strike и уехать жить на Гавайи.

О чём речь?

Обфуска́ция (от английского obfuscate — делать неочевидным, запутанным, сбивать с толку)  в широком смысле - приведение исходного текста или исполняемого кода программы к виду, сохраняющему её функциональность, но затрудняющему анализ, понимание алгоритмов работы и модификацию при декомпиляции.

Красивый пример из Википедии кода, прошедшего обфускацию.
Красивый пример из Википедии кода, прошедшего обфускацию.

Далее в программе

  • Зачем это нужно?

  • Как это должно работать?

  • Как это работает?

  • Методы

  • Состояние дел сейчас

Зачем это нужно?

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

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

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

Как это должно работать?

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

В идеале хотелось бы, чтобы программа, прошедшая обфускацию, давала бы не больше информации нежели чёрный ящик, имитирующий поведение исходной программы. Гипотетический алгоритм, реализующий такое преобразование называется "Обфускация чёрного ящика". Декомпиляция зашифрованной таким образом программы дала бы злоумышленникам не больше информации, чем декомпиляция клиента мессенджера, представляющего собой лишь обёртку над апи "настоящего" приложения, что бы полностью решило поставленную в предыдущем блоке проблему. Однако показано[3], что реализация такого алгоритма для произвольной программы невозможна.

Как это работает

Большинство методов обфускации преобразуют следующие аспектов кода:

• Данные: делают элементы кода похожими на то, чем они не являются

• Поток кода: выставляют исполняемую логику программы абсурдной или даже недетерминированной

• Структура формата: применяют различное форматирование данных, переименование идентификаторов, удаление комментариев кода и т.д.

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

При обфускации кода, важно правильно оценить, какие части когда можно эффективно запутать. Следует избегать обфускации кода, критичного относительно производительности.

Методы

1. Преобразование данных

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

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

2. Обфускация потока управления кодом

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

3. Обфускация адресов

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

Подробнее об адресной обфускации можно прочесть тут.

4. Регулярное обновление кода

Этот метод предотвращает атаки, регулярно выпуская обновления обфусцированного программного обеспечения. Своевременные замены частей существующего программного обеспечения новыми обфусцированными экземплярами, могут вынудить злоумышленника отказаться от существующего результата обратного анализа, так как усилия по взлому кода в таком случае могут превысить получаемую от этого ценность.

5. Обфускация инструкций ассемблера

Преобразование и изменение ассемблерного когда также может затруднить процесс обратного инжиниринга. Одним из таких методов является использование перекрывающихся инструкций (jump-in-a-middle), в результате чего дизассемблер может произвести неправильный вывод. Ассемблерный код также может быть усилен против проникновения за счёт включения бесполезных управляющих операторов и прочего мусорного кода. 

6. Обфускация отладочной информации

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

Заключение

Я не стал описывать историю развития различных подходов к обфускации, так как на мой взгляд, она неплохо отражена в уже существующей на Хабре статье.

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

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

Ссылки и источники

[1] https://en.wikipedia.org/wiki/Obfuscation_(software)

[2] https://www.sciencedirect.com/science/article/pii/S1877050915032780

[3] Barak B., Goldreich O., Impagliazzo R., Rudich S., Sahai A., Vadhan S. and Yang K. «On the (im) possibility of obfuscating programs.» CRYPTO 2001.

[4] https://www.researchgate.net/publication/235611093TechniquesofProgramCodeObfuscationforSecureSoftware

Let's block ads! (Why?)

Как тебе такое, Илон Маск? Amazon закончила разработку недорогой пользовательской антенны для конкурента Starlink


Недорогой широкополосный спутниковый интернет — мечта миллионов людей, живущих в удаленных регионах. В большинстве таких мест подключиться к обычной магистрали не получится, а все остальные способы либо недоступны, либо очень дороги. Поэтому спутниковый интернет сейчас развивают сразу три компании — Starlink Илона Маска, OneWeb и Project Kuiper от Amazon.

На днях Amazon рассказала о завершении создания антенны для недорогого спутникового пользовательского терминала. По словам разработчиков, основа конструкции антенны — новая архитектура, которая позволила сделать систему небольшой и легкой. При этом она обеспечивает пропускную способность канала вплоть до 400Mbps.

Что из себя представляет эта антенна?


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

Amazon удалось разработать систему, где обе антенны наслаиваются друг на друга — элементы конструкции перекрываются, причем так, чтобы не мешать приему или передаче данных. Конструкция предусматривает комбинацию цифровых и аналоговых компонентов. Такой вариант компоновки дал возможность создать антенну диаметром всего 12 дюймов, которая обеспечивает пропускную способность вплоть до 400Mbps.

«Ключевым достижением стало объединение передающей и приемной антенн с фазированной решеткой в ​​одной апертуре. Это можно сделать и в других частотных диапазонах, но Project Kuiper планирует работать в Ka-диапазоне, где частоты передачи и приема отстоят далеко друг от друга», — говорится в заявлении компании.


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

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

Amazon собирается использовать спектр частот 17.7-18.6 ГГц и 18.8-20.2 ГГц для сигнала «космос-Земля» и 27.5-30.0 ГГц для связи «Земля-космос».

Этап тестирования уже пройден


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

Сейчас Amazon нанимает сотрудников для расширения масштабов проекта. То есть, он входит в практическую фазу и нужны люди, новые сотрудники.

Приоритет — на загрузке контента


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

Одно из достижений — рассеивание тепла в самих спутниках. Проблема в том, что в космосе нет воздуха, который может отводить тепло. Поэтому пришлось разработать не только мощные системы, способные обеспечивать надежный сигнал, но и подумать над снижением энергопотребления этих систем. Участники проекта говорят, что им пришлось поломать голову над этой проблемой, но в итоге ее удалось решить.

К сожалению, рассказав о таких подробностях, участники проекта умолчали о том, что Kuiper станет доступным для конечных пользователей. FCC дала Amazon 6 лет на разворачивание спутниковой системы — за это время на орбите должны оказаться минимум 50% аппаратов. Все остальные должны быть запущены к 30 июля 2029 года.

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

Главный конкурент спутниковой сети Amazon — Starlink от SpaceX, уже предоставила тестовый доступ нескольким сотням абонентов. Доступная скорость — от 50 до 150 Mbps с задержками от 20 до 40 мс. К 2021 году компания обещает значительно улучшить все показатели работы сети.

Let's block ads! (Why?)

.Использование GitHub в обучении студентов

В своей преподавательской практике использую GitHub...

Но для начала давайте представлюсь. Зовут меня Старинин Андрей. И я преподаю программирование, хотя по первому образованию я биолог. А ещё один из основателей и ведущих подкаста "IT за Edu".

Мой стек дисциплин:

Кажется, что всего много. Но успеваем не сильно погрузиться в отдельные технологии. После какого-то времени (точно не помню уже какого) понял, что студентов можно и даже нужно "приучать" к системам управления версиями почти сразу с начала обучения. Для обучения выбрал GitHub. Хотя Bitbucket тоже нравится. Да, я не учу студентов сразу по харду, они не сразу изучают git в CLI. Я их знакомлю сначала с web-интерфейсом GitHub'а. Потом рассказываю про GUI-клиенты. Из них мне нравится GitKraken. Но не заставляю их пользоваться тем, что нравится мне - они вольны выбирать сами чем пользоваться.

Постепенно - это примерно так:

  1. Просто показываю как выкладывать код

  2. Прошу их выкладывать свои решения и присылать мне ссылки на репозитории

  3. Выкладываю текст заданий и прошу ответы присылать через pull-request'ы

  4. Пробуем поработать в маленьких командах над одним репозиторием без веток

  5. Пробуем поработать небольшой командой над одним репозиторием с отдельными ветками

  6. Пробуем работать над большим проектом большой командой с несколькими репозиториями и ветками.

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

Не все студенты сразу всё понимают и принимают. Но тем интереснее и приятнее когда они "доходят". Ещё люблю подход: учимся на своих ошибках. Во время обучения есть возможность ошибаться и понять к чему это приводит.

Что мне нравится в GitHub при обучении?

  • Поддержка аккаунтов для организаций, а в аккаунтах возможность создания команд с гибкими настройками доступов

  • Система форков. Может любой человек сделать форк, а потом предложить запрос на слияние. Не всегда нужно всех студентов добавлять в команду.

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

  • Система issues. Можно давать другим командам студентов задание на проверку кода и выявления багов, с занесением всего в issues.

Для чего я приучаю студентов к GitHub'у?

  • Создание своего портфолио уже с самого начала обучения, а не только под конец.

  • Понимание принципов написания кода. Когда начинают чужой код проверять - многое понимают

  • Понимание "соглашения об именовании". Пока не наступят на грабли разного именования в одной команде - не понимают. Ну или не все понимают

  • Понимание как работать в команде. И как командам между собой взаимодействовать.

Прекрасно понимаю, что мои методы не самые лучшие и далеки от совершенства, да и от реальности далековаты. Но стараюсь их приблизить к реальности.

Let's block ads! (Why?)

Хабрарейтинг 2020: статистика и рейтинг лучших статей за 2020 год

Привет Хабр.

Заканчивается 2020 год, а значит, настало время подвести статистические итоги и составить уже традиционный рейтинг лучших статей Хабра за этот год. Этот рейтинг не является официальным, данные собираются парсером с помощью Python. Сортируя данные по тем или иным параметрам, можно получать разные выборки, что на мой взгляд, даёт довольно неплохие результаты. Для читателей также может быть интересно перечитать какие-то статьи, которые они пропустили в течении года.

Поехали.

Общая информация


Всего на Хабр было выложено 22 тыс статей, или примерно 60 статей в день. Довольно внушительная цифра, и с сожалением нужно признать, что я наверно не прочитал и 1% от этого числа.

Было интересно посмотреть, не произошло ли каких-то кардинальных изменений по сравнению с прошлым годом. Число публикаций немного выросло, но не кардинально, каких-либо всплесков связанных с локдаунами, на графике не видно. Соотношение количества новостей, статей и корпоративных блогов также осталось примерно на том же уровне. Было также интересно проверить, повлияла ли эпидемия на число просмотров — как оказалось, и да и нет. Есть несколько статей про Covid, число просмотров которых достигает нескольких миллионов (рекорд — 5.5 млн просмотров), но для всех остальных материалов какого-то существенного роста заметно не было.

Самые популярные слова в названиях статей принципиально не изменились:

Как «примету года» можно отметить новое слово covid19, которое по популярности оказалось на 14м месте. Впрочем, нельзя сказать, что оно попадалось в названиях так уж часто — про Google, Microsoft и Apple все же писали заметно чаще.

Рейтинг


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

Let's get started.

Топ-10 статей по числу просмотров

ВИЧ — нулевой пациент мировой пандемии от ScientaeVulgaris20, 613000 просмотров, 228 комментариев, рейтинг +218.0/-20.0
Стивен Вольфрам: кажется, мы близки к пониманию фундаментальной теории физики, и она прекрасна от SergioShpadi, 278000 просмотров, 440 комментариев, рейтинг +312.0/-5.0
10 признаков того, что хороший программист из вас не получится от bredtPidt, 261000 просмотров, 521 комментарий, рейтинг +139.0/-23.0
Как Греф с программистами боролся от svok, 224000 просмотров, 282 комментария, рейтинг +164.0/-15.0
Как я чуть не выкинул 150к на ветер или история установки приточной вентиляции в квартире от jirfag, 224000 просмотров, 549 комментариев, рейтинг +370.0/-5.0
USB-флешки: заряжать нельзя игнорировать от chechestor, 204000 просмотров, 269 комментариев, рейтинг +223.0/-8.0
Все нововведения Windows 10 2004 (20H1) от Sanctuary_s, 199000 просмотров, 417 комментариев, рейтинг +110.0/-2.0
Не держите людей за идиотов или почему человек с инженерным образованием может сжечь вышку сотовой связи (видео) от GrantM, 197000 просмотров, 331 комментарий, рейтинг +272.0/-28.0
Как живется в США 'неайтишникам'. Другая сторона от 2fidel, 194000 просмотров, 561 комментарий, рейтинг +332.0/-3.0
Как стать долларовым миллионером за 30 лет, лежа на диване от SergioShpadi, 194000 просмотров, 556 комментариев, рейтинг +291.0/-17.0

Топ-10 статей по рейтингу

Тёмная сторона работы в Яндекс.Маркете от IvanVakhrushev, 805 комментариев, рейтинг +497.0/-31.0, 154000 просмотров
В IT растет цензура, а мы не замечаем — разрешают только улыбаться и молчать от arttom, 1522 комментария, рейтинг +480.0/-17.0, 107000 просмотров
Наша огромная гордость: мирные советские роботы-комбайны убрали первый урожай в южных регионах от chernogorov_andrey, 525 комментариев, рейтинг +447.0/-3.0, 69200 просмотров
Расследование: создатель AlterOffice украл код у Microsoft и рвётся назад в реестр от Yconilev, 189 комментариев, рейтинг +432.0/-8.0, 81200 просмотров
Открытое письмо компании Mail.ru об игре 'Аллоды II: Повелитель душ' от skobki, 178 комментариев, рейтинг +394.0/-2.0, 88900 просмотров
Как я чуть не выкинул 150к на ветер или история установки приточной вентиляции в квартире от jirfag, 549 комментариев, рейтинг +370.0/-5.0, 224000 просмотров
Пора на свалку от 0xd34df00d, 1248 комментариев, рейтинг +373.0/-24.0, 136000 просмотров
Ставим котю на ноги от Uris, 214 комментариев, рейтинг +351.0/-2.0, 69400 просмотров
Теперь я не могу сделать даже маленький сайт от markmariner, 318 комментариев, рейтинг +370.0/-31.0, 121000 просмотров
Чешские программисты бесплатно написали сайт стоимостью 16 миллионов евро? Правда? от ximaera, 267 комментариев, рейтинг +342.0/-6.0, 122000 просмотров

Топ-10 статей по соотношению рейтинга к числу просмотров

Хабру — 14 лет от Boomburum 14800 просмотров, рейтинг +187.0/-0.0
Авторам! [именно так: в дательном падеже и с восклицанием] от simpleadmin 22800 просмотров, рейтинг +284.0/-12.0
Реверсим и улучшаем SATA контроллер от 15432 19500 просмотров, рейтинг +198.0/-0.0
Скоростной АЦП с нуля. 16 бит за 10 лет от analog_design 11800 просмотров, рейтинг +113.0/-0.0
Хабр Конвертер: чтобы версталось легко от AloneCoder 11600 просмотров, рейтинг +112.0/-1.0
Как Linux'овский sort сортирует строки от aragont 13500 просмотров, рейтинг +123.0/-0.0
Как нарисовать звезду (и не только) в полярных координатах от Refridgerator 16800 просмотров, рейтинг +150.0/-2.0
Как мы первыми в мире роботизируем кормоуборочные комбайны от eduard_abdulkin 22000 просмотров, рейтинг +191.0/-1.0
МРЭМ — 200. Электронный микроскоп родом из СССР от FIZIK-TECHNIK 13000 просмотров, рейтинг +112.0/-0.0
Telogreika v1.0 — носимое устройство персонального обогрева на Arduino от MRizhoff 23600 просмотров, рейтинг +202.0/-2.0

Топ-10 по числу добавлений в «закладки»

Подготовка к собеседованиям в IT-гиганты: как я преодолела проклятье алгоритмического собеседования от greenEkatherine, 155000 просмотров, 1458 закладок
Какие английские слова IT-лексикона мы неправильно произносим чаще всего от YuriyIvon, 151000 просмотров, 1165 закладок
Как стать долларовым миллионером за 30 лет, лежа на диване от SergioShpadi, 194000 просмотров, 1083 закладки
Как я чуть не выкинул 150к на ветер или история установки приточной вентиляции в квартире от jirfag, 224000 просмотров, 1077 закладок
Как научиться разработке на Python: новый видеокурс Яндекса от orlovdl, 124000 просмотров, 1019 закладок
Полная домашняя автоматизация в новостройке от empenoso, 177000 просмотров, 969 закладок
70 вопросов по JavaScript для подготовки к собеседованию от aio350, 186000 просмотров, 934 закладки
10 интересных репозиториев на GitHub, полезных любому разработчику от Plarium, 64400 просмотров, 917 закладок
Сайты для обучения программированию: Топ 100 от dangerstats, 93400 просмотров, 738 закладок
Практическое руководство по разработке бэкенд-сервиса на Python от alvassin, 60800 просмотров, 729 закладок

Топ-10 по соотношению количества добавлений в закладки/просмотры

Как бы я изучал Data Science, если бы начал пару лет назад, или Руководство по эффективному изучению науки о данных от Picard, 327 закладок, 10700 просмотров
Как выбрать красивые цвета для вашей инфографики от m1rko, 265 закладок, 14400 просмотров
Как облегчить себе жизнь при использовании Git (а также подборка материалов для глубокого погружения) от pxeno, 401 закладка, 21900 просмотров
Имитируем сетевые проблемы в Linux от azakharenko, 351 закладка, 19600 просмотров
Повышение продуктивности при работе с Jupyter Notebook за 5 минут от germn, 215 закладок, 12200 просмотров
Запасной вариант для Let's Encrypt — бесплатные автоматические УЦ от GlobalSign_admin, 193 закладки, 11200 просмотров
Пишем движок полнотекстового поиска на Go от m1rko, 194 закладки, 11400 просмотров
Как построить диаграмму на Python от skillfactory_school, 196 закладок, 11700 просмотров
Лучшие практики bash-скриптов: краткое руководство по надежным и производительным скриптам bash от RomanenkoDenys, 289 закладок, 17300 просмотров
Визуализация промисов и Async/Await от aio350, 212 закладок, 12700 просмотров

Топ-10 самых комментируемых статей

Главная причина, почему не Linux от mrtux, 2401 комментарий, 151000 просмотров
Как оптимизировали экономику СССР и что из этого вышло от vdsina_m, 2028 комментариев, 51900 просмотров
Разработчики - никакая не элита, а голые короли индустрии от chapuza, 1985 комментариев, 151000 просмотров
Динамическая типизация — это не инструмент для разработки. Это чепуха (паршивая) от fillpackart, 1978 комментариев, 72700 просмотров
Враги свободы от Chronicler, 1943 комментария, 48400 просмотров
Меня перевезли в другую страну и через две недели выставили на мороз — потому что передумали нанимать от pnovikov, 1862 комментария, 177000 просмотров
В IT растет цензура, а мы не замечаем — разрешают только улыбаться и молчать от arttom, 1522 комментария, 107000 просмотров
Германия, или Туда и Обратно — 3 от Gradiens, 1490 комментариев, 71600 просмотров
Пользователю все это не нужно! Хватит пропагандировать Линукс от tmat, 1432 комментария, 87400 просмотров
В софте всё восхитительно, но все недовольны от phillennium, 1295 комментариев, 43500 просмотров

Топ-10 самых «спорных» статей

Минусы Дурова от progblog 74 комментария, рейтинг +72.0/-74.0, 36400 просмотров
Горячая четвёрка умирающих языков программирования от ru_vds 273 комментария, рейтинг +68.0/-64.0, 82700 просмотров
В кризис я потерял работу и теперь боюсь писать умный код, чтобы не распугать последние вакансии от fillpackart 214 комментариев, рейтинг +61.0/-56.0, 29900 просмотров
Как платить программистам от DragonSoft 69 комментариев, рейтинг +56.0/-51.0, 27600 просмотров
Web в Китае умер. Почему так произошло и что пришло вместо него? от vaily 861 комментарий, рейтинг +144.0/-131.0, 153000 просмотров
История человека, чувствительного к электромагнитному излучению, или как мы щупаем край обрыва… UPD: +FAQ от 3Dvideo 933 комментария, рейтинг +102.0/-88.0, 46400 просмотров
Разрабы приходят сюда жаловаться на бизнес. Другие разрабы здесь говорят им, что они сами виноваты от fillpackart 182 комментария, рейтинг +62.0/-52.0, 12700 просмотров
Медуза: власти выложили в открытый доступ персональные данные всех интернет-избирателей (на самом деле нет) от olartamonov 371 комментарий, рейтинг +76.0/-53.0, 21300 просмотров
Ни туда, ни обратно от euroUK 531 комментарий, рейтинг +92.0/-63.0, 23400 просмотров
Разработчики - никакая не элита, а голые короли индустрии от chapuza 1985 комментариев, рейтинг +387.0/-262.0, 151000 просмотров

Бонус


В качестве бонуса я решил составить рейтинг по двум категориям, не относящихся к программированию. В прошлом году это были хабы «Эмиграция» и «Здоровье», в этом году про «здоровье» вещают и так из каждого утюга, так что возьмем… «Эмиграцию» и «Космонавтику». Никакой логической связи между этим выбором просьба не искать, я не призываю эмигрировать на Луну или Марс (а может, и неплохая идея?). Просто космонавтика это хороший повод отвлечься от проблем земных, и прочитать про что-то в прямом смысле слова возвышенное и небесное ;)

Эмиграция

Как живется в США 'неайтишникам'. Другая сторона от 2fidel, 194000 просмотров, 561 комментарий, рейтинг +332.0/-3.0
Реальная стоимость жизни в Кремниевой Долине для разработчика от vincentstark, 137000 просмотров, 896 комментариев, рейтинг +269.0/-9.0
Заметки о жизни в США от pavgra, 118000 просмотров, 1207 комментариев, рейтинг +293.0/-16.0
Я мечтал вырваться из Узбекистана и стать крутым разрабом. Больше не хочу — но разработка не отпускает от vladten, 112000 просмотров, 307 комментариев, рейтинг +195.0/-15.0
В России плохо жить, даже если ты разраб. Но я все равно отказываюсь от релокейта от fillpackart, 87700 просмотров, 1177 комментариев, рейтинг +260.0/-94.0
Почтовый агент. Ловушка для жены эмигранта от snipsnap, 87600 просмотров, 520 комментариев, рейтинг +163.0/-3.0
Германия, или Туда и Обратно — 3 от Gradiens, 71600 просмотров, 1490 комментариев, рейтинг +254.0/-14.0
Как американцы становятся миллионерами: принципы FIRE от 3eta, 60700 просмотров, 294 комментария, рейтинг +47.0/-3.0
WFH убивает Кремниевую Долину? от vincentstark, 56700 просмотров, 189 комментариев, рейтинг +125.0/-10.0
Оффер в Лондон за один день: как его получить и чем заняться после переезда от AHDREN, 51900 просмотров, 280 комментариев, рейтинг +65.0/-8.0

Космонавтика

Что помешало экипажу Crew Dragon выйти из корабля? от Zelenyikot, 125000 просмотров, 185 комментариев, рейтинг +207.0/-1.0
Ограбление Луны по-китайски от Zelenyikot, 124000 просмотров, 157 комментариев, рейтинг +141.0/-4.0
Как мы потеряли 'Мир': пожар на космической станции, столкновение с грузовиком 'Прогресс', разгерметизация от HostingManager, 74800 просмотров, 522 комментария, рейтинг +172.0/-16.0
В 26 лет Яна Харлан руководит разработкой космического двигателя. В следующем году его планируют запустить от maybe_elf, 61400 просмотров, 305 комментариев, рейтинг +138.0/-14.0
Разбираем цифровые часы с космического корабля 'Союз' от SLY_G, 60900 просмотров, 339 комментариев, рейтинг +122.0/-6.0
Кадровый провал в космической отрасли России и с чем его едят от PavelPushkin, 59300 просмотров, 388 комментариев, рейтинг +95.0/-17.0
Водоросли, танкер и шторм против Камчатки от Zelenyikot, 49700 просмотров, 352 комментария, рейтинг +174.0/-26.0
Проводы российской 'Науки' от Zelenyikot, 47200 просмотров, 150 комментариев, рейтинг +122.0/-3.0
Илон Маск: 'Марс — свободная планета, там не действуют законы Земли' от Seleditor, 45100 просмотров, 585 комментариев, рейтинг +108.0/-4.0
Прощание с Аресибо от Zelenyikot, 44100 просмотров, 157 комментариев, рейтинг +178.0/-0.0

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

Заключение


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

Let's block ads! (Why?)

Использование journalctl для просмотра и анализа логов: подробный гайд

Journalctl — отличный инструмент для анализа логов, обычно один из первых с которым знакомятся начинающие администраторы linux систем. Встроенные возможности ротации, богатые возможности фильтрации и возможность просматривать логи всех systemd unit-сервисов одним инструментом очень удобны и заметно облегчают работу системным администраторам.

Эта статья рассматривает основные возможности утилиты journalctl и различные варианты ее применения. С помощью journalctl можно просматривать логи системы, чтобы решить возникшие проблемы на рабочей станции или сервере использующие дистрибутив linux с демоном инициализации systemd, де-факто уже ставшим стандартом в современных Linux-системах, например: RHEL, CentOS, Fedora, Debian и многих других.

Существует мнение, что systemd не так уж и хорош — он нагружает систему и это все еще предмет для споров на сегодняшний день, но нельзя отрицать, что он предоставляет прекрасный набор инструментов для управления системой и поиска проблем. Представьте, что вам приходится иметь дело с проблемным сервером, который даже не загружается — в таком случае можно загрузиться с live-дистрибутива, смонтировать системный раздел и просмотреть логи systemd, чтобы понять, в чем проблема.

Systemd


Systemd состоит из трех основных компонентов:
  • systemd — менеджер системы и сервисов
  • systemctl — утилита для просмотра и управление статусом сервисов
  • systemd-analyze — предоставляет статистику по процессу загрузки системы, проверяет корректность unit-файлов и так же имеет возможности отладки systemd

Journald


Journald — системный демон журналов systemd. Systemd спроектирован так, чтобы централизованно управлять системными логами от процессов, приложений и т.д. Все такие события обрабатываются демоном journald, он собирает логи со всей системы и сохраняет их в бинарных файлах.

Преимуществ централизованного логирования событий в бинарном формате достаточно много, например системные логи можно транслировать в различные форматы, такие как простой текст, или в JSON, при необходимости. Так же довольно просто можно отследить лог вплоть до одиночного события используя фильтры даты и времени.

Файлы логов journald могут собирать тысячи событий и они обновляются с каждым новым событием, поэтому если ваша Linux-система работает достаточно долго — размер файлов с логами может достигать несколько гигабайт и более. Поэтому анализ таких логов может происходить с задержками, в таком случае, при анализе логов можно фильтровать вывод, чтобы ускорить работу.

Файл конфигурации journald


Файл конфигурации можно найти по следующему пути: /etc/systemd/journald.conf, он содержит различные настройки journald, я бы не рекомендовал изменять этот файл, если вы точно не уверены в том, что делаете.

Каталог с журналом journald располагается /run/log/journal (в том случае, если не настроено постоянное хранение журналов, но об этом позже).
Файлы хранятся в бинарном формате, поэтому нормально их просмотреть с помощью cat или nano, как привыкли многие администраторы — не получится.

Использование journalctl для просмотра и анализа логов


Основная команда для просмотра:
# journalctl

Она выведет все записи из всех журналов, включая ошибки и предупреждения, начиная с того момента, когда система начала загружаться. Старые записи событий будут наверху, более новые — внизу, вы можете использовать PageUp и PageDown чтобы перемещаться по списку, Enter — чтобы пролистывать журнал построчно и Q — чтобы выйти.

По умолчанию journalctl выводит время событий согласно настроенного в вашей системе часового пояса, также journalctl позволяет просмотреть логи с временем UTC (в этом стандарте времени сохраняются события внутри файлов journald), для этого можно использовать команду:

# journalctl --utc

Фильтрация событий по важности


Система записывает события с различными уровнями важности, какие-то события могут быть предупреждением, которое можно проигнорировать, какие-то могут быть критическими ошибками. Если мы хотим просмотреть только ошибки, игнорируя другие сообщения, введем команду с указанием кода важности:
# journalctl -p 0

Для уровней важности, приняты следующие обозначения:

  • 0: emergency (неработоспособность системы)
  • 1: alerts (предупреждения, требующие немедленного вмешательства)
  • 2: critical (критическое состояние)
  • 3: errors (ошибки)
  • 4: warning (предупреждения)
  • 5: notice (уведомления)
  • 6: info (информационные сообщения)
  • 7: debug (отладочные сообщения)

Когда вы указываете код важности, journalctl выведет все сообщения с этим кодом и выше. Например если мы укажем опцию -p 2, journalctl покажет все сообщения с уровнями 2, 1 и 0.

Настройка хранения журналов


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

Когда в конфигурационном файле /etc/systemd/journald.conf параметру Storage= задано значение auto) и каталога /var/log/journal/ не существует, журнал будет записываться в /run/log/journal без сохранения между перезагрузками, если /var/log/journal/ существует, журналы будут сохраняться в нем, на постоянной основе, но если каталог будет удален, systemd не пересоздаст его автоматически и вместо этого будет вести журнал снова в /run/systemd/journal без сохранения. Каталог может быть пересоздан в таком случае, если добавить Storage=persistent в journald.conf и перезапустить systemd-journald.service (или перезагрузиться).

Создадим каталог для хранения журналов, установим необходимые атрибуты и перезапустим службу:

# mkdir /var/log/journal
# systemd-tmpfiles --create --prefix /var/log/journal
# systemctl restart systemd-journald

Просмотр журналов загрузки


Если journald был настроен на постоянное хранение журналов, мы можем просматривать журналы логов по каждой отдельной загрузке, следующая команда выведет список журналов:
# journalctl --list-boots

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

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

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

# journalctl -b 0

А для того, чтобы просмотреть журнал предыдущей загрузки:
# journalctl -b -1

Просмотр журнала за определенный период времени


Journalctl позволяет использовать такие служебные слова как “yesterday” (вчера), “today” (сегодня), “tomorrow” (завтра), или “now” (сейчас).
Поэтому мы можем использовать опцию "--since" (с начала какого периода выводить журнал).

С определенной даты и времени:

# journalctl --since "2020-12-18 06:00:00"

С определенной даты и по определенное дату и время:
# journalctl --since "2020-12-17" --until "2020-12-18 10:00:00

Со вчерашнего дня:
# journalctl --since yesterday

С 9 утра и до момента, час назад:
# journalctl --since 09:00 --until "1 hour ago"

Просмотр сообщений ядра


Чтобы просмотреть сообщения от ядра Linux за текущую загрузку, используйте команду с ключом -k:
# journalctl -k

Просмотр журнала логов для определенного сервиса systemd или приложения


Вы можете отфильтровать логи по определенному сервису systemd. Например, что бы просмотреть логи от NetworkManager, можно использовать следующую команду:

# journalctl -u NetworkManager.service

Если нужно найти название сервиса, используйте команду:

# systemctl list-units --type=service

Так же можно просмотреть лог приложения, указав его исполняемый файл, например чтобы просмотреть все сообщения от nginx за сегодня, мы можем использовать команду:
# journalctl /usr/sbin/nginx --since today

Или указав конкретный PID:
# journalctl _PID=1

Дополнительные опции просмотра


Следить за появлением новых сообщений (аналог tail -f):
# journalctl -f

Открыть журнал «перемотав» его к последней записи:
# journalctl -e

Если в каталоге с журналами очень много данных, то фильтрация вывода journalctl может занять некоторое время, процесс можно значительно ускорить с помощью опции --file, указав journalctl только нужный нам журнал, за которым мы хотим следить:
journalctl --file /var/log/journal/e02689e50bc240f0bb545dd5940ac213/system.journal -f

По умолчанию journalctl отсекает части строк, которые не вписываются в экран по ширине, хотя иногда перенос строк может оказаться более предпочтительным. Управление этой возможностью производится посредством переменной окружения SYSTEMD_LESS, в которой содержатся опции, передаваемые в less (программу постраничного просмотра, используемую по умолчанию). По умолчанию переменная имеет значение FRSXMK, если убрать опцию S, строки не будут обрезаться.

Например:

SYSTEMD_LESS=FRXMK journalctl

Ограничение размера журнала


Если journald настроен что бы сохранять журналы после перезагрузки, то по умолчанию размер журнала ограничен 10% от объема файлового раздела и максимально может занять 4 Гб дискового пространства.
Максимальный объем журнала можно скорректировать, раскомментировав и отредактировав следующий параметр в файле конфигурации journald:
SystemMaxUse=50M

Удаление журналов


Удалить файлы архивных журналов, можно вручную с помощью rm или использовав journalctl.

Удалить журналы, оставив только последние 100 Мб:

# journalctl --vacuum-size=100M

Удалить журналы, оставив журналы только за последние 7 дней:
# journalctl --vacuum-time=7d

Заключение


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

Let's block ads! (Why?)

Пишем простой Path Tracer на старом добром GLSL

На волне ажиотажа вокруг новых карточек от Nvidia с поддержкой RTX, я, сканируя хабр в поисках интересных статей, с удивлением обнаружил, что такая тема, как трассировка путей, здесь практически не освящена. "Так дело не пойдет" - подумал я и решил, что неплохо бы сделать что-нибудь небольшое на эту тему, да и так, чтоб другим полезно было. Тут как кстати API собственного движка нужно было протестировать, поэтому решил: запилю-ка я свой простенький path-tracer. Что же из этого вышло вы думаю уже догадались по превью к данной статье.

Немного теории

Трассировка пути является одним из частных случаев трассировки лучей и представляет собой наиболее точный с физической точки зрения способ рендеринга. Используя непосредственное моделирование распространения света для расчета освещения, этот алгоритм позволяет с минимальными усилиями получать весьма реалистичные сцены, для рендеринга которых традиционным способом ушло бы неимоверное количество усилий.

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

трассировка лучей от позиции наблюдателя
трассировка лучей от позиции наблюдателя

Одним из первых вопросов, которым можно задаться при рассмотрении данного алгоритма: а до какого вообще момента нам нужно моделировать движение луча? С точки зрения физики, практически каждый объект, что нас окружает, отражает хоть сколько-то света. Соответственно, естественный способ моделирования пути фотона - вычисление траектории движения вплоть до тех пор, пока количество отраженного света от объекта станет настолько малым, что им можно пренебречь. Такой метод безусловно имеет место быть, однако является крайне непроизводительным, и поэтому в любом трассирующем лучи алгоритме все же приходится жертвовать физической достоверностью изображения ради получения картинки за хоть сколько-то вменяемое время. Чаще всего выбор числа отражений луча зависит от сцены - для грубых диффузных поверхностей требуется гораздо меньше итераций, нежели для зеркальных или металлических (можете вспомнить классический пример с двумя зеркалами в лифте - свет в них отражается огромное число раз, создавая эффект бесконечного туннеля, и мы должны уметь это моделировать).

различные материалы, отрисованные физически-корректным рендерингом
различные материалы, отрисованные физически-корректным рендерингом

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

Я же для своего алгоритма решил условно выделить для материала объекта следующие параметры:

  • Отражательная способность (reflectance) - какое количество и какой волны свет отражает каждый объект

  • Шероховатость поверхности (roughness) - насколько сильно лучи рассеиваются при столкновении с объектом

  • Излучение энергии (emittance) - количество и длина волны света, которую излучает объект

  • Прозрачность (transparency/opacity) - отношение пропущенного сквозь объект света к отраженному

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

Реализуем наш алгоритм на GLSL

К сожалению (или к счастью?) сегодня мы не будем делать профессиональный трассировщик путей, и ограничимся лишь базовым алгоритмом с возможностью трассировать на сцене параллелепипеды и сферы. Для них относительно легко находить пересечения c лучем, рассчитывать нормали, да и в целом такого набора примитивов уже достаточно, чтобы отрендерить классический cornell-box.

один из вариантов cornell box'a для тестирования корректности рендеринга
один из вариантов cornell box'a для тестирования корректности рендеринга

Основной алгоритм мы будем делать во фрагментном GLSL шейдере. В принципе, даже если вы не знакомы с самим языком, код будет достаточно понятным, так как во многом совпадает с языком С: в нашем распоряжении есть функции и структуры, возможность писать условные конструкции и циклы, и ко всему прочему добавляется возможность пользоваться встроенными примитивами для математических расчетов - vec2, vec3, mat3 и т.д.

Ну что же, давайте к коду! Для начала зададим наши примитивы и структуру материала:

struct Material
{
    vec3 emmitance;
    vec3 reflectance;
    float roughness;
    float opacity;
};

struct Box
{
    Material material;
    vec3 halfSize;
    mat3 rotation;
    vec3 position;
};

struct Sphere
{
    Material material;
    vec3 position;
    float radius;
};

Для примитивов реализуем проверки пересечения с лучем: будем принимать начало луча и его направление, а возвращать расстояние до ближайшей точки пересечения и нормаль объекта в этой точке, если, конечно, такое пересечение в принципе произошло:

bool IntersectRaySphere(vec3 origin, vec3 direction, Sphere sphere, out float fraction, out vec3 normal)
{
    vec3 L = origin - sphere.position;
    float a = dot(direction, direction);
    float b = 2.0 * dot(L, direction);
    float c = dot(L, L) - sphere.radius * sphere.radius;
    float D = b * b - 4 * a * c;

    if (D < 0.0) return false;

    float r1 = (-b - sqrt(D)) / (2.0 * a);
    float r2 = (-b + sqrt(D)) / (2.0 * a);

    if (r1 > 0.0)
        fraction = r1;
    else if (r2 > 0.0)
        fraction = r2;
    else
        return false;

    normal = normalize(direction * fraction + L);

    return true;
}

bool IntersectRayBox(vec3 origin, vec3 direction, Box box, out float fraction, out vec3 normal)
{
    vec3 rd = box.rotation * direction;
    vec3 ro = box.rotation * (origin - box.position);

    vec3 m = vec3(1.0) / rd;

    vec3 s = vec3((rd.x < 0.0) ? 1.0 : -1.0,
        (rd.y < 0.0) ? 1.0 : -1.0,
        (rd.z < 0.0) ? 1.0 : -1.0);
    vec3 t1 = m * (-ro + s * box.halfSize);
    vec3 t2 = m * (-ro - s * box.halfSize);

    float tN = max(max(t1.x, t1.y), t1.z);
    float tF = min(min(t2.x, t2.y), t2.z);

    if (tN > tF || tF < 0.0) return false;

    mat3 txi = transpose(box.rotation);

    if (t1.x > t1.y && t1.x > t1.z)
        normal = txi[0] * s.x;
    else if (t1.y > t1.z)
        normal = txi[1] * s.y;
    else
        normal = txi[2] * s.z;

    fraction = tN;

    return true;
}

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

Хранить все наши объекты будем в обычных массивах, благо, GLSL создавать их также позволяет. Сцена у нас небольшая, поэтому никаких оптимизаций при поиске пересечения не реализуем - просто проходим по всем объектам и вычисляем ближайшее расстояние до точки пересечения:

#define FAR_DISTANCE 1000000.0
#define SPHERE_COUNT 3
#define BOX_COUNT 8

Sphere spheres[SPHERE_COUNT];
Box boxes[BOX_COUNT];

bool CastRay(vec3 rayOrigin, vec3 rayDirection, out float fraction, out vec3 normal, out Material material)
{
    float minDistance = FAR_DISTANCE;

    for (int i = 0; i < SPHERE_COUNT; i++)
    {
        float D;
        vec3 N;
        if (IntersectRaySphere(rayOrigin, rayDirection, spheres[i], D, N) && D < minDistance)
        {
            minDistance = D;
            normal = N;
            material = spheres[i].material;
        }
    }

    for (int i = 0; i < BOX_COUNT; i++)
    {
        float D;
        vec3 N;
        if (IntersectRayBox(rayOrigin, rayDirection, boxes[i], D, N) && D < minDistance)
        {
            minDistance = D;
            normal = N;
            material = boxes[i].material;
        }
    }

    fraction = minDistance;
    return minDistance != FAR_DISTANCE;
}

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

Трассировка пути

В нашей реализации каждый объект может излучать свет, отражать свет, и поглощать (случай с преломлением пока опустим). В таком случае формулу для расчета отраженного света от поверхности можно задать следующим образом: L' = E + f*L, где E - излучаемый объектом свет (emittance), f - отражаемый объектом свет (reflectance), L - свет, упавший на объект, и L' - то, что объект в итоге излучает.

И в итоге такое выражение легко представить в виде итеративного алгоритма:

// максимальное количество отражений луча
#define MAX_DEPTH 8

vec3 TracePath(vec3 rayOrigin, vec3 rayDirection)
{
    vec3 L = vec3(0.0); // суммарное количество света
    vec3 F = vec3(1.0); // коэффициент отражения
    for (int i = 0; i < MAX_DEPTH; i++)
    {
        float fraction;
        vec3 normal;
        Material material;
        bool hit = CastRay(rayOrigin, rayDirection, fraction, normal, material);
        if (hit)
        {
            vec3 newRayOrigin = rayOrigin + fraction * rayDirection;
            vec3 newRayDirection = ...
            // рассчитываем, куда отразится луч

            rayDirection = newRayDirection;
            rayOrigin = newRayOrigin;

            L += F * material.emmitance;
            F *= material.reflectance;
        }
        else
        {
            // если столкновения не произошло - свет ничто не испускает
            F = vec3(0.0);
        }
    }
    // возвращаем суммарный вклад освещения
    return L;
}

Если бы мы писали наш код на условном C++, можно было бы напрямую получать L как результат работы рекурсивно вызываемой функции CastRay. Однако, GLSL не разрешает рекурсивные вызовы функций в любом виде, поэтому приходится развернуть наш алгоритм так, чтобы он работал итеративно. С каждым отражением мы уменьшаем коэффициент, на который умножается испускаемый или отражаемый объектом свет, и тем самым повторяем описанную выше формулу. В моей реализации потенциально каждый объект может излучать какое-то количество света, поэтому emittance учитывается при каждом столкновении. Если же луч ни с чем не сталкивается, мы считаем, что никакого света до нас не дошло. В принципе для таких случаев можно добавить выборку из карты окружения или задать "дневной свет", но после экспериментов с этим я понял, что больше всего мне нравится текущая реализация, с пустотой вокруг сцены.

Об отражениях

Теперь давайте решим следующий вопрос: а по какому же принципу луч отражается от объекта? Очевидно, что в нашем path-tracer'е это будет зависеть от нормали в точке падения и микро-рельефа поверхности. Если обратиться к реальному миру, мы увидим, что для гладких материалов (таких как отполированный металл, стекло, вода) отражение будет очень четким, так как все лучи, падающие на объект под одним углом, будут и отражаться примерно под одинаковым углом (см. specular на картинке ниже), когда как для шероховатых, неровных поверхностей мы наблюдаем очень размытые отражения, чаще всего диффузные (см. diffuse на картинке ниже), так как лучи распространяются по полусфере относительно нормали объекта. Именно этой закономерностью мы и воспользуемся, задав итоговое направление отраженного луча как D = normalize(a * R + (1 - a) * T), где a - коэффициент шероховатости/гладкости поверхности, R - идеально отраженный луч, T - луч, отраженный в случаном направлении в полусфере относительно нормали. Очевидно, что при коэффициенте a = 1 в такой формуле мы всегда будет получать идеальное отражение луча, а при a = 0, наоборот, равномерно распределенное по полусфере. При коэффициенте шероховатости, лежащем в интервале от 0 до 1, на выходе будем иметь некоторое распределение лучей, ориентированное по углу отражения, что в вполне корректно и как раз характерно для глянцевых поверхностей (см. glossy на картинке ниже).

распределение лучей для различных типов поверхностей
распределение лучей для различных типов поверхностей

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

#define PI 3.1415926535

vec3 RandomSpherePoint(vec2 rand)
{
    float cosTheta = sqrt(1.0 - rand.x);
    float sinTheta = sqrt(rand.x);
    float phi = 2.0 * PI * rand.y;
    return vec3(
        cos(phi) * sinTheta,
        sin(phi) * sinTheta,
        cosTheta
    );
}

vec3 RandomHemispherePoint(vec2 rand, vec3 n)
{
    vec3 v = RandomSpherePoint(rand);
    return dot(v, n) < 0.0 ? -v : v;
}

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

vec3 hemisphereDistributedDirection = RandomHemispherePoint(Random2D(), normal);

vec3 randomVec = normalize(2.0 * Random3D() - 1.0);

vec3 tangent = cross(randomVec, normal);
vec3 bitangent = cross(normal, tangent);
mat3 transform = mat3(tangent, bitangent, normal);

vec3 newRayDirection = transform * hemisphereDistributedDirection;

Небольшое примечание: здесь и далее Random?D генерирует случайные числа в интервале от 0 до 1. В GLSL шейдере делать это можно разными способами. Я использую следующую функцию, генерирующую случайным шум без явных паттернов (любезно взята со StackOverflow по первому запросу):

float RandomNoise(vec2 co)
{
    return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);
}

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

отражения с различной шероховатостью
отражения с различной шероховатостью

После всех наших модификацийкод нашей функции TracePath будет выглядеть вот так:

vec3 TracePath(vec3 rayOrigin, vec3 rayDirection)
{
    vec3 L = vec3(0.0);
    vec3 F = vec3(1.0);
    for (int i = 0; i < MAX_DEPTH; i++)
    {
        float fraction;
        vec3 normal;
        Material material;
        bool hit = CastRay(rayOrigin, rayDirection, fraction, normal, material);
        if (hit)
        {
            vec3 newRayOrigin = rayOrigin + fraction * rayDirection;
            vec3 hemisphereDistributedDirection = RandomHemispherePoint(Random2D(), normal);

            randomVec = normalize(2.0 * Random3D() - 1.0);

            vec3 tangent = cross(randomVec, normal);
            vec3 bitangent = cross(normal, tangent);
            mat3 transform = mat3(tangent, bitangent, normal);
            
            vec3 newRayDirection = transform * hemisphereDistributedDirection;
                
            vec3 idealReflection = reflect(rayDirection, normal);
            newRayDirection = normalize(mix(newRayDirection, idealReflection, material.roughness));
            
            // добавим небольшое смещение к позиции отраженного луча
            // константа 0.8 тут взята произвольно
            // главное, чтобы луч случайно не пересекался с тем же объектом, от которого отразился
            newRayOrigin += normal * 0.8;

            rayDirection = newRayDirection;
            rayOrigin = newRayOrigin;

            L += F * material.emmitance;
            F *= material.reflectance;
        }
        else
        {
            F = vec3(0.0);
        }
    }

    return L;
}

Преломление света

Давайте рассмотрим еще такой важный для нас эффект, как преломление света. Все же помнят, как соломка, находящаяся в стакане, кажется сломанной в том месте, где она пересекается с водой? Этот эффект происходит потому, что свет, переходя между двумя средами с разными свойствами, меняет свою волновую скорость. Вдаваться в подробности того, как это работает с физической точки зрения мы не будем, вспомним лишь, что если свет падаем под углом a, то угол преломления b можно посчитать по следующей несложной формуле (см. закон Снеллиуса): b = arcsin(sin(a) * n1 / n2), где n1 - показатель преломления среды, из которой пришел луч, a n2 - показатель преломления среды, в которую луч вошел. И к счастью для нас, показатели преломления уже рассчитаны для интересующих нас сред, достаточно лишь открыть википедию, или, накрайняк, учебник по физике.

Угол падения, отражения и преломления
Угол падения, отражения и преломления

Стоит заметить следующий интересный факт: sin(a) принимает значения от 0 для 1 для острых углов. Относительный показатель преломления n1 / n2 может быть любым, в том числе большим 1. Но тогда выходит, что аргумент sin(a) * n1 / n2 не всегда находится в области определения функции arcsin. Что же происходит с углом преломления? Почему наша формула не работает для такого случая, хотя с физической точки зрения ситуация вполне возможная?

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

эффект Френеля
эффект Френеля

Сколько же света в итоге будет отражено от поверхности, а сколько пройдет сквозь нее? Чтобы ответить на этот вопрос, нам необходимо обратиться к формулам Френеля, которые как раз и используются для расчета коэфициентов отрадения и пропускания. Но не спешите ужасаться - в нашем трассировщике мы не будем расписывать эти громоздкие выражения. Давайте воспользуемся более простой аппроксимирующей формулой за авторством Кристофе Шлика - аппроксимацией Шлика. Она достаточно простая в реализации и дает приемлимые визуальные результаты, поэтому не вижу причин не добавить ее в наш код:

float FresnelSchlick(float nIn, float nOut, vec3 direction, vec3 normal)
{
    float R0 = ((nOut - nIn) * (nOut - nIn)) / ((nOut + nIn) * (nOut + nIn));
    float fresnel = R0 + (1.0 - R0) * pow((1.0 - abs(dot(direction, normal))), 5.0);
    return fresnel;
}

Ну что же, применим весь наш пройденный материал: первым делом реализуем функцию, которая бы возвращала преломленный луч с учетом полного внутреннего отражения, о котором мы говорили выше:

vec3 IdealRefract(vec3 direction, vec3 normal, float nIn, float nOut)
{
    // проверим, находимся ли мы внутри объекта
    // если да - учтем это при рассчете сред и направления луча
    bool fromOutside = dot(normal, direction) < 0.0;
    float ratio = fromOutside ? nOut / nIn : nIn / nOut;

    vec3 refraction, reflection;
    refraction = fromOutside ? refract(direction, normal, ratio) : -refract(-direction, normal, ratio);
    reflection = reflect(direction, normal);

    // в случае полного внутренного отражения refract вернет нам 0.0
    return refraction == vec3(0.0) ? reflection : refraction;
}

Чтобы учитывать эффект френеля, будем просто генерировать случайное число, и на его основе решать, преломится ли луч или отразится. Также не забываем, что у наших объектов есть параметр плотности, который также отвечает за отношение преломленного света к отраженному. Соединить оба этих условия можно по-разному, но я взял самый примитивный вариант:

bool IsRefracted(float rand, vec3 direction, vec3 normal, float opacity, float nIn, float nOut)
{
    float fresnel = FresnelSchlick(nIn, nOut, direction, normal);
    return opacity > rand && fresnel < rand;
}

Теперь наконец можно склеить все вместе: добавим в нашу функцию TracePath логику, которая бы рассчитывала преломление света и при этом учитывала и шереховатость объекта - ее для полупрозрачных тел никто не отменял:

#define N_IN 0.99
#define N_OUT 1.0

vec3 TracePath(vec3 rayOrigin, vec3 rayDirection)
{
    vec3 L = vec3(0.0);
    vec3 F = vec3(1.0);
    for (int i = 0; i < MAX_DEPTH; i++)
    {
        float fraction;
        vec3 normal;
        Material material;
        bool hit = CastRay(rayOrigin, rayDirection, fraction, normal, material);
        if (hit)
        {
            vec3 newRayOrigin = rayOrigin + fraction * rayDirection;
            vec3 hemisphereDistributedDirection = RandomHemispherePoint(Random2D(), normal);

            randomVec = normalize(2.0 * Random3D() - 1.0);
            vec3 tangent = cross(randomVec, normal);
            vec3 bitangent = cross(normal, tangent);
            mat3 transform = mat3(tangent, bitangent, normal);
            vec3 newRayDirection = transform * hemisphereDistributedDirection;
                
            // проверяем, преломится ли луч. Если да, то меняем логику рассчета итогового направления
            bool refracted = IsRefracted(Random1D(), rayDirection, normal, material.opacity, N_IN, N_OUT);
            if (refracted)
            {
                vec3 idealRefraction = IdealRefract(rayDirection, normal, N_IN, N_OUT);
                newRayDirection = normalize(mix(-newRayDirection, idealRefraction, material.roughness));
                newRayOrigin += normal * (dot(newRayDirection, normal) < 0.0 ? -0.8 : 0.8);
            }
            else
            {
                vec3 idealReflection = reflect(rayDirection, normal);
                newRayDirection = normalize(mix(newRayDirection, idealReflection, material.roughness));
                newRayOrigin += normal * 0.8;
            }

            rayDirection = newRayDirection;
            rayOrigin = newRayOrigin;

            L += F * material.emmitance;
            F *= material.reflectance;
        }
        else
        {
            F = vec3(0.0);
        }
    }
    return L;
}

Для коэффициентов преломления N_IN и N_OUT я взял два очень близких числаdoo. Это не совсем физически-корректно, однако создает желаемый эффект того, что поверхности сделаны из стекла (как шар на первом скриншоте статьи). Можете смело их изменить и посмотреть, как поменяется угол преломления лучей, проходящих сквозь объект.

Давайте уже запускать лучи!

Дело осталось за малым: инициализировать нашу сцену в начале шейдера, передать внутрь все параметры камеры, и запустить лучи по направлению взгляда. Начнем с камеры: от нее нам потребуется несколько параметров: direction - направление взгляда в трехмерном пространстве. up - направление "вверх" относительно взгляда (нужен чтобы задать матрицу перевода в мировое пространство), а также fov - угол обзора камеры. Также передадим для рассчета чисто утилитарные вещи - экранную позицию обрабатываемого пикселя (от 0 до 1 по x и y) и размер окна для рассчета отношения сторон. В математику в коде тоже особо углубляться не буду - о том, как переводить из пространство экрана в пространство мира можно почитать к примеру в этой замечательной статье.

vec3 GetRayDirection(vec2 texcoord, vec2 viewportSize, float fov, vec3 direction, vec3 up)
{
    vec2 texDiff = 0.5 * vec2(1.0 - 2.0 * texcoord.x, 2.0 * texcoord.y - 1.0);
    vec2 angleDiff = texDiff * vec2(viewportSize.x / viewportSize.y, 1.0) * tan(fov * 0.5);

    vec3 rayDirection = normalize(vec3(angleDiff, 1.0f));

    vec3 right = normalize(cross(up, direction));
    mat3 viewToWorld = mat3(
        right,
        up,
        direction
    );

    return viewToWorld * rayDirection;
}

Как бы ни прискорбно это было заявлять, но законы, по которым отражаются наши лучи имеют некоторую случайность, и одного семпла на пиксель нам будет мало. И даже 16 семплов на пиксель не достаточно. Но не расстраивайтесь! Давайте найдем компромисс: каждый кадр будем считать от 4 до 16 лучей, но при этом результаты кадров аккамулировать в одну текстуру. В итоге мы делаем не так много работы каждый кадр, можем летать по нашей сцене (хоть и испытывая на своих глазах ужасные шумы), а при статичной картинке качество рендера будет постепенно расти, пока не упрется в точность float'а. Преимущества такого подхода видны невооруженным взглядом:

рендер одного кадра и нескольких, сложенных вместе
рендер одного кадра и нескольких, сложенных вместе

В итоге наша функция main будет выглядеть примерно следующим образом (в алгоритме нет ничего сложного - просто запускаем несколько лучей и считаем среднее от результата TracePath):

// ray_tracing_fragment.glsl

in vec2 TexCoord;
out vec4 OutColor;

uniform vec2 uViewportSize;
uniform float uFOV;
uniform vec3 uDirection;
uniform vec3 uUp;
uniform float uSamples;

void main()
{
    // заполняем нашу сцену объектами
    InitializeScene();

    vec3 direction = GetRayDirection(TexCoord, uViewportSize, uFOV, uDirection, uUp);

    vec3 totalColor = vec3(0.0);
    for (int i = 0; i < uSamples; i++)
    {
        vec3 sampleColor = TracePath(uPosition, direction);
        totalColor += sampleColor;
    }

    vec3 outputColor = totalColor / float(uSamples);
    OutColor = vec4(outputColor, 1.0);
}

Аккамулируем!

Давайте закроем вопрос с тем, как мы будем отображать результат работы трассировщика. Очевидно, что если мы решили накапливать кадры в одной текстуре, то классический вариант с форматом вида RGB (по байту на каждый канал) нам не подойдет. Лучше взять что-то вроде RGB32F (проще говоря формат, поддерживающий числа с плавающей точкой одинарной точности). Таким образом мы сможем накапливать достаточно большое количество кадров прежде чем упремся в потолок из-за потерь точности вычислений.

Также сходу напишем шейдер, принимающий нашу аккамулирующую текстуру и вычисляющий среднее от множества кадров. Тут же применим тональную коррекцию изображения, а затем гамма-коррекцию (в коде я использую самый простой вариант tone-mapping'а, вы можете взять что-то посложнее, к примеру кривую Рейнгарда):

// post_process_fragment.glsl

in vec2 TexCoord;
out vec4 OutColor;

uniform sampler2D uImage;
uniform int uImageSamples;

void main()
{
    vec3 color = texture(uImage, TexCoord).rgb;
    color /= float(uImageSamples);
    color = color / (color + vec3(1.0));
    color = pow(color, vec3(1.0 / 2.2));
    OutColor = vec4(color, 1.0);
}

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

virtual void OnUpdate() override
{
    // получаем текущую камеру и текстуру, в которую осуществляется рендер
    auto viewport = Rendering::GetViewport();
    auto output = viewport->GetRenderTexture();

    // получим текущие параметры камеры (позицию, угол обзора и т.д.)
    auto viewportSize = Rendering::GetViewportSize();
    auto cameraPosition = MxObject::GetByComponent(*viewport).Transform.GetPosition();
    auto cameraRotation = Vector2{ viewport->GetHorizontalAngle(), viewport->GetVerticalAngle() };
    auto cameraDirection = viewport->GetDirection();
    auto cameraUpVector = viewport->GetDirectionUp();
    auto cameraFOV = viewport->GetCamera<PerspectiveCamera>().GetFOV();

    // проверим, что камера неподвижна. От этого зависит, нужно ли очищать предыдущий кадр
    bool accumulateImage = oldCameraPosition == cameraPosition &&
                           oldCameraDirection == cameraDirection &&
                           oldFOV == cameraFOV;

    // при движении снизим количество семплов ради приемлемой частоты кадров
    int raySamples = accumulateImage ? 16 : 4;

    // установим все униформы в шейдере, осуществляющем трассировку лучей
    this->rayTracingShader->SetUniformInt("uSamples", raySamples);
    this->rayTracingShader->SetUniformVec2("uViewportSize", viewportSize);
    this->rayTracingShader->SetUniformVec3("uPosition", cameraPosition);
    this->rayTracingShader->SetUniformVec3("uDirection", cameraDirection);
    this->rayTracingShader->SetUniformVec3("uUp", cameraUpVector);
    this->rayTracingShader->SetUniformFloat("uFOV", Radians(cameraFOV));

    // меняем тип блендинга в зависимости от того, аккамулируем ли мы кадры в одну текстуру
    // также считаем количество кадров, чтобы потом получить среднее значение
    if (accumulateImage)
    {
        Rendering::GetController().GetRenderEngine().UseBlending(BlendFactor::ONE, BlendFactor::ONE);
        Rendering::GetController().RenderToTextureNoClear(this->accumulationTexture, this->rayTracingShader);
        accumulationFrames++;
    }
    else
    {
        Rendering::GetController().GetRenderEngine().UseBlending(BlendFactor::ONE, BlendFactor::ZERO);
        Rendering::GetController().RenderToTexture(this->accumulationTexture, this->rayTracingShader);
        accumulationFrames = 1;
    }

    // рассчитаем среднее от множества кадров и сохраним в рендер-текстуру камеры
    this->accumulationTexture->Bind(0);
    this->postProcessShader->SetUniformInt("uImage", this->accumulationTexture->GetBoundId());
    this->postProcessShader->SetUniformInt("uImageSamples", this->accumulationFrames);
    Rendering::GetController().RenderToTexture(output, this->postProcessShader);

    // обновим сохраненные параметры камеры
    this->oldCameraDirection = cameraDirection;
    this->oldCameraPosition = cameraPosition;
    this->oldFOV = cameraFOV;
}

В заключение

Ну что же, на этом все! Всем спасибо за прочтение этой небольшой статьи. Хоть мы и не успели рассмотреть множество интересных эффектов в path-tracing'е, опустили обсуждение различных ускоряющих трассировку структур данных, не сделали денойзинг, но все равно итоговый результат получился весьма сносным. Надеюсь вам понравилось и вы нашли для себя что-то новое. Я же по традиции приведу в конце несколько скриншотов, полученных с помощью path-tracer'а:

эффект бесконечного туннеля от двух параллельных зеркал
эффект бесконечного туннеля от двух параллельных зеркал
преломление света при взгляде сквозь прозрачную сферу
преломление света при взгляде сквозь прозрачную сферу
непрямое освещение и мягкие тени
непрямое освещение и мягкие тени

Ссылки на связанные ресурсы

Let's block ads! (Why?)

24x01 I2C на ATTINY13 без TWI

/*
        InDRIVE v4 Application (chip like: FT24C01, Selfprg Must Be Enabled!!! )

        Fuse BYTES
        Low(0x7A) :             High(0xEE) :
        SPIEN = 1               SELFPRGEN = 1
        EESAVE = 0              DWEN = 0
        WDTON = 0               BODLEVEL1 = 0
        CKDIV8 = 0              BODLEVEL0 = 0
        SUT1 = 0                RSTDISBL = 1
        SUT0 = 1
        CKSEL1 = 0
        CKSEL0 = 1

*/

.include "tn13def.inc"

/* On/Off Defines */
.equ    OFF                     =0
.equ    ON                      =1
/* Chip Pinouts */
.equ    FT24Cxx         =0
.equ    InDRIVE_v4      =1

/* Reset Controller Enable/Disable */
.equ    RESET_CNT       =OFF
/* Chip Pinouts Define: InDrivev4/Regular I2C Chip */
.equ    CHIP_PINOUTS    =FT24Cxx

.equ    DEBUG           =OFF
/* I2C Mode Define MODE 1/MODE 2 */
.equ    MODE_1          =1
.equ    MODE_2          =2
.EQU    I2C_MODE        =MODE_1

/* Define I2C Parameters */
/* Page Size for Write in BYTES 4/8/16 */
/* 128/256 Size in MODE 1 Protocol can't receive more than 128 Bytes */
.IF I2C_MODE == MODE_2
        .MESSAGE "Compile for I2C Mode 2"
.equ    I2C_PAGE_SIZE   =8
.equ    I2C_EEP_SIZE    =256
.ELSE
        .MESSAGE "Compile for I2C Mode 1"
.equ    I2C_PAGE_SIZE   =4
.equ    I2C_EEP_SIZE    =128
.ENDIF

/* 
Modes Define

        A - Address
        iD - Data From Master
        oD - Data To Master

        MODE 1
                Write - [START][AAAAAAA0][iD + 0]...[iD + I2C_PAGE_SIZE - 1][STOP]
                Read -  [START][AAAAAAA1][oD + 0]...[oD + I2C_EEP_SIZE - 1][STOP]
        MODE 2 
                Write - [START][10100000][AAAAAAAA][iD + 0]...[iD + I2C_PAGE_SIZE - 1][STOP]
                Read -  [START][10100001][oD + 0]...[oD + I2C_EEP_SIZE - 1][STOP]
*/

/*
        I2C slave, fSCL = 400kHz
        FULLY Implemented I2C Protocol for 24xx01/24xx02
        And RESET Controller FOR InDRIVE(v4)

**************************************************************   
        ATTINY13A I2C Configuration
**************************************************************   
        pin configuration InDRIVE v4:

                       ,---_---.
    (RESET/PB5)     nc |1     8| VCC
          (PB3)  inRST |2     7| SCL    (PB2)
          (PB4) outRST |3     6| EMU24X (PB1) SDA pin from CORE
                   GND |4     5| nc     (PB0) unused SDA pin
                       `-------'

        pin configuration FT24C01:

                       ,---_---.
    (RESET/PB5)     nc |1     8| VCC
                  (PB3)         nc |2     7| nc         (PB2)
            (PB4)       nc |3     6| SCL        (PB1) SCL pin I2C
                   GND |4     5| SDA    (PB0) SDA pin I2C
                       `-------'

        pin configuration FT24C02:

                       ,---_---.
    (RESET/PB5)     A0 |1     8| VCC
           (PB3)        A1 |2     7| nc         (PB2)
            (PB4)       A2 |3     6| SCL        (PB1) SCL pin I2C
                   GND |4     5| SDA    (PB0) SDA pin I2C
                       `-------'

*/
/* Pins Define */
.IF CHIP_PINOUTS == InDRIVE_v4
        .MESSAGE "Chip Pinouts: InDRIVE v4"
.equ    EMU24X  = 1
.equ    SCL     = 2
.ELSE
.equ    EMU24X  = 0
.equ    SCL     = 1
        .MESSAGE "Chip Pinouts: Regular 24Cxx"
.ENDIF
.equ    SDA     = EMU24X
.equ    ACK     = SDA
.equ    inRST   = 3
.equ    outRST  = 4


.def    TMPnoINT                =R19
.def    Counter                 =R3
.def    CounterInWrite  =R4
.def    SREGST                  =R5

/* Real ATTINY13A FLASH Page SIZE In Bytes */
.equ    PAGESIZEB               =(PAGESIZE*2)

/* SRAM Mapping */
.DSEG
.IF I2C_MODE == MODE_2
        _I2c_device_inaddr:  .BYTE 1    /* MODE 2 Region */
        _I2c_device_myaddr:  .BYTE 1
.ENDIF
        _valSPMCSR: .BYTE 1
        _I2c_data_buffer:       .BYTE I2C_PAGE_SIZE
        _I2c_FLASH_buffer:      .BYTE PAGESIZEB
.cseg

/* Read/Write Pointers */
.def    I2c_start_addr  =R16
.def    I2c_wr_counter  =R17
.def    I2c_wr_pointer  =R18
.def    I2c_rd_pointer  =R21

.def    _PINB                   =R20

.IF I2C_MODE != MODE_2
        .IF I2C_MODE != MODE_1
                .error "Invalid mode for I2C Selected, please correct I2C_MODE define"
        .ENDIF
.ENDIF

.cseg

.org 0//Reset
.IF RESET_CNT == ON
                rjmp    WaitinRSTHI             ;RESET
                reti                                    ;INT0addr       = 0x0001        ; External Interrupt 0
                rjmp    ResetInProcess  ;PCI0addr       = 0x0002        ; External Interrupt Request 0
.ELSE
                rjmp    main                    ;RESET
                reti                                    ;INT0addr       = 0x0001        ; External Interrupt 0
                reti                                    ;PCI0addr       = 0x0002        ; External Interrupt Request 0
.ENDIF
                reti                                    ;OVF0addr       = 0x0003        ; Timer/Counter0 Overflow
                reti                                    ;ERDYaddr       = 0x0004        ; EEPROM Ready
                reti                                    ;ACIaddr        = 0x0005        ; Analog Comparator
                reti                                    ;OC0Aaddr       = 0x0006        ; Timer/Counter Compare Match A
                reti                                    ;OC0Baddr       = 0x0007        ; Timer/Counter Compare Match B
                reti                                    ;WDTaddr        = 0x0008        ; Watchdog Time-out
                reti                                    ;ADCCaddr       = 0x0009        ; ADC Conversion Complete

// *******************************************************************************************
// **   Reset Processor                                                                                 **
// *******************************************************************************************
.IF RESET_CNT == ON
        .MESSAGE "Reset Controller Function is: ON"
ResetInProcess: ;Proccess reset
                sbic    pinb,inRST      ;Process reset on falling edge
                reti

WaitinRSTHI:
                cli

                ;Wait ~80 mS 0x04E360 on 9.6MHz
                ldi             R16,0x10
                ldi             R17,0xE3
                ldi             R18,0x60
                ;Set RESET Enable
                cbi             PORTB,outRST

_CntLO:
                dec             R18
                BRNE    _CntLO
_CntME:
                dec             R17
                BRNE    _CntLO
_CntHI:
                dec             R16
                BRNE    _CntLO

                ; And Clear Iterrupt flag for normal exit
                ldi             R16,(1 << PCIF)
                out             GIFR,R16
.ELSE
        .MESSAGE "Reset Controller Function is: OFF"
.ENDIF  // RESET_CNT == ON
                
// *******************************************************************************************************
// **   Main Programm                                                                                   **
// *******************************************************************************************************
main:
                ;init STACK
                ldi             TMPnoINT,low(RAMEND)
                out             SPL,TMPnoINT

                ;init IO
                ldi             TMPnoINT,0x00
                out             MCUCR,TMPnoINT
                ;Outputs to HI (Pull UP)
                ldi             TMPnoINT,0xFF
                out             PORTB,TMPnoINT
.IF RESET_CNT == ON
                ;Output enable for outRST, all other for input
                sbi             PORTB, outRST
                sbi             PINB, inRST
                sbi             DDRB,outRST     ; Output Enable
                ;Init Interrupt         
        ldi     TMPnoINT, (1 << inRST)    ; set pin change interrupt for inRST
        out     PCMSK, TMPnoINT         
                ldi             TMPnoINT, (1<<PCIE)               ; unmask interrupt PCIE
                out             GIMSK,TMPnoINT
                SEI                                     ;Enable PCIE Int Processing
.ELSE
                CLI             ; Disable Reset Controller
.ENDIF

.IF I2C_MODE == MODE_2
                /* Set I2C Device Address */
                ldi             TMPnoINT, 0xA0
                sts             _I2c_device_myaddr,TMPnoINT
.ENDIF

                /* Clear Buffer Pointers */
                clr             I2c_wr_counter
                clr             I2c_wr_pointer
                clr             I2c_start_addr
                clr             I2c_rd_pointer


main_loop:

lI2c_get:
        sbi portb,ack
        sbis pinb,scl                                            ;wait for SCL&SDA=1
        rjmp lI2c_get
        sbis pinb,sda
        rjmp lI2c_get
lI2c_wait_for_start:
        sbis pinb,scl                                            ;wait for SCL=1,SDA=0 (START)
        rjmp lI2c_get
        sbic pinb,sda
        rjmp lI2c_wait_for_start
lI2c_get_0:                                                  ;clear receive area
.IF I2C_MODE == MODE_2
        clr r22
        sts _I2c_device_inaddr,r22
.ENDIF
lI2c_get_1:                                                  ;get 1st byte
        sbi portb,ack
lI2c_10:                                                     ;wait for SCL=0
        sbic pinb,scl
        rjmp lI2c_10
        ldi r22,8                                                ;bits to receive=8
.IF I2C_MODE == MODE_2
        lds r23,_I2c_device_myaddr                               ;I2C address->R23
.ENDIF
lI2c_11:
        in      _PINB, pinb
        sbrs _PINB,scl                                            ;wait for SCL=1
        rjmp lI2c_11
        sbrc _PINB,sda
        rjmp lI2c_13
lI2c_12:                                                     ;if SDA=0
        sbic pinb,sda
        rjmp lI2c_wait_for_start                                  ;  SDA 0->1? I2CSTOP! (unexpected: wait for next start)
        sbic pinb,scl
        rjmp lI2c_12                                              ;  loop while SCL=1
        clc                                                      ;  SDA=0->C
        rjmp lI2c_15
lI2c_13:                                                     ;if SDA=1
        sbis pinb,sda
        rjmp lI2c_get_1                                           ;  SDA 1->0? I2CSTART! (repeated start)
        sbic pinb,scl
        rjmp lI2c_13                                              ;  loop while SCL=1
        sec                                                      ;  SDA=1->C
lI2c_15:
        rol r24
        dec r22
        brne lI2c_11                                              ;loop to next bit

.IF I2C_MODE == MODE_2
        sts _I2c_device_inaddr,r24
        SUB r24,r23                                              ;my address?
        cpi r24,2
        brlo lI2c_ack_1
        rjmp lI2c_exit                                            ;  no: exit and wait for next start
.ENDIF

lI2c_ack_1:                                                  ;  yes: generate ack

        clr             I2c_wr_counter                                                          ; Clear Write Buffer Pointers
        clr             I2c_wr_pointer
        clr             I2c_start_addr
        cbi portb,ack
        sbi ddrb,ack                                             ;pinb.ack = output (ACK)
lI2c_ack_10:
        sbis pinb,scl                                            ;wait for SCL=1
        rjmp lI2c_ack_10
lI2c_ack_11:
        sbic pinb,scl                                            ;wait for SCL=0
        rjmp lI2c_ack_11
/*************************************************************************
*               Select Read/Write
**************************************************************************/
.IF I2C_MODE == MODE_2
        cpi r24,0
        breq lI2c_get_2
.ELSE
        mov     I2c_start_addr,r24                              ; Extract Received Address
        lsr I2c_start_addr                                      ; And Read/Write BIT
        mov I2c_rd_pointer,I2c_start_addr       ; Upadate Read Address
        brcs lI2c_send_new_byte 
        rjmp    lI2c_ack_25
.ENDIF
/*************************************************************************
*               Sending Data to Master
**************************************************************************/
lI2c_send_new_byte:                                             ;read address received
        sbi ddrb,sda                                             ;pinb.sda = output (will send data)
/* Read From Flash */
        andi I2c_rd_pointer, (I2C_EEP_SIZE - 1)
        LDI     ZH,high(MemoryBlockFLASH<<1)
        LDI     ZL,low(MemoryBlockFLASH<<1)
        add ZL,I2c_rd_pointer
        inc I2c_rd_pointer
        lpm     R24, Z
        ldi r22,8

.IF DEBUG == 1
        rjmp lI2c_s1    
/* Read From SRAM */
ReadFromSram:
        andi I2c_rd_pointer, (I2C_EEP_SIZE - 1)
        LDI     ZH,high(_I2c_device_inaddr)
        LDI     ZL,low(_I2c_device_inaddr)
        add ZL,I2c_rd_pointer
        inc I2c_rd_pointer
        ld      R24, Z
        ldi r22,8
.ENDIF

lI2c_s1:
        cbi portb,sda
        sbrc r24,7
        sbi portb,sda
lI2c_s2:
        sbis pinb,scl                                            ;wait for SCL=1
        rjmp lI2c_s2
        lsl r24
lI2c_s3:
        sbic pinb,scl                                            ;wait for SCL=0
        rjmp lI2c_s3
        dec r22
        brne lI2c_s1
        sbi portb,sda                                            ;pinb.sda = 0 (will generate ACK)
        cbi ddrb,sda                                             ;pinb.sda = input (will receive data)

lI2c_s4:                                                                ;wait acknowloge receive
        sbis pinb,scl                                            ;wait for SCL=1
        rjmp lI2c_s4
lI2c_s5:
        sbic pinb,scl                                            ;wait for SCL=0
        rjmp lI2c_s5
        
        sbis pinb,sda                                                                                   ;if answer received, - continue
        rjmp    lI2c_send_new_byte

        rjmp lI2c_wait_for_start_stop                             ;wait for next start/stop

/*************************************************************************
*               Receiving Data from Master
**************************************************************************/
.IF I2C_MODE == MODE_2
lI2c_get_2:                                                  ;write address received: get 2nd byte
        cbi ddrb,ack                                             ;pinb.ack = output (ACK)
lI2c_20:                                                     ;wait for SCL=0
        sbic pinb,scl
        rjmp lI2c_20
        ldi r22,8                                                ;bits to receive=8
lI2c_21:
        sbis pinb,scl                                            ;wait for SCL=1
        rjmp lI2c_21
        sbic pinb,sda
        rjmp lI2c_23
lI2c_22:                                                     ;if SDA=0
        sbic pinb,sda
        rjmp lI2c_stop                                            ;  SDA 0->1? I2CSTOP! (finish this sequence)
        sbic pinb,scl
        rjmp lI2c_22                                              ;  loop while SCL=1
        clc                                                      ;  SDA=0->C
        rjmp lI2c_25
lI2c_23:                                                     ;if SDA=1
        sbis pinb,sda
        rjmp lI2c_get_1                                           ;  SDA 1->0? I2CSTART! (repeated start)
        sbic pinb,scl
        rjmp lI2c_23                                              ;  loop while SCL=1
        sec                                                      ;  SDA=1->C
lI2c_25:
        rol r24
        dec r22
        brne lI2c_21                                              ;loop to next bit
        mov I2c_start_addr,r24                                         ;store received I2C address
        mov I2c_rd_pointer,I2c_start_addr                                               ; Upadate Read Address
.ENDIF

ReceiveAcknowloge:
        cbi     portb,sda
        sbi     ddrb,sda
lI2c_ack_21:
        sbis pinb,scl                                            ;wait for SCL=1
        rjmp lI2c_ack_21
        sbic pinb,sda
        rjmp lI2c_ack_23
lI2c_ack_22:                                                     ;if SDA=0
        sbic pinb,sda
        rjmp lI2c_stop                                            ;  SDA 0->1? I2CSTOP! (finish this sequence)
        sbic pinb,scl
        rjmp lI2c_ack_22                                              ;  loop while SCL=1
        rjmp lI2c_ack_25
lI2c_ack_23:                                                     ;if SDA=1
        sbis pinb,sda
        rjmp lI2c_get_1                                           ;  SDA 1->0? I2CSTART! (repeated start)
        sbic pinb,scl
        rjmp lI2c_ack_23                                              ;  loop while SCL=1
lI2c_ack_25:

        cbi ddrb,ack                                             ;pinb.ack = input
        LDI     ZH,high(_I2c_data_buffer)
        LDI     ZL,low(_I2c_data_buffer)
lI2c_get_3:                                                  ;get 3rd byte
lI2c_30:                                                     ;wait for SCL=0
        sbic pinb,scl
        rjmp lI2c_30
        ldi r22,8                                                ;bits to receive=8
        mov     TMPnoINT, I2c_wr_pointer
        andi TMPnoINT, (I2C_PAGE_SIZE - 1)
        add ZL,TMPnoINT
lI2c_31:
        sbis pinb,scl                                            ;wait for SCL=1
        rjmp lI2c_31
        sbic pinb,sda
        rjmp lI2c_33_
lI2c_32:                                                     ;if SDA=0
        sbic pinb,sda
        rjmp lI2c_stop_                                            ;  SDA 0->1? I2CSTOP! (finish this sequence)
        sbic pinb,scl
        rjmp lI2c_32                                              ;  loop while SCL=1
        clc                                                      ;  SDA=0->C
        rjmp lI2c_35
lI2c_33_:
lI2c_33:                                                     ;if SDA=1
        sbis pinb,sda
        rjmp lI2c_start_                                           ;  SDA 1->0? I2CSTART! (repeated start)
        sbic pinb,scl
        rjmp lI2c_33                                              ;  loop while SCL=1
        sec                                                      ;  SDA=1->C
lI2c_35:
        rol r24
        dec r22
        brne lI2c_31                                              ;loop to next bit

        inc I2c_wr_counter
        inc I2c_wr_pointer
        inc I2c_rd_pointer
        st      Z,R24

        rjmp ReceiveAcknowloge

lI2c_wait_for_start_stop:                                    ;wait for start/stop
lI2c_ss_1:
        sbis pinb,scl                                            ;wait for SCL=1
        rjmp lI2c_ss_1
        sbic pinb,sda
        rjmp lI2c_ss_3
lI2c_ss_2:                                                   ;if SDA=0
        sbic pinb,sda
        rjmp lI2c_stop                                            ;  SDA 0->1? I2CSTOP! (finish this sequence)
        sbic pinb,scl
        rjmp lI2c_ss_2                                            ;  loop while SCL=1
        rjmp lI2c_ss_1
lI2c_ss_3:                                                   ;if SDA=1
        sbis pinb,sda
        rjmp lI2c_get_1                                           ;  SDA 1->0? I2CSTART! (repeated start)
        sbic pinb,scl
        rjmp lI2c_ss_3                                            ;  loop while SCL=1
        rjmp lI2c_ss_1                                            ;  SDA=1->C

lI2c_stop_:
lI2c_stop:                                                   ;if stop,
        cbi     ddrb, sda
        cpi     I2c_wr_counter,0x00
        brne WriteFlashRom
lI2c_exit:
        rjmp lI2c_get

lI2c_start_:
        rjmp lI2c_get_1

WriteFlashRom:
        cli                                                     ; Disable INTERRUPTS
        rcall WriteReceivedData
        clr             I2c_wr_counter                                                          ; Clear Write Buffer Pointers
        clr             I2c_wr_pointer
        sei                                                     ; Enable INTERRUPTS
        rjmp lI2c_get
/***********************************************************
*                       Write internal FLASH by page
************************************************************/
//--------- Erase/Program Page FROM FLASH to SRAM
BackupFlashPage:
        ldi             YH,high(_I2c_FLASH_buffer) ; Load SRAM Buffer 
        ldi             YL,low(_I2c_FLASH_buffer)

        mov             TMPnoINT, I2c_start_addr
        andi    TMPnoINT, ((I2C_EEP_SIZE - 1) & ~(PAGESIZEB - 1))

        ldi             ZH,high(MemoryBlockFLASH << 1)
        ldi             ZL,low(MemoryBlockFLASH << 1)

        adc             ZL,TMPnoINT
        brcc    NoIncBackupPage
        inc             ZH
NoIncBackupPage:

        ldi             R22, PAGESIZEB
BackupPageLoop:
        lpm             TMPnoINT,Z+
        st              Y+,TMPnoINT
        dec             R22
        brne    BackupPageLoop
        ret
//--------- Add received data to SRAM Page
WriteReceivedData:
        /* Have Data For Write ? */
        cpi             I2c_wr_counter,0x00
        brne    DataForWritePresent
        ret
DataForWritePresent:

        rcall BackupFlashPage

        ldi             YH,high(_I2c_FLASH_buffer) ; Load SRAM Backup Buffer Address
        ldi             YL,low(_I2c_FLASH_buffer)

        ldi             XH,high(_I2c_data_buffer) ; Load SRAM Receive Buffer Address
        ldi             XL,low(_I2c_data_buffer)

        mov             TMPnoINT, I2c_start_addr
        andi    TMPnoINT, (PAGESIZEB - 1)

        adc             YL,TMPnoINT
        brcc    NoIncPreparePage
        inc             YH
NoIncPreparePage:

        mov             CounterInWrite,TMPnoINT
        /* Make PAGE SIZE Window */
        cpi             I2c_wr_counter, I2C_PAGE_SIZE
        brlo    PreparePageLoop
        ldi             I2c_wr_counter, I2C_PAGE_SIZE
PreparePageLoop:
        ld              R22,X+
        st              Y+,R22

        dec             I2c_wr_counter
        breq    NormalPageWrite

        mov             TMPnoINT,CounterInWrite
        cpi             TMPnoINT, (PAGESIZEB - 1)
        brne    NoWritePageSizeExceeded

        rcall   Erase_page_by_SPM
        rcall   Write_Current_Page

        inc             CounterInWrite
        inc             I2c_start_addr
        dec             I2c_wr_pointer

        rjmp    WriteReceivedData

NoWritePageSizeExceeded:

        inc             CounterInWrite
        inc             I2c_start_addr
        dec             I2c_wr_pointer

        rjmp    PreparePageLoop

NormalPageWrite:
        rcall   Erase_page_by_SPM
        rcall   Write_Current_Page

        inc             I2c_start_addr
        dec             I2c_wr_pointer
        ret
//--------- Erase page
Erase_page_by_SPM:
        ldi             ZH,high(MemoryBlockFLASH << 1)
        ldi             ZL,low(MemoryBlockFLASH << 1)

        mov             TMPnoINT,I2c_start_addr
        andi    TMPnoINT, ((I2C_EEP_SIZE - 1) & ~(PAGESIZEB - 1)) /* Mask Maximum Data Size and Mask Out Real Flash Page Size */
        adc             ZL,TMPnoINT
        brcc    NoIncErasePage
        inc             ZH
NoIncErasePage:
        /* Load Erase Instruction */
        ldi             TMPnoINT, (1<<PGERS) | (1<<SPMEN)
        sts             _valSPMCSR, TMPnoINT
        rcall   Wait_spm        ;CPU Halted while erase
        ret

//Wite page
Write_Current_Page:
        ldi             XH,high(_I2c_FLASH_buffer) ; Load SRAM Backup Buffer Address
        ldi             XL,low(_I2c_FLASH_buffer)

        ldi             ZH,high(MemoryBlockFLASH << 1)
        ldi             ZL,low(MemoryBlockFLASH << 1)

        mov             TMPnoINT,I2c_start_addr
        andi    TMPnoINT, ((I2C_EEP_SIZE - 1) & ~(PAGESIZEB - 1)) /* Mask Maximum Data Size and Mask Out Real Flash Page Size */
        adc             ZL,TMPnoINT
        brcc    NoIncWritePage
        inc             ZH
NoIncWritePage:

        ldi             TMPnoINT, (PAGESIZE)
        mov             Counter, TMPnoINT

        movw    Y,Z                     ; Store Z in Y reg
        clr             ZL                      ;Clear Z
        clr             ZH
Fill_Page_Buffer_loop:
        ld              R0, X+
        ld              R1, X+

        ldi             TMPnoINT, (1<<SPMEN)      ;Write word into page buffer
        sts             _valSPMCSR, TMPnoINT
        rcall   Wait_spm        ;CPU Halted while erase

        adiw    Z, 2
        dec             Counter
        brne    Fill_Page_Buffer_loop   

        movw    Z,Y                     ; Restore Z from Y reg

        ldi     TMPnoINT, (1<<PGWRT) | (1<<SPMEN)
        sts             _valSPMCSR, TMPnoINT
        rcall   Wait_spm        ;CPU Halted while erase
        ret

Do_spm:
; check for previous SPM complete
Wait_spm:
        in      TMPnoINT, SPMCSR
        sbrc TMPnoINT, SPMEN
        rjmp Wait_spm
Wait_ee:
        sbic EECR, EEWE
        rjmp Wait_ee
; SPM timed sequence
        lds     TMPnoINT, _valSPMCSR
        out SPMCSR, TMPnoINT
        spm
        ret

.cseg
.org    (0x200 - (I2C_EEP_SIZE/2))
MemoryBlockFLASH:          ; Bytes in Flash
   .DB 0x55, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
   .DB 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
   .DB 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F
   .DB 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
   .DB 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F
   .DB 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F
   .DB 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F
   .DB 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D
   ; Mark End of EEP DATA
.org    (0x1FF)
        .db 0x7E,0x7F

.MESSAGE "I2C For Read/Write Processing SELFPRGEN FUSE Must Be Enabled!!!"

.eseg
MemoryBlockEEP:          ; Bytes in EEP
   .DB 0x55, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
   .DB 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
   .DB 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F
   .DB 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F

Let's block ads! (Why?)