Что такое gpu рендеринг. Какой рендер лучше? biased и unbiased рендеры

Как создать рендерер, который бы работал даже на компьютере вашей бабушки? Изначально перед нами стояла немного другая задача - создать unbiased рендер для всех моделей GPU: NVidia, ATI, Intel.
Хотя идея такого рендера для всех видеокарт витала в воздухе давно, до качественной реализации, тем более на Direct3D, дело не доходило. В своей работе мы пришли к весьма дикой связке и дальше расскажем, что нас к ней привело и как она работает.

О том, что такое unbiased rendering, или рендеринг без допущений, очень хорошо изложил Marchevsky в серии статей
«Трассировка пути на GPU» часть 1 , часть 2 и "Unbiased rendering (рендеринг без допущений) ".
Вкратце: это рендеринг, который не вносит систематические ошибки в расчет и воспроизводит физически точные эффекты

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

Ввиду физической достоверности и качества картинки, такой подход заведомо является очень ресурсоёмким. Решить проблему можно путем переноса расчётов на GPU, так как такой подход даёт увеличение скорости расчета до 50‒и раз на каждое GPU устройство.


1200x600 (кликабельно), AMD Radeon HD 6870, render time: 9 min

Почему Direct3D

Существует множество GPGPU платформ (OpenCL, CUDA, FireStream, DirectCompute, C++ AMP, Shaders и др.), но споры об оптимальном выборе идут до сих пор, и однозначного ответа на то, что лучше использовать, нет. Рассмотрим основные аргументы в пользу Direct3D, которые склонили нас к выбору именного этого API:
  • Работает на всем спектре видеокарт, эмулируется на всех моделях процессоров: один и тот же шейдер работает везде
  • Именно спецификации Direct3D задают направление развития потребительского железа
  • Всегда первым получает самые свежие и стабльные драйверы
  • Остальные кросс‒вендорные технологии не стабильны, либо слабо поддерживаются
Из OpenCL и Direct3D мы выбрали то зло, которое, по крайней мере, имеет стабильные драйвера, отточенные десятилетиями игровой индустрии, и имеет лучшую производительность в ряде бенчмарков. Также, исходя из задачи, была отброшена CUDA, несмотря на все инструменты, обильное количество примеров и сильное сообщество разработчиков. C++ AMP в то время еще не был анонсирован, но т.к. его реализация построена поверх DirectX, перенести рендер на него не составит особых проблем.
Связка OpenGL/GLSL также рассматривалась, но быстро была отброшена ввиду ограничений, которые в DirectX решаются с помощью DirectCompute (задачи двунаправленной трассировки пути и пр.).

Так же, отметим ситуацию с GPGPU драйверами потребительского железа, которые выходят с запозданием и долго доводятся до стабильности. Так, при выходе линейки NVIDIA Kepler 600, геймеры сразу получили качественные Direct3D драйверы и более производительные машины для игр, но большая часть GPGPU приложений потеряла свою совместимость или стала менее производительной. Например, Octane Render и Arion Render, построенные на CUDA, начали поддерживать линейку Kepler буквально на днях. К тому же, профессиональное GPGPU железо не всегда оказывается намного лучше в ряде задач, о чем рассказано в статье “NVidia для профессиональных 3D приложений ”. Это дает повод собрать рабочую станцию именно на потребительском игровом железе.

Почему не Direct3D

Во всех анонсах DirectX 10‒11 пишут о том, что новые модели шейдеров идеально подходят для трассировки лучей и многих других GPGPU задач. Но на деле никто особо не использовал эту возможность. Почему?
  • Не было инструментов и поддержки
  • Исследования сдвинуты в сторону NVIDIA CUDA ввиду сильного маркетинга
  • Привязка к одной платформе
Вернемся на год назад. Последнее обновление DX SDK было в июле 2010. Интеграции с VisualStudio нет, сообщества разработчиков GPGPU и качественных примеров нормальных вычислительных задач практически нет. Да что уж там, подсветки синтаксиса шейдеров нет! Вменяемых инструментов дебага тоже нет. PIX не способен выдержать несколько вложенных циклов или шейдер в 400+ строк кода. D3DCompiler мог падать через раз и компилировать сложные шейдеры десятки минут. Ад.

С другой стороны - слабое внедрение технологии. Большинство научных статей и публикаций были написаны, используя CUDA, и заточены под железо NVIDIA. Команда NVIDIA OptiX тоже не особо заинтересована в исследованиях для других вендоров. Немецкая компания mentalimages, которая десятилетиями накапливала опыт и патенты в этой области, теперь так же принадлежит NVIDIA. Все это создает нездоровый перекос в сторону одного вендора, но рынок есть рынок. Для нас это все означало, что все новые GPGPU техники трассировки и рендеринга надо исследовать заново, но только на DirectX и на железе ATI и Intel, что часто приводило к совершенно другим результатам, например на архитектуре VLIW5.

Реализация

Устраняем проблемы
Перед описанием реализации приведу несколько полезных советов, которые помогли нам в разработке:
  • По возможности, переходите на VisualStudio2012. Долгожданная интеграция с DirectX, встроенный debug шейдеров, и, о чудо, подсветка синтаксиса HLSL сэкономят вам кучу времени.
  • Если VS2012 - не варинат, можно использовать инструменты типа NVidia Parallel Nsight, но опять возникает привязка к одному типу GPU.
  • Используйте Windows 8.0 SDK, это ваш бро. Даже если разрабатываете на Windows Vista / 7 и старых версиях VisualStudio, в вашем распоряжении будут последние D3D библиотеки, включая свежий D3DCompiler, который сокращает время компиляции шейдеров в 2‒4 раза и стабильно работает. Для настройки DirectX из Windows 8.0 SDK имеется подробный мануал .
  • Если вы все еще используете D3DX, задумайтесь о том, чтобы отказаться от него, это не бро. В Windows 8.0 SDK прекратили его поддержку по весьма понятным причинам.

Растеризация vs трассировка
Не смотря на то, что используется DirectX, о растеризации речи не идет. Стандартный Pipeline вершинных, Hull, Domain, геометрических и пиксельных шейдеров не используется. Речь идет о трассировке путей света средствами пиксельных и Compute шейдеров. Идея комбинирования растеризации + трассировки, конечно же, возникала, но оказалась очень сложной в реализации. Первое пересечение лучей можно заменить растеризацией, но после этого сгенерировать второстепенные лучи очень непросто. Зачастую оказывалось так, что лучи находились под поверхностью, и результат оказывался неверным. К такому же заключению пришли ребята из Sony Pictures Imageworks, разрабатывающие Arnold Renderer.

Rendering
Существуют два основных подхода организации рендеринга:

  1. Все вычисления происходят в мега-ядре программы GPU, которая ответственна как за трассировку, так и за шейдинг. Это самый быстрый способ рендеринга. Но если сцена не помещается в память GPU, то произойдет либо свопинг сцены, либо приложение сломается.
  2. Out Of Core Rendering: в GPU передается только геометрия сцены либо ее часть, вместе с буфером лучей для трассировки, и производится многопроходная трассировка лучей. Шейдинг производится либо на CPU, либо еще одним проходом на GPU. Такие подходы не славятся потрясающей производительностью, зато позволяют рендерить сцены продакшн‒размера.

Мы остановились на первом варианте с использованием шейдеров для GPGPU, но перед тем как что‒либо рендерить, нужно подготовить геометрию и данные о сцене, правильно разместив их в памяти GPU.
Данные о сцене включают в себя:

  • геометрию (вершины, треугольники, нормали, текстурные координаты)
  • ускоряющую структуру (узлы Kd‒дерева или BVH)
  • материалы поверхностей (тип, цвета, указатели на текстуры, отражательные экспоненты и многое другое)
  • текстуры материалов, карты нормалей и пр.
  • источники освещения (сингулярные и протяженные)
  • положение и параметры камеры, такие как DOF, FOV и пр.
Никаких вершинных и индексных буферов при рендере не используется. В Direct3D11 данные унифицированы, все хранится в одинаковом формате, но можно сказать устройству, как на них смотреть: как на Buffer, Texture1D/2D/3D/Array/CUBE, RenderTarget и т.д. Данные, к которым производится более‒менее линейный доступ, лучше хранить в виде Buffer"ов. Данные с хаотичным доступом, например, ускоряющие структуры сцены, лучше хранить в текстуре, т.к. при частых обращениях часть данных закешируется.
Быстроменяющиеся и небольшие куски данных разумно хранить в константных буферах, это параметры камеры, источники освещения и материалы, если их не много и они умещаются в буфер, размером 4096 x float4. При интерактивном рендеринге изменение положения камеры, настройка материалов и света - самая частая операция. Изменение геометрии происходит несколько реже, но константной памяти все равно не хватит, чтобы разместить ее.

Т.к. памяти в GPU относительно мало, нужно использовать умные подходы к ее организации и стараться запаковать все, что только можно запаковать и использовать сжатие данных. Текстуры материалов мы размещаем в многослойном текстурном атласе, т.к. количество текстурных слотов GPU ограничено. Также, в GPU имеются встроенные форматы сжатия текстур – DXT, которые используются для текстурных атласов и могут уменьшить размер текстур до 8‒и раз.

Упаковка текстур в атлас:

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

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

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


Далее, для каждого пиксела в шейдере производится рассчет алгоритма трассировки пути лучей. Этим способом производятся GPGPU вычисления на пиксельных шейдерах. Такой подход может показаться не самым оптимальным, и более разумно было бы использовать DirectCompute, для которого никаких вершинных шейдеров и скрин‒квада создавать не надо. Но многочисленные тесты показали, что DirectCompute оказывается на 10‒15% медленнее. В задаче трассировки пути все преимущества от использования SharedMemory или использоания пакетов лучей быстро сходят на нет из-за случайно природы алгоритма.

Для рендеринга используются две техники: интерактивный просмотр работает на модифицированной однонаправленной трассировке пути (Path Tracing), а для конечного рендеринга может быть использована двунаправленная трассировка пути (Bidirectional Path Tracing), т.к. ее фреймрейт весьма не интерактивен на сложных сценах. Семплирование методом Metropolis Light Transport пока не используется, т.к. его эффективность еще не оправдалась, о чем говорит один из разработчиков V-Ray на закрытом форуме ChaosGroup:

vlado posted:

“...I came to the conclusion that MLT is way overrated. It can be very useful in some special situations, but for most everyday scenarios, it performs (much) worse than a well-implemented path tracer. This is because MLT cannot take advantage of any sort of sample ordering (e.g. quasi-Monte Carlo sampling, or the Schlick sequence that we use, or N-rooks sampling etc). A MLT renderer must fall back to pure random numbers which greatly increases the noise for many simple scenes (like an open skylight scene).”

Multi‒Core. Multi‒Device. Cloud.

Отметим, чем очень хорош unbiased‒рендеринг - он основан на методе Монте‒Карло, а это значит, что в общем случае каждая итерация рендеринга не зависит от предыдущей. Именно это делает данный алгоритм привлекательным для вычислений на GPU, многоядерных системах и кластерах.

Чтобы поддержать железо классов DX10 и DX11 и не переписывать все заново под каждую версию, стоит использовать DirectX11, который с небольшими ограничениями работает на DX10 Железe . Имея поддержку широкого класса железа и предрасположенность алгоритма к распределению, мы сделали Multi‒Device рендеринг, принцип работы которого очень прост: в каждый GPU нужно поместить одинаковые данные, шейдеры и просто собирать результат с каждого GPU по мере его готовности, перезапуская рендеринг при изменениях в сцене. Алгоритм позволяет распределить рендеринг на очень большое количество устройств. Эта концепция замечательно подходит для вычислений в облаках. Но, облачных GPU‒провайдеров не так много, и компьют‒тайм стоит тоже не очень дешево.

С появление DirectX11 на помощь пришла замечательная технология - WARP (Windows Advanced Rasterization Platform). WARP Device транслирует Ваш GPU код в SSE‒оптимизированный многопоточный код, позволяя производить GPU вычисления на всех ядрах CPU. Причем абсолютно любых CPU: x86, x64 и даже ARM! С точки зрения программирования, такое устройство ничем не отличается от GPU устройства. Именно на основе WARP в C++ AMP реализованы гетерогенные вычисления. WARP Device - тоже ваш бро, используйте WARP Device.

Благодаря этой технологии мы смогли запустить GPU рендеринг в CPU облаке. Немного бесплатного доступа к Windows Azure мы получили через программу BizSpark. Для хранения данных был использован Azure Storage, данные с геометрией сцены и текстурами хранились в “блобах” (Blobs), данные о заданиях рендеринга, закачке и скачке сцен находятся в очередях (Queues). Для обеспечения стабильной работы использовалось три процесса: распределитель задач (Work Scheduler), наблюдатель за процессами (Process Monitor) и процесс, скачивающий отрендеренные изображения (Image Downloader). Work Scheduler ответственен за загрузку данных в блобы и постановку задач. Process Monitor отвечает за поддержание всех воркеров (Worker – вычислительный узел Azure) в рабочем состоянии. Если один из воркеров перестает отвечать, то происходит инициализация нового экземпляра, таким образом, обеспечивается максимальная работоспособность системы. Image Downloader собирает отрисованные куски картинки со всех воркеров и передает готовое или промежуточное изображение клиенту. Как только задача рендеринга выполнилась, Process Monitor ликвидирует образы воркеров, чтобы не было простаивающих ресурсов, за работу которых пришлось бы платить.

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

Результат

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

Сейчас он находится на стадии закрытого альфа тестирования. Если Вы GPU‒энтузист, 3D‒художник, задумали построить ATI/NVIDIA кластер, у Вас куча разных GPU и CPU или любая другая интересная конфигурация, дайте знать , будет интересно вместе поработать. Очень хотелось бы проверить рендер на чем-нибудь вроде этого:

Так же, впереди C++ AMP версия рендера, более серьезные облачные тесты и разработка плагинов для других редакторов. Присоединяйтесь!

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

Однако не все так просто! Давайте оставим за рамками вопросы подходит ли GPU рендер под требования производства отдельных видов графики, отсутствие полноценного продакшен GPU рендера и все такое. Представим, что у вас небольшая банда из 3 визуализаторов, вы делаете преимущественно рекламные ролики и вот вы перешли на GPU рендеринг. Давайте посмотрим на 7 проблем, которые вас подстерегают:

  1. Высокая стоимость видеокарт, дорогостоящее скалирование мощностей

Большинство специализированных GPU решений предполагают, что добавление видеокарт дешевле покупки полного компьютера, но только при покупке специальных серверных шкафов и систем для работы большого количества видеокарт. Сами такие решения недешевы и требуют определенных навыков. Но представим, что мы пошли по пути покупки стандартных компьютеров и установки в них 2-3-4 видеокарт. Сравните стоимость в дальнейшем апгрейда процессора и апгрейда этих 2-3-4 GPU.

2. Маленькое влияние крутых видеокарт на всю систему

В отличие от апгрейда CPU, который окажет влияние на работу всех приложений, видеокарты окажут влияние только на сокращение времени рендера. Их присутствие никак не повлияет на работу ОС и любых 3d приложений для работы с графикой. За исключением, разве что, игр. Но вы же не играете на своем рабочем компьютере, правда?)

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

3. Постоянный шум и тепловыделение

Охлаждение практически любой видеокарты гораздо шумнее работы охлаждения CPU. Еще важнее, что постоянная работа 2-3 видеокарт быстро создает в помещении невыносимую температуру.

4. Вопросы масштабирования

Если у вас несколько GPU машин, рано или поздно встает вопрос покупки дополнительных лицензий и возможны проблемы совместимости видеокарт различных производителей и разных моделей. По мере обновления парка столкновение поколений видеокарт неизбежно. А вы же помните что, если в одной системе стоит, например, видеокарта с 8гб памяти и 12гб, то память будет ограничена меньшим значением.

Обычное обновление драйвера может обрушить все. Видеокарты в любой системе – основной источник зависаний и сбоев. Любая проблема с обновлением драйверов, их совместимостью, багами и прочими прелестями однозначно ведет к тому, что и в рендере будут проблемы! Нужно ли говорить, что при рендере на CPU этих проблем нет.

6. Ограниченная память

В настоящее время в GTX 1080Ti всего 12 Гб памяти, которые не увеличить вставив планку памяти как в обычных компьютерах. И она не суммируется при установке нескольких карт. Если, например, установить таких 3 карты, то под сцены будет доступно так же 12 Гб. При превышении доступного объема памяти на рендере, все просто крашится. Вариантов как на CPU с использованием файла подкачки, который хоть и замедляет рендер, но все же делает его возможным – нет.

7. Ограниченный круг ПО

Рендер на GPU поддерживает ограниченное количество графических приложений и рендеров, что накладывает свои ограничения.

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

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

GPU быстрый, дешевый и масштабируемый

$400 долларовая игровая видеокарта, такая как GTX 1070 или Radeon RX580 быстрее, нежели 22-х ядерный Intel Xeon 2699v4 ($3500) в большинстве задач рендеринга. Если рассматривать лишь цену оборудования, то GPU является безоговорочным победителем. Ценность GPU возрастает еще больше, потому что мы запросто можем установить 4 видеокарты в один системник, тем самым получив 4-х кратное увеличение скорости рендеринга, в то время как многопроцессорные системы стоят ОЧЕНЬ дорого. Но у графических процессоров есть один недостаток …

Ограничения памяти GPU

Чтобы производит рендеринг на GPU Cycles должен поместить все данные сцены в память видеокарты. Если же сцена не помещается, то рендеринг на ней будет просто невозможен. Большинство потребительских графических процессоров сегодня имеют 8 Гбайт памяти. Это означает, что вы сможете разместить только 32 уникальных текстуры в разрешении 8K, прежде чем память будет полностью забита. Это не много текстур. Существуют графические процессоры с большим объемом памяти, но их цена часто бывает астрономической, что делает рендеринг на GPU столь же дорогостоящим, как и на CPU. С другой стороны, в то время как процессорный рендеринг не использует меньше памяти, оперативная память намного дешевле. 32 ГБ ОЗУ можно приобрести по очень разумной цене.

Потребление энергии

Для студий, производящих большое количество рендеров, энергопотребление является еще одним аспектом, который следует учитывать. Удивительно, но несмотря на большую разницу в цене между CPU и GPU, производительность на ватт поразительно схожа для устройств с большим количеством ядер. GTX 1070 и Xeon 2699v4 имеют пиковое энергопотребление около 150 Вт и работают, примерно, одинаково. Поэтому независимо от того, какое устройство вы используете, аппаратные средства одного поколения должны использовать, примерно, одинаковое количество энергии. Тем не менее, процессоры с низким количество ядер и большой частотой, такие как Intel 7700k, как правило, потребляют больше энергии, чем многоядерные процессоры с низкой тактовой частотой.

Набор функций

Хорошо, хватит об аппаратном обеспечении. Нам также нужно сравнить различия в возможностях между CPU и GPU рендерингом. Что касается Blender 2.78c, рендеринг на GPU и рендеринг на CPU находится, практически, на одном уровне. Есть лишь небольшой набор функций, которые не поддерживаются графическим процессором, а самой большой недостающей особенностью является Open Shading Language. Но, если вы не планируете писать собственные шейдеры, GPU так же хорош, как и процессор.

Операционные системы

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

Доброго времени суток, дорогие читатели.

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

biased и unbiased рендеры

По принципу действия все рендеры можно разделить на 2 большие группы.

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

Biased рендер сначала создаст для себя карту освещенности всего пространства, затем «наложит» ее на геометрию, что бы понять, какие участки просчитывать точно, а какие нет. Допустим он «видит», что область в углу комнаты практически не освещена. Он сверяется с выставленными настройками и уменьшает качество обработки этой области до указанного пользователем минимума — зачем проводить сложные рассчеты, если объекты находятся в сильном затенении и на них в любом случае не будут четко видны блики, отражения и т.д?

Unbiased — это так называемые «физически корректные движки». Они берут всю сцену целиком и начинают ее просчитывать с по сути «бесконечными настройками». На практике это происходит так: сначала вся картинка имеет ужасное качество и ужасный шум. По мере рендеринга шум начинает пропадать и качество всей картинки начинает улучшаться.

К какому виду относится популярный движок V-Ray?

V-Ray может работать в обоих режимах. Переключение осуществляется так: F10-Common-Assign Render (в самом низу)

  • VRay Adv — Biased
  • VRay RT — Unbiased

Как все это относится к видеокартам?

Рендеринг на видеокарте (GPU-рендеринг) возможен только в Unbiased «режиме». Отсюда мы имеем первый спорный момент.

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

Для примера 660 Ti, согласно спецификации имеет 1344 ядра. А процессор 4 физических и 8 виртуальных.

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

Какой рендер лучше?

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

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

Рендеринг на процессоре (CPU) позволяет получать качественную картинку значительно быстрее при вполне приемлемом уровне фотореалистичности. Однако стоит учесть, что качество картинки при просчете не процессоре сильно зависит от умения настроить рендер движок.

Что выбрать новичку?

Для новичка нет необходимости обращать внимание на GPU.

  • это самый длительный рендер. Он никак не подходит для процесса обучения, который подразумевает создание большого количества черновых картинок;
  • практическое применение GPU оправданно далеко не во всех задачах и подразумевает использование только дорогих видеокарт. Если вы новичек, то такие задачи появятся у вас не скоро.


Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: