Разработка шейдерного класса - PullRequest
5 голосов
/ 28 августа 2011

С тех пор, как я начал изучать OpenGL, я подумал, что я бы также написал небольшую платформу C ++ (для себя), чтобы избежать тошноты, которую явно вызывает чрезмерное использование кода C-ish.:) Так как я собираюсь придерживаться Qt, фреймворк использует некоторые классы Qt.

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

class Shader
{
public:
    //create a shader with no source code 
    explicit Shader(GLenum shaderType);
    //create a shader with source code and compile it
    Shader(GLenum shaderType, const QString& sourceCode);
    //create a shader from source file and compile it
    Shader(GLenum shaderType, QFile& sourceFile);
    ~Shader();

    //change the source code and recompile
    void Source(QFile& sourceFile);
    void Source(const QString& sourceCode);

    GLuint get() const; //get the handle

private:
    //common part for creation in different constructors
    void createShader(GLenum shaderType); 
    //compile
    void compile();

private:
    GLuint handle;
};

Должно быть совершенно очевидно, что делают разные функции.Каждый вызывает соответствующие подпрограммы OpenGL, проверяет ошибки и выдает исключения в случае любого сбоя.Конструктор вызывает glCreateShader.Теперь сложная часть.Деструктору нужно вызвать glDeleteShader(handle);, но в этом случае у меня возникает дилемма:

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

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

Shader s1(GL_VERTEX_SHADER, QFile("MyVertexShader.vp"));
Shader s2(s1);
s2.Source(QFile("MyOtherVertexShader.vp"));

Как видите, я изменил источник s1 через s2, потому что они используют один и тот же внутренний дескриптор шейдера.Если честно, я не вижу здесь большой проблемы.Я написал класс, так что я знаю, что его семантика копирования такова, и я в порядке.Проблема в том, что я не уверен, что такой дизайн когда-либо приемлем.Все это может быть достигнуто с помощью общих указателей Option1 +, с той лишь разницей, что я не хочу иметь общий указатель каждый раз, когда создаю шейдер (не из соображений производительности - просто для удобства синтаксиса).

Q1: Пожалуйста, прокомментируйте варианты и, если необходимо, всю идею. 1
Q2: Если бы я выбрал вариант 2, я должен реализовать это сам или есть готовый класс в boost или Qt, от которого я мог бы получить или иметь член, и я бы получил бесплатный подсчет ссылок?
Q3: Согласны ли вычто сделать Shader абстрактным классом и иметь три производных класса VertexShader, FragmentShader и GeometryShader было бы излишним?

1 Если вы хотите отослать меня к существующей платформе C ++ OpenGL, это очень хорошо (так как я на самом деле ее не нашел)но это действительно должно быть побочным примечанием, чем ответом на мои вопросы.Также обратите внимание, что я видел класс QGLShader где-то в документации, но он, по-видимому, отсутствует в моей версии Qt, и у меня есть причины не обновляться прямо сейчас.

UPDATE

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

Ответы [ 2 ]

3 голосов
/ 28 августа 2011

Я уже оценил эти параметры, и я реализовал класс шейдера другим способом.

Первый момент заключается в том, что CreateShader и DeleteShader нужен текущий контекст, что не всегда верно. Все функции возвращают ошибки, и последняя может вызвать утечку. Итак, я представлю подпрограмму Create и Delete, которая действительно вызывает CreateShader и Delete Shader. Таким образом, можно уничтожить объект даже в отдельном потоке (сам шейдер будет уничтожен позже, когда контекст будет текущим.

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

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

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

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

3 голосов
/ 28 августа 2011

Я говорю, используйте вариант 1: он может делать все, что может вариант 2 (через умные указатели), тогда как вариант 2 заставляет вас оплачивать косвенную стоимость, даже если она вам не нужна. Кроме того, это, возможно, легче написать.

Точно так же я когда-то рассматривал использование handle-body / PIMPL при переносе через C API, чтобы позволить возвращать объекты из функций (тип дескриптора C не гарантированно копируемый, поэтому для этого было необходимо косвенное обращение). Я отказался от этого, поскольку std::unique_ptr<T> выполняет преобразование неподвижное -> подвижное (так же, как shared_ptr<T> делает T копируемым). С тех пор я проектирую свои классы, чтобы у них была «самая узкая» семантика перемещения / копирования.

Однако у вас есть смысл, когда дело доходит до синтаксического шума! Такие вещи, как Boost.Phoenix и лямбды, как правило, помогают. Если / когда они не являются опцией, я бы сказал, что написать отдельный shared_shader или какую-либо оболочку (обертку-обертку?) Имеет смысл, по крайней мере, для кода на уровне библиотеки (что, я считаю, имеет место Вот). Я не знаю ни одной утилиты, которая бы помогала с утомительным написанием функций пересылки.

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


Поскольку вы просите привести пример милости Феникса.

Что я хочу сделать, если мне не нужно разыменовывать:

std::transform(begin, end, functor);

Вместо того, чтобы:

std::for_each(begin, end, *arg1 = ref(functor)(*arg1));

Можно по-прежнему использовать std::transform (желательно для ясности), используя некоторые средства Phoenix (construct IIRC), но это будет стоить выделения.

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