Ghostwire Tokyo: DLSS vs. TSR vs. FSR
Сравнение разных техник сглаживания и апскейлинга.
На скриншотах много тонких линий, что создает проблемы для реконструкции.
Но нехватает тестов в динамике на мелкие движущиеся объекты, DLSS с этим плохо справляется и оставляет шлейфы.
#AA
Сравнение разных техник сглаживания и апскейлинга.
На скриншотах много тонких линий, что создает проблемы для реконструкции.
Но нехватает тестов в динамике на мелкие движущиеся объекты, DLSS с этим плохо справляется и оставляет шлейфы.
#AA
Как у меня сделана проверка на корректность синхронизаций.
Для этого я написал логгер команд, который выдает читаемый лог вызовов Vulkan комманд и результат не меняется в зависимости от запусков, что позволяет следить за изменениями. Но все синхронизации придется один раз вручную проверить на корректность.
Пример лога
Исходники тут
Другой вариант - запустить vkconfig и включить полную валидацию синхронизаций - Synchronization preset.
Guide to Vulkan Synchronization Validation
Либо из кода через расширение VK_EXT_validation_features включить VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT.
Так как VK_EXT_validation_features - расширение слоя валидации, то оно не указано в vkEnumerateInstanceExtensionProperties.
Пару лет назад я тестировал открытые движки и фреймворки на правильность синхронизаций и слой валидации легко находил ошибки. Из чего я сделал вывод, что никто не трогал настройки слоев валидации. Только мой FG автоматически расставлял синхронизации без ошибок.
#blog #vk
Для этого я написал логгер команд, который выдает читаемый лог вызовов Vulkan комманд и результат не меняется в зависимости от запусков, что позволяет следить за изменениями. Но все синхронизации придется один раз вручную проверить на корректность.
Пример лога
Исходники тут
Другой вариант - запустить vkconfig и включить полную валидацию синхронизаций - Synchronization preset.
Guide to Vulkan Synchronization Validation
Либо из кода через расширение VK_EXT_validation_features включить VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT.
Так как VK_EXT_validation_features - расширение слоя валидации, то оно не указано в vkEnumerateInstanceExtensionProperties.
Пару лет назад я тестировал открытые движки и фреймворки на правильность синхронизаций и слой валидации легко находил ошибки. Из чего я сделал вывод, что никто не трогал настройки слоев валидации. Только мой FG автоматически расставлял синхронизации без ошибок.
#blog #vk
Еще в прошлом году планировал написать большую статью про архитектуру асинхронного движка и игры на нем, но сама архитектура часто корректировалась, так как у меня просто не было опыта в подобном.
Сейчас в планах выкладывать части статьи в виде коротких заметок, из чего потом соберу большую статью.
#blog
Сейчас в планах выкладывать части статьи в виде коротких заметок, из чего потом соберу большую статью.
#blog
Профилирование.
Тут все сильно отличается, с одной стороны разбивая код на таски я получил возможность легко написать свой профайлер, достаточно вставить замеры до и после вызова таска. Также каждый таск подписан, что упрощает привязку данных профайлера к коду, не нужно вставлять макросы в функции или использовать VTune API.
С другой стороны без визуализации сложно понять что происходит внутри кадра и в каком порядке выполняются таски. При этом обычный профайлер начинает показывать время работы планировщика тасков, что замусоривает результаты, особенно на демках, где нет большой нагрузки на потоки.
#blog #engine
Тут все сильно отличается, с одной стороны разбивая код на таски я получил возможность легко написать свой профайлер, достаточно вставить замеры до и после вызова таска. Также каждый таск подписан, что упрощает привязку данных профайлера к коду, не нужно вставлять макросы в функции или использовать VTune API.
С другой стороны без визуализации сложно понять что происходит внутри кадра и в каком порядке выполняются таски. При этом обычный профайлер начинает показывать время работы планировщика тасков, что замусоривает результаты, особенно на демках, где нет большой нагрузки на потоки.
#blog #engine
Запись команд на Vulkan и Metal.
В Vulkan все просто - есть буфер команд, он заполняется и отправляется на ГП.
В Metal немного сложнее - запись команд идет через энкодеры, переключение между которыми не бесплатное.
Каждый энкодер записывает команды, которые могут выполняться параллельно с другими энкодерами, это решает внутренний фреймграф.
У меня в движке за основу взяты энкодеры из Metal, получились контексты: DrawCtx, GraphicsCtx, ComputeCtx, TransferCtx, ASBuildCtx, RayTracingCtx.
Для Vulkan это также полезно, так как на мобильных архитектурах переключения `graphics -> compute -> graphic`s работают неоптимально и их надо избегать, также разные контексты содержат независимые друг от друга этапы, а значит они могут выполняться параллельно.
Контексты можно использовать только внутри таска
1. Создается массив командных буферов -
2. Создается
3. Внутри таска создается контекст, он принимает ссылку на таск.
4. Записываются команды.
5. В конце, командный буфер передается в
6. Когда все командные буферы записаны и переданны в
Аналогично работает параллельное рисование через
Софтварный командный буфер.
В Vulkan и Metal командный буфер может использоваться только в одном потоке. Если не ограничивать количество потоков рендера, то может создаться много командных буферов, и это особенно накладно, если записывается всего несколько команд. Намного удобнее записать команды в память и в одном потоке записать их в нативный командный буфер.
Интерфейсы контекстов тут
#blog #engine
В Vulkan все просто - есть буфер команд, он заполняется и отправляется на ГП.
В Metal немного сложнее - запись команд идет через энкодеры, переключение между которыми не бесплатное.
Каждый энкодер записывает команды, которые могут выполняться параллельно с другими энкодерами, это решает внутренний фреймграф.
У меня в движке за основу взяты энкодеры из Metal, получились контексты: DrawCtx, GraphicsCtx, ComputeCtx, TransferCtx, ASBuildCtx, RayTracingCtx.
Для Vulkan это также полезно, так как на мобильных архитектурах переключения `graphics -> compute -> graphic`s работают неоптимально и их надо избегать, также разные контексты содержат независимые друг от друга этапы, а значит они могут выполняться параллельно.
DrawCtx
- только команды рисования, работает для одного сабпасса, соответствует MTLRenderCommandEncoder.GraphicsCtx
- только для манипуляций с рендер пассами, для Metal это вызовы MTLCommandBuffer::renderCommandEncoder и MTLCommandBuffer::parallelRenderCommandEncoder.TransferCtx
- команды копирования, соответствует MTLBlitCommandEncoder и MTLResourceStateCommandEncoder.ASBuildCtx
- построение и копирование ускоряющих структур, сответствует MTLAccelerationStructureCommandEncoder, для Vulkan все выполняется на этапе VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR.RayTracingCtx
- только команды трассировки, нет аналогов на Metal. Для Vulkan это этап VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_KHR и VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR для пайплайнов и дескрипторов.Контексты можно использовать только внутри таска
RenderTask
, последовательность выглядит так:1. Создается массив командных буферов -
CommandBatch
.2. Создается
RenderTask
, для него выделяется уникальный индекс в массиве команд.3. Внутри таска создается контекст, он принимает ссылку на таск.
4. Записываются команды.
5. В конце, командный буфер передается в
CommandBatch
.6. Когда все командные буферы записаны и переданны в
CommandBatch
, они отправляются на ГП (queue submit).Аналогично работает параллельное рисование через
DrawTask
и DrawCommandBatch
.Софтварный командный буфер.
В Vulkan и Metal командный буфер может использоваться только в одном потоке. Если не ограничивать количество потоков рендера, то может создаться много командных буферов, и это особенно накладно, если записывается всего несколько команд. Намного удобнее записать команды в память и в одном потоке записать их в нативный командный буфер.
Интерфейсы контекстов тут
#blog #engine
Низкоуровневый рендер в движке.
При планировании архитектуры был выбор из двух вариантов:
1. Привязка к кадрам. Все команды отправляются для конкретного кадра, даже async compute не может выполняться несколько кадров - при двойной буферизации 2й кадр будет ждать завершения всех команд. Но это дает и преимущества - кадр более предсказуемый, что дает и более стабильное время кадра.
2. Без привязки к кадрам. Это позволяет делать долгие асинхронные вычисления или копирования, но делает поведение менее предсказуемым.
Я выбрал вариант с привязкой к кадрам, таким образом:
* Ресурсы не удаляются сразу, а с задержкой в 2 кадра.
* Используется общий staging buffer, выделенная память гарантированно валидна в пределах кадра, это упростило работу с память. Например в при чтении из видеопамяти в
* Ограничен максимальный размер staging buffer на кадр, так чтобы все данные успели передаться по шине PCI-E за время кадра. Таким образом ГП не простаивает в ожидании данных с ЦП и время кадра более стабильное.
#blog #engine
При планировании архитектуры был выбор из двух вариантов:
1. Привязка к кадрам. Все команды отправляются для конкретного кадра, даже async compute не может выполняться несколько кадров - при двойной буферизации 2й кадр будет ждать завершения всех команд. Но это дает и преимущества - кадр более предсказуемый, что дает и более стабильное время кадра.
2. Без привязки к кадрам. Это позволяет делать долгие асинхронные вычисления или копирования, но делает поведение менее предсказуемым.
Я выбрал вариант с привязкой к кадрам, таким образом:
* Ресурсы не удаляются сразу, а с задержкой в 2 кадра.
* Используется общий staging buffer, выделенная память гарантированно валидна в пределах кадра, это упростило работу с память. Например в при чтении из видеопамяти в
ITransferContext::ReadbackImage ()
.* Ограничен максимальный размер staging buffer на кадр, так чтобы все данные успели передаться по шине PCI-E за время кадра. Таким образом ГП не простаивает в ожидании данных с ЦП и время кадра более стабильное.
RenderTask
- используется для ассинхронной записи командного буфера.DrawTask
- используется для ассинхронной записи вторичного командного буфера.CommmandBatch
- хранит масив командных буферов и семафоров для синхронизации с другими батчами и с ЦП. Аналогичен одному вызову vkQueueSubmit.DrawCommandBatch
- хранит массив вторичных командных буферов, которые затем выполняются в IGraphicsContext::ExecuteSecondary()
.RenderTaskScheduler
- переключает кадры и управляет батчами команд: создает их и отправляет на ГП, когда они заполнятся.RenderGraph
- сделан поверх предыдущих классов, добавляет отслеживание состояния ресурсов и синхронизациями между батчами.#blog #engine
Синхронизации без рендерграфа.
Когда рендер кадра состоит из заранее известных проходов, то управлять состояниями ресурсов можно и вручную.
Первым проходом должно быть обновление данных на ГП.
Синхронизация
Все копирования и обновление юниформ должно быть в этом проходе, чтоб избавиться от лишних синхронизаций в других проходах, зачем это нужно я объяснял тут (п.3).
Далее идут проходы рисования и вычислений.
Предполагается, что все неизменяемые ресурсы уже находятся в том состоянии, в котором они используются в дескрипторах.
Остаются синхронизации для изменяемых ресурсов (Attachment, StorageImage, StorageBuffer), их легко отслеживать и синхронизировать вручную, для проверки корректности есть способы.
Синхронизации между очередями.
Есть 2 подхода:
1. Сделать ресурсы общими для всех очередей (VK_SHARING_MODE_CONCURRENT), тогда достаточно сделать синхронизации семафорами, чтобы избежать одновременной записи или чтения и записи (data race), в движке это делается через
пример: Async compute + shared resources
2. Явно передавать ресурсы между очередями (queue ownership transfer). Внутри рендер таска это сложнее отслеживать, поэтому такие барьеры удобнее вынести в интерфейс
пример: Async compute + queue ownership transfer
#blog #engine
Когда рендер кадра состоит из заранее известных проходов, то управлять состояниями ресурсов можно и вручную.
Первым проходом должно быть обновление данных на ГП.
Синхронизация
Host_Write -> CopySrc|IndexBuffer|VertexBuffer|UniformBuffer
нужна при записи данных на стороне ЦП.Все копирования и обновление юниформ должно быть в этом проходе, чтоб избавиться от лишних синхронизаций в других проходах, зачем это нужно я объяснял тут (п.3).
Далее идут проходы рисования и вычислений.
Предполагается, что все неизменяемые ресурсы уже находятся в том состоянии, в котором они используются в дескрипторах.
Остаются синхронизации для изменяемых ресурсов (Attachment, StorageImage, StorageBuffer), их легко отслеживать и синхронизировать вручную, для проверки корректности есть способы.
Синхронизации между очередями.
Есть 2 подхода:
1. Сделать ресурсы общими для всех очередей (VK_SHARING_MODE_CONCURRENT), тогда достаточно сделать синхронизации семафорами, чтобы избежать одновременной записи или чтения и записи (data race), в движке это делается через
CommandBatch::AddInputDependency (CommandBatch &)
. Минус этого подхода - на AMD на общих ресурсах не включается компресия рендер таргетов, что снижает производительность.пример: Async compute + shared resources
2. Явно передавать ресурсы между очередями (queue ownership transfer). Внутри рендер таска это сложнее отслеживать, поэтому такие барьеры удобнее вынести в интерфейс
CommandBatch
, так появился метод CommandBatch::DeferredBarriers()
и initial, final
параметры при создании рендер таска. Теперь управление перемещением ресурсов происходит на этапе планирования батчей команд.пример: Async compute + queue ownership transfer
#blog #engine
(video) Optimizing Binary Search - Sergey Slotin - CppCon 2022
3:50 - вариант с ветвлением, как в stl. Так было сделано для совместимости со всеми итераторами.
8:30 - детально разбирается почему ветвления это плохо.
12:29 - вариант без ветвлений, работает в 2 раза быстрее на небольших массивах.
Для большинства этого должно хватить, а дальше начинается магия оптимизаций.
#cpp #cpu_opt
3:50 - вариант с ветвлением, как в stl. Так было сделано для совместимости со всеми итераторами.
8:30 - детально разбирается почему ветвления это плохо.
12:29 - вариант без ветвлений, работает в 2 раза быстрее на небольших массивах.
Для большинства этого должно хватить, а дальше начинается магия оптимизаций.
#cpp #cpu_opt
RDNA3 Instruction Set Architecture
* (1.2.2.1) Локальный кэш (LDS) содержит 64 атомика для быстрого доступа в преелах work-group.
* (1.2.2.2) Глобальный кэш (GDS) содержит 2 атомика для быстрого доступа.
* (2.1) wave64 (subgroupSize=64) вызывает все инструкции дважды.
* (2.3) CU и WGP режимы работы для work-group. Один WGP может содержать waves с обоими режимами работы, видимо это нужно для лучшей производительности графики при комбинации вершинного шейдера и меш шейдеров.
* (6.2) Некоторые константы заданы специальными константами, например: 0.5, 1.0, 2.0, 4.0, 1.0/(2*Pi).
* (7.9) WMMA инструкция для умножения матриц fp16 16х16.
* (10.4) Есть поддержка 16 битных деривативов. Есть 64 битные атомарные операции над текстурами.
#amd_gpu
* (1.2.2.1) Локальный кэш (LDS) содержит 64 атомика для быстрого доступа в преелах work-group.
* (1.2.2.2) Глобальный кэш (GDS) содержит 2 атомика для быстрого доступа.
* (2.1) wave64 (subgroupSize=64) вызывает все инструкции дважды.
* (2.3) CU и WGP режимы работы для work-group. Один WGP может содержать waves с обоими режимами работы, видимо это нужно для лучшей производительности графики при комбинации вершинного шейдера и меш шейдеров.
* (6.2) Некоторые константы заданы специальными константами, например: 0.5, 1.0, 2.0, 4.0, 1.0/(2*Pi).
* (7.9) WMMA инструкция для умножения матриц fp16 16х16.
* (10.4) Есть поддержка 16 битных деривативов. Есть 64 битные атомарные операции над текстурами.
#amd_gpu
Синхронизации с помощью рендерграфа.
Реализация более простая чем в FG, тут не поддерживается перестановка команд, но и лучше чем в DE, так как совместим с многопоточностью.
Контекст для записи команд.
Работает поверх существующих контекстов, о которых уже рассказывал. Добавленно только отслеживание состояний ресурсов в пределах
Этап планирования.
Точно также создаются батчи, но теперь через builder паттерн, где можно указать какие ресурсы будут использоваться в батче и их начальное/конечное состояние. Если ресурс используется только в одной ГП-очереди (VkQueue), то указывать его не обязательно. Но если ресурс используется в нескольких очередях, то требуется явно добавить его в батч, тогда внутри вставятся все необхрдимые синхронизации.
Для каждого рендер таска также можно указать начальное и конечное состояние ресурса, это позволит оптимизировать синхронизации между тасками.
Данный рендерграф нужен для прототипирования и в случаях, когда проходы рендера задаются в более высокоуровневом коде, например в скриптах. В таких случаях потери от использования рендерграфа не так важны как скорость разработки.
пример: async compute + RG
#blog #engine
Реализация более простая чем в FG, тут не поддерживается перестановка команд, но и лучше чем в DE, так как совместим с многопоточностью.
Контекст для записи команд.
Работает поверх существующих контекстов, о которых уже рассказывал. Добавленно только отслеживание состояний ресурсов в пределах
RenderTask
и автоматическое перемещение их в нужное состояние. Начальное и конечное состояние ресурса это либо дефолтное как в FG, либо задается вручную на этапе планирования рендер графа, иногда это добавляет ненужные синхронизации, но потери на них минимальные, если не приводят к декомпресии рендер таргетов.Этап планирования.
Точно также создаются батчи, но теперь через builder паттерн, где можно указать какие ресурсы будут использоваться в батче и их начальное/конечное состояние. Если ресурс используется только в одной ГП-очереди (VkQueue), то указывать его не обязательно. Но если ресурс используется в нескольких очередях, то требуется явно добавить его в батч, тогда внутри вставятся все необхрдимые синхронизации.
Для каждого рендер таска также можно указать начальное и конечное состояние ресурса, это позволит оптимизировать синхронизации между тасками.
Данный рендерграф нужен для прототипирования и в случаях, когда проходы рендера задаются в более высокоуровневом коде, например в скриптах. В таких случаях потери от использования рендерграфа не так важны как скорость разработки.
пример: async compute + RG
#blog #engine
Презентация с кратким обзором большинства алгоритмов компьютерной графики:
https://www.cl.cam.ac.uk/teaching/1718/AdvGraph/Printable%20(6-up).pdf
Есть все: трассировка, маршинг, SDF, матрицы, АА, постобработка, VR.
https://www.cl.cam.ac.uk/teaching/1718/AdvGraph/Printable%20(6-up).pdf
Есть все: трассировка, маршинг, SDF, матрицы, АА, постобработка, VR.
Выравнивание данных в Vulkan/Metal.
В Vulkan все выравнивания надо получать в рантайме и под каждый девайс.
В Metal выравнивание указано в документации и привязано к GPUFamily, которое дополнительно разделяется на Common/Apple/Mac со своими требованиями к выравниванию.
В движке выравнивания вынесены в DeviceProperties для использования в рантайме и
Это позволяет на этапе компиляции ресурсов и кода рассчитать размер буфера или задать все смещения константами, что упрощает проверки и дает гарантии, что код будет одинаково работать на разных устройствах.
А еще это дает микрооптимизации на вычисление и доступ к памяти, для тех, кто считает такты, но в некоторых случаях расплачиваться приходится большим расходом памяти на ГП.
#blog #engine
В Vulkan все выравнивания надо получать в рантайме и под каждый девайс.
В Metal выравнивание указано в документации и привязано к GPUFamily, которое дополнительно разделяется на Common/Apple/Mac со своими требованиями к выравниванию.
В движке выравнивания вынесены в DeviceProperties для использования в рантайме и
DeviceLimits
для использования в компайлтайме. DeviceLimits
содержит максимальное выравнивание для большинства ГП. В Metal все выравнивания - константы, поэтому тут просто, а для Vulkan эти значения проверяются через vulkan.gpuinfo, а также при старте движка сравниваются с текущими выравниваниями.Это позволяет на этапе компиляции ресурсов и кода рассчитать размер буфера или задать все смещения константами, что упрощает проверки и дает гарантии, что код будет одинаково работать на разных устройствах.
А еще это дает микрооптимизации на вычисление и доступ к памяти, для тех, кто считает такты, но в некоторых случаях расплачиваться приходится большим расходом памяти на ГП.
#blog #engine
Часто вижу в примерах по Vulkan код вида:
Но с тех времен кое-что изменилось - добавили
Правильнее передавать больше флагов:
1. HOST_VISIBLE | HOST_COHERENT - с optFlags.
2. HOST_VISIBLE | HOST_CACHED - без optFlags.
#blog #vk
Это пошло с первых примеров, тогда на дискретных ГП были такие типы памяти:
GetMemoryTypeIndex( mem_req.memoryTypeBits, DEVICE_LOCAL );
DEVICE_LOCAL, HOST_COHERENT, HOST_CACHED
.Но с тех времен кое-что изменилось - добавили
DEVICE_LOCAL | HOST_COHERENT
память для прямого доступа со стороны ЦП, обычно эта память ограничена в 256 Мб, что достаточно для юниформ буферов. Но эта же память может использоваться для staging буферов и всех DEVICE_LOCAL буферов, если этот тип памяти окажется в списке раньше других. То есть сейчас все работает как раньше только потому, что новый тип памяти добавили в конец списка и перебор до него не доходит.Правильнее передавать больше флагов:
Пример использования:
uint GetMemoryTypeIndex (uint memoryTypeBits, VkMemoryPropertyFlags includeFlags, VkMemoryPropertyFlags optFlags, VkMemoryPropertyFlags excludeFlags)
Возвращает:
GetMemoryTypeIndex( mem_req.memoryTypeBits, include: HOST_VISIBLE, opt: HOST_COHERENT, exclude: DEVICE_LOCAL );
1. HOST_VISIBLE | HOST_COHERENT - с optFlags.
2. HOST_VISIBLE | HOST_CACHED - без optFlags.
#blog #vk
Проверка ошибок.
Метка
Все проверки можно было заменить одной
В итоге, даже в сложных случаях, таких как промисы и корутины, удалось сделать проверку ошибок и корректный выход из функции без использования исключений.
#blog #engine
CHECK_ERR( expr, [opt] result )
- разворачивается в if ( not (expr) ) return result
.CHECK_TE( expr )
- работает только внутри AsyncTask::Run()
, помечает таск как отмененный и выходит из функции. Такой же результат даст и исключение внутри таска.CHECK_PE( expr )
- используется внутри Promise<>
и возвращает специальное значение CancelPromise
, которое помечает промис/таск как отмененный и у зависящих от него промисов выполнится функция, переданная в Promise::Except( fn )
.CHECK_CE( expr )
- используется внутри корутин (coroutine) и также помечает корутину/таск как отмененный.CHECK_THROW( expr, [opt] exception )
- разворачивается в if ( not (expr) ) throw exception
. Чаще всего используется чтобы прокинуть исключение в скрипты.CATCH_ERR( code, [opt] result )
- возвращает 'result', если 'code' бросает исключение. Используется для оборачивания stl вызовов, которые могут бросить исключение.Метка
[opt]
означает опциональный параметр, по-умолчанию возвращается false
или пустой объект {}
.Все проверки можно было заменить одной
CHECK_THROW
, но исключения дают меньшую производительность, что не так важно, так как случается редко. Также хочется оставить возможность отказаться от исключений, поэтому используются специализированные макросы. Например wasm
по-умолчанию выключает исключения при -O1
и выше для наилучшей производительности.В итоге, даже в сложных случаях, таких как промисы и корутины, удалось сделать проверку ошибок и корректный выход из функции без использования исключений.
#blog #engine
HDR режим монитора.
В Vulkan один из распространенных форматов на мониторе - RGBA16F + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR.
В документации про него нет информации, но удалось найти аналогичный формат в EGL, для которого есть документация: EGL_EXT_gl_colorspace_scrgb_linear.
В нем сказано, что этот формат обратно совместим с sRGB на диапазоне (0,1), но также позволяет использовать и значения более 1.
Цвет (1.0, 1.0, 1.0) соответствует яркости в 80 нит, что очень мало, зато доступен диапазаон до 125.0, что будет соответствовать 10 000 нит.
У меня монитор на 1000 нит и экспериментально выянилось, что цвета сохраняются до 12.0-13.0, после чего плавно уходят в белый цвет. Чисто белый появляется после 500.0.
Какие преимущества это дает:
* намного больший диапазон цветов.
* точное управление яркостью, то есть 1000 нит в игре будет в реальности светить в игрока на 1000 нит.
* можно отказаться от тонемапинга, если не использовать значения за пределами 500.0 или если нужно выводить физически корректную картинку.
Есть и минусы:
* нужно знать яркость монитора для правильного тонемапинга или экспозиции.
* много разных HDR форматов и под каждый нужно настраивать рендер.
* скриншоты от сторонних программ чиают только диапазон (0,1).
Поддержка в игрых и программах.
За 2 года пользования монитором я ни разу не смог воспользоваться HDR режимом. В играх этот режим помечался как неподдерживаемый, возможно больше рассчитывали на HDR TV под консоли, где другой режим.
Только в Doom Enternal есть возможность включить HDR, но картинка была в диапазоне (0,1), а это очень тусклые цвета, то есть они это не тестировали.
Без HDR режима монитор сам подстраивал яркость, что давало хорошую имитацию HDR.
#HDR
В Vulkan один из распространенных форматов на мониторе - RGBA16F + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR.
В документации про него нет информации, но удалось найти аналогичный формат в EGL, для которого есть документация: EGL_EXT_gl_colorspace_scrgb_linear.
В нем сказано, что этот формат обратно совместим с sRGB на диапазоне (0,1), но также позволяет использовать и значения более 1.
Цвет (1.0, 1.0, 1.0) соответствует яркости в 80 нит, что очень мало, зато доступен диапазаон до 125.0, что будет соответствовать 10 000 нит.
У меня монитор на 1000 нит и экспериментально выянилось, что цвета сохраняются до 12.0-13.0, после чего плавно уходят в белый цвет. Чисто белый появляется после 500.0.
Какие преимущества это дает:
* намного больший диапазон цветов.
* точное управление яркостью, то есть 1000 нит в игре будет в реальности светить в игрока на 1000 нит.
* можно отказаться от тонемапинга, если не использовать значения за пределами 500.0 или если нужно выводить физически корректную картинку.
Есть и минусы:
* нужно знать яркость монитора для правильного тонемапинга или экспозиции.
* много разных HDR форматов и под каждый нужно настраивать рендер.
* скриншоты от сторонних программ чиают только диапазон (0,1).
Поддержка в игрых и программах.
За 2 года пользования монитором я ни разу не смог воспользоваться HDR режимом. В играх этот режим помечался как неподдерживаемый, возможно больше рассчитывали на HDR TV под консоли, где другой режим.
Только в Doom Enternal есть возможность включить HDR, но картинка была в диапазоне (0,1), а это очень тусклые цвета, то есть они это не тестировали.
Без HDR режима монитор сам подстраивал яркость, что давало хорошую имитацию HDR.
#HDR
Фото монитора.
Выводится чистый красный цвет от 0 до 1000.
UI выводится в диапазоне (0,1) и выглядит очень тускло.
Формат: RGBA16F + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR.
#HDR
Выводится чистый красный цвет от 0 до 1000.
UI выводится в диапазоне (0,1) и выглядит очень тускло.
Формат: RGBA16F + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR.
#HDR
HDR color grading and display in Frostbite
Как переделали Frostbite для поддержки множества HDR дисплеев.
#HDR
Как переделали Frostbite для поддержки множества HDR дисплеев.
#HDR
Vulkanised 2023
Выложили видео с конференции.
Доклады выглядят узкоспециализированно, поэтому пользы не много.
#news
Выложили видео с конференции.
Доклады выглядят узкоспециализированно, поэтому пользы не много.
#news
Vulkanised 2023: Diligent Engine
Большую часть времени рассказывается про биндинги ресурсов, а это сильно специфичная вещь и имеет смысл только для пользователей движка.
Про async compute или ray tracing можно было поинтереснее рассказать.
Что касается системы биндингов, то интересное решение было для DX11 байткода, нужно было его обработать, пропатчить и заново подписать.
Для DXIL было чуть проще - использовался встроенные дизассемблер, потом патчился асм-код и заново компилировался.
Патчинг нужен был чтобы сделать рут сигнатуры/пайплайн лейауты совместимыми между разными шейдерами и пайплайнами, таким образом уменьшается количество дескриптор сетов, биндингов и тд.
Большую часть времени рассказывается про биндинги ресурсов, а это сильно специфичная вещь и имеет смысл только для пользователей движка.
Про async compute или ray tracing можно было поинтереснее рассказать.
Что касается системы биндингов, то интересное решение было для DX11 байткода, нужно было его обработать, пропатчить и заново подписать.
Для DXIL было чуть проще - использовался встроенные дизассемблер, потом патчился асм-код и заново компилировался.
Патчинг нужен был чтобы сделать рут сигнатуры/пайплайн лейауты совместимыми между разными шейдерами и пайплайнами, таким образом уменьшается количество дескриптор сетов, биндингов и тд.