− Скрыть
Первое, что бросается в глаза — отсутствие проверок возвращаемых значений. В DirectX 12 многие функции возвращают HRESULT, и их нужно проверять на успешность. Например, CreateDXGIFactory1, D3D12CreateDevice, CreateSwapChainForHwnd и другие. Если где-то произойдет ошибка, программа просто упадет без диагностики. Нужно добавить проверки с помощью макросов типа SUCCEEDED или FAILED и обработать ошибки.
Далее, при создании окна, класс окна регистрируется, но не проверяется, успешно ли это произошло. Функция RegisterClass возвращает ATOM, который может быть нулем в случае ошибки. Хорошо бы добавить проверку и обработку.
Создание устройства: используется EnumAdapters1(0, &adapter). А если на системе несколько адаптеров? Может быть, лучше найти адаптер с поддержкой D3D12, например. Прямо сейчас код берет первый попавшийся, который может не поддерживать нужный функционал. Нужно перебрать адаптеры и выбрать подходящий, возможно.
При создании цепочки обмена (swap chain) используется sd.BufferCount = 2. Обычно рекомендуется использовать тройную буферизацию, т.е. BufferCount = 3. Но тут зависит от желаемого. Однако, в DXGI_SWAP_CHAIN_DESC1 поле BufferCount может иметь ограничения, и лучше проверить, поддерживается ли выбранное количество буферов.
В цикле рендеринга после Present вызывается GetCurrentBackBufferIndex, но не синхронизируется с кадром через fence. Это может привести к тому, что кадры будут перезаписывать друг друга, так как нет ожидания завершения предыдущего кадра. Нужно добавить синхронизацию с использованием fence и ожидания события. Сейчас после Present сразу обновляется frameIndex, но без ожидания завершения рендеринга предыдущего кадра, что может привести к гонкам.
Есть структура D3D12_HEAP_PROPERTIES, которая инициализируется нулями, но не все поля заполнены. Например, явно не установлен Type как D3D12_HEAP_TYPE_UPLOAD. Хотя в коде heapProps.Type задается, но возможно, остальные поля (например, CPUPageProperty или MemoryPoolPreference) должны быть явно инициализированы? Хотя для UPLOAD-кучи обычно достаточно указать Type.
Вершинный буфер создается в состоянии D3D12_RESOURCE_STATE_GENERIC_READ, что правильно для UPLOAD-кучи. Но при использовании этого буфера в командах рендеринга, возможно, нужно убедиться, что он в правильном состоянии. В данном примере, так как буфер используется только для чтения в конвейере, возможно, все в порядке. Но в более сложных сценариях могут потребоваться барьеры.
В коде отсутствует шейдеры и корневая сигнатура. Без них вывод треугольника невозможен. Возможно, это упущение в примере, но в текущем виде код не будет отображать ничего, так как не настроен конвейер состояний (Pipeline State Object, PSO). Ошибка здесь в том, что не создан PSO, не загружены шейдеры, не установлена корневая сигнатура. Без этого команды рисования (DrawInstanced) не будут работать.
В вызове commandList->OMSetRenderTargets используется frameIndex для доступа к дескриптору. Но rtvHeap создан с двумя дескрипторами, и frameIndex может быть 0 или 1. Однако, после Present, GetCurrentBackBufferIndex может вернуть 0 или 1, что правильно. Но в OMSetRenderTargets передается 1 дескриптор, но массив renderTargets содержит два элемента. Возможно, здесь все правильно.
При вызове commandList->ClearRenderTargetView используется Colors::Black, но в коде не видно, откуда взят Colors. Возможно, это опечатка, и нужно использовать, например, цвет через массив float[4]. Colors::Black — это часть DirectXToolkit, которая не подключена здесь. Это ошибка компиляции.
В цикле рендеринга, после выполнения команд, вызывается Present(1, 0). Параметр SyncInterval 1 означает ожидание вертикальной синхронизации (VSync), что может быть нормально, но в некоторых случаях лучше 0 для максимального FPS. Однако это зависит от требований.
Кроме того, после выполнения командного списка и Present, нужно дождаться завершения выполнения команд в очереди перед тем, как начинать новый кадр. Иначе может произойти переполнение очереди команд. Для этого используется fence: увеличиваем значение fence, ставим сигнал в очередь, и ждем, когда предыдущий кадр завершится.
Также в коде отсутствует обработка изменения размера окна. При изменении размера окна нужно пересоздать цепочку обмена и ресурсы рендер-таргетов. В текущем коде при ресайзе окна swap chain не обновляется, что приведет к ошибкам или некорректному отображению.
Еще одна деталь: после создания командного списка (CreateCommandList), он находится в открытом состоянии. Однако перед первым использованием его нужно закрыть (Close), но в коде после создания сразу начинается запись команд в цикле. Но в цикле перед Reset вызывается commandAllocator->Reset(), что допустимо. Возможно, это не ошибка, так как командный список создается в закрытом состоянии? Нет, при создании командного списка через CreateCommandList он создается в открытом состоянии. Поэтому после создания нужно сразу закрыть, если не собираемся записывать команды сразу. В данном коде commandList создается, и затем в цикле вызывается Reset, что нормально, так как после создания он открыт, но перед первым использованием в цикле он сбрасывается. Хотя возможно, что при первом вызове Reset для commandList будет ошибка, так изначально он уже открыт. Возможно, нужно закрыть его сразу после создания.
Вероятно, в коде есть ошибка, связанная с тем, что после создания командного списка он находится в открытом состоянии, и при первом вызове Reset в цикле рендеринга будет ошибка. Потому что нельзя сбросить открытый командный список. Поэтому после создания commandList нужно его закрыть, вызвав Close().
Также в коде используется CD3DX12_VIEWPORT и CD3DX12_RECT, но не подключен заголовочный файл d3dx12.h, который содержит эти вспомогательные структуры. Это может привести к ошибкам компиляции, если этот заголовок не включен. Нужно убедиться, что он подключен, или использовать обычные структуры D3D12_VIEWPORT и D3D12_RECT с ручной инициализацией.
Теперь по оптимизации. В цикле рендеринга каждый раз создаются барьеры для перехода ресурсов. Возможно, их можно вынести за пределы цикла, но в данном случае, так как состояние ресурса меняется каждый кадр, это не применимо.
Кроме того, постоянный вызов CreateRenderTargetView в цикле инициализации — это нормально, но если размер окна меняется, нужно пересоздавать RTV. В текущем коде это не предусмотрено.
Использование магических чисел, например, 800 и 600 для размеров окна и свопчейна. Лучше получать актуальный размер окна каждый кадр, особенно если окно может менять размер. Иначе при изменении размера окна изображение будет растягиваться или обрезаться.
Также, возможно, стоит использовать двойную проверку на WM_QUIT, так как PeekMessage может обрабатывать сообщения не в порядке их поступления. Но в данном цикле обработки сообщений это, кажется, корректно.
Подведем итоги по ошибкам:
1. Отсутствие проверок возвращаемых значений HRESULT.
2. Не выбрасывается исключение или обработка ошибок при создании объектов DirectX.
3. Неправильное использование Colors::Black без подключения необходимых заголовков.
4. Командный список создается в открытом состоянии, и не закрывается после создания, что может вызвать ошибку при первом вызове Reset.
5. Отсутствует синхронизация с fence, что приводит к возможной гонке между кадрами.
6. Нет обработки изменения размера окна.
7. Отсутствует создание PSO, шейдеров, корневой сигнатуры, из-за чего треугольник не будет отрисовываться.
8. Использование CD3DX12_VIEWPORT и CD3DX12_RECT без подключения d3dx12.h.
9. Возможная ошибка в выборе адаптера (не проверяется поддержка D3D12).
10. Неправильная инициализация кучи для вершинного буфера (пропущены некоторые поля, хотя, возможно, они не требуются).
Оптимизации:
1. Добавить проверки ошибок.
2. Реализовать синхронизацию кадров через fence.
3. Использовать правильный цвет для ClearRenderTargetView.
4. Закрыть командный список после создания.
5. Подключить d3dx12.h или заменить на прямые структуры.
6. Создать PSO, шейдеры, корневую сигнатуру.
7. Обрабатывать изменение размера окна.
8. Проверять адаптер на поддержку D3D12.
9. Использовать правильное количество буферов в swap chain и проверять поддерживаемые форматы.
10. Убрать магические числа, получать размеры окна динамически.
Теперь по пунктам исправления:
- Проверка HRESULT: обернуть вызовы в SUCCEEDED или аналоги, и обрабатывать ошибки.
- Colors::Black заменить на массив {0.0f, 0.0f, 0.0f, 1.0f}.
- После создания commandList вызвать commandList->Close().
- Добавить ожидание fence после каждого кадра.
- Создать PSO, загрузить шейдеры, определить корневую сигнатуру.
- Включить d3dx12.h или использовать явное создание структур VIEWPORT и RECT.
- Использовать GetWindowRect или аналоги для получения текущего размера окна и обновления размеров буферов свопчейна при ресайзе.
- Проверять адаптер на возможность создания устройства D3D12.
Также, возможно, стоит использовать более оптимальные методы, например, создание ресурсов заранее, повторное использование ресурсов и т.д., но в данном примере это, возможно, излишне.
Теперь пример исправленного кода с комментариями:
1. Добавить проверки HRESULT.
2. Исправить Colors::Black.
3. Закрыть командный список после создания.
4. Добавить синхронизацию через fence.
5. Создать PSO и шейдеры.
6. Обработка ресайза окна.
7. Проверка адаптера.
Однако, реализация всего этого потребует много кода. В рамках примера, возможно, стоит сосредоточиться на основных ошибках.
Вот исправленный и оптимизированный код с комментариями: