2015-12-05 2 views
1

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

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

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

let shader_src_A = r#" 
attribute float a; 
attribute float b; 

out float b; 

void main() { 
    b = a * b; 
} 
"#; 

let shader_src_B = r#" 
attribute float a; 
attribute float b; 

out float b; 

void main() { 
    b = a + b; 
} 
"#; 

let mut program_A : ShaderProgram; 
let mut program_B : ShaderProgram; 

fn init() { 
    initGL(); 
    program_A = compile_and_link(shader_src_A); 
    program_B = compile_and_link(shader_src_B); 
} 

fn render() { 
    let data1 = vec![1,2,3,4]; 
    let data2 = vec![5,6,7,8]; 

    // move data to the gpu 
    let gpu_data_1 = move_to_gpu(data1); 
    let gpu_data_2 = move_to_gpu(data2); 

    let gpu_data_3 : GpuData<float>; 
    let gpu_data_4 : GpuData<float>; 

    program_A(
     (gpu_data_1, gpu_data_2) // input 
     (gpu_data_3,) // output 
    ); 
    program_B(
     (gpu_data_1, gpu_data_2) // input 
     (gpu_data_4,) // output 
    ); 

    let data_3 = move_to_cpu(gpu_data_3); 
    let data_4 = move_to_cpu(gpu_data_4); 

    println!("data_3 {:?} data_4 {:?}", data_3, data_4); 
    // data_3 [5, 12, 21, 32] data_4 [6, 8, 10, 12] 
} 

Цель для меня, чтобы быть в состоянии написать что-то вроде этого:

fn init() { 
    initGL(); 
    mystery_macro!(); 
} 

fn render() { 
    let data1 = vec![1,2,3,4]; 
    let data2 = vec![5,6,7,8]; 

    // move data to the gpu 
    let gpu_data_1 = move_to_gpu(data1); 
    let gpu_data_2 = move_to_gpu(data2); 

    let gpu_data_3 : GpuData<float>; 
    let gpu_data_4 : GpuData<float>; 

    shade!( 
     (gpu_data_1, gpu_data_2), // input tuple 
     (gpu_data_3,),   // output tuple 
     "gpu_data_3 = gpu_data_1 * gpu_data_2;" // this is the shader source, the rest should be generated by the macro. 
    ); 

    shade!( 
     (gpu_data_1, gpu_data_2), // input tuple 
     (gpu_data_3,),   // output tuple 
     "gpu_data_4 = gpu_data_1 + gpu_data_2;" // this is the shader source, the rest should be generated by the macro. 
    ); 

    let data_3 = move_to_cpu(gpu_data_3); 
    let data_4 = move_to_cpu(gpu_data_4); 

    println!("data_3 {:?} data_4 {:?}", data_3, data_4); 
} 

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

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

EDIT:

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

+0

Итак, вы пытаетесь сгенерировать код в функции init, заданной источником шейдера, из совершенно другой области кода? Я не уверен, что это возможно. – LinearZoetrope

+0

Например, вы должны иметь возможность легко написать макрос, который будет вставлять строку в шаблон источника шейдера, но поиск этих строк из загадки «shade!» Вызывает то, что в коде совершенно несовместимо, не произойдет (по крайней мере не без серьезной темной магии, с которой я не знаком). Вы МОЖЕТЕ сделать это с расширением компилятора. – LinearZoetrope

+0

@ Jsor На данный момент я думаю, что если вы можете скомпилировать программы, ленивые при первом вызове. Таким образом, при первом вызове генерируется источник, программа скомпилирована и выполнена. Второй вызов цикла рендеринга, программа должна быть повторно использована. – Arne

ответ

2

С The Rust Programming Language раздела на macros (курсив мой):

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

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

Кроме того, макросы Rust работают на уровне выше макросов макросов. Макросы ржавчины не представлены с исходным текстом, а вместо этого имеют некоторые части АСТ программы.

Давайте начнем с этой упрощенной версией:

struct Shader(usize); 
impl Shader { 
    fn compile(source: &str) -> Shader { 
     println!("Compiling a shader"); 
     Shader(source.len()) 
    } 

    fn run(&self) { 
     println!("Running a shader {}", self.0) 
    } 
} 

fn main() { 
    for _ in 0..10 { 
     inner_loop(); 
    } 
} 

fn inner_loop() { 
    let shader_1_src = r#"add 1 + 1"#; 
    let shader_1 = Shader::compile(shader_1_src); 

    let shader_2_src = r#"add 42 + 53"#; 
    let shader_2 = Shader::compile(shader_2_src); 

    shader_1.run(); 
    shader_2.run(); 
} 

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

#[macro_use] 
extern crate lazy_static; 

// Previous code... 

fn inner_loop() { 
    const SHADER_1_SRC: &'static str = r#"add 1 + 1"#; 
    lazy_static! { 
     static ref SHADER_1: Shader = Shader::compile(SHADER_1_SRC); 
    } 

    const SHADER_2_SRC: &'static str = r#"add 42 + 53"#; 
    lazy_static! { 
     static ref SHADER_2: Shader = Shader::compile(SHADER_2_SRC); 
    } 

    SHADER_1.run(); 
    SHADER_2.run(); 
} 

Вы можете пойти еще один шаг вперед и сделать еще один макрос вокруг этого:

// Previous code... 

macro_rules! shader { 
    ($src_name: ident, $name: ident, $l: expr, $r: expr) => { 
     const $src_name: &'static str = concat!("add ", $l, " + ", $r); 
     lazy_static! { 
      static ref $name: Shader = Shader::compile($src_name); 
     } 
    } 
} 

fn inner_loop() { 
    shader!(S1, SHADER_1, "1", "2"); 
    shader!(S2, SHADER_2, "42", "53"); 

    SHADER_1.run(); 
    SHADER_2.run(); 
} 

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

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

Существует вероятность того, что syntax extension сможет делать то, что вы хотите, но у меня нет достаточного опыта с ними, но для правильного управления им или из него.

+0

К сожалению, компиляция шейдеров при компиляции невозможна время. Результатом компиляции шейдера является просто int, который представляет вашу программу на графическом процессоре. И тогда в более поздних версиях OpenGL есть предварительно скомпилированные шейдеры, но ожидается, что их результат будет несовместимым между разными поставщиками, даже между разными GPU от одного и того же поставщика. Но возможно ли выписать источник шейдера hte из макроса во время компиляции в файл, тогда функция init просто должна будет проанализировать этот один файл? – Arne

+0

* возможно ли выписать источник шейдера из макроса во время компиляции * - опять же, если вы можете написать код, который делает это * без * макроса, тогда вы можете написать макрос, чтобы сделать его более чистым. Макросы действительно не дают вам больше энергии, они просто уменьшают шаблон.* результат компиляции шейдера, как правило, просто int * - поскольку это значение будет создано в вашем 'main' методе, как вы передадите его из' main' в местоположение, в котором ваш шейдер фактически используется? – Shepmaster

+0

Вы должны знать, что я не программист на ржавчине, я программист на C++, и я немного расстроен в отношении ограничений C++ и его шаблона. Я собираюсь оценить, дает ли ржавчина достаточную силу для того, чтобы понять, стоит ли перескакивать, и если это не выполнимо в ржавчине, как будто я хочу, чтобы это было сделано, я буду искать лучшие языки программирования. И чтобы ответить на ваш вопрос, да, можно обойтись без макросов, я дал (упрощенный) пример, как это будет сделано без макросов. Проблема в том, что большая часть шаблона, который мне приходится писать, находится вне вызова макроса. – Arne

Смежные вопросы