簡體   English   中英

使用Glium中的UniformBuffer將任意大小的對象傳遞給片段着色器

[英]Passing an arbitrarily sized object to a fragment shader using a UniformBuffer in Glium

我在嘗試一系列不同的技術時提出了我的問題,但我沒有多少經驗。 可悲的是,我甚至不知道我是否犯了一個愚蠢的邏輯錯誤,我是否正在使用glium crate錯誤,我是否搞亂了GLSL等等。無論如何,我設法從一個新的Rust項目開始從頭開始,朝着顯示我的問題的最小例子努力,問題至少在我的計算機上重現。

最小的例子最終難以解釋,所以我首先做一個更小的例子來做我想做的事情,雖然通過黑客攻擊並限制為128個元素(四次32位,在GLSL uvec4 )。 從這一點來看,我的問題出現的版本的步驟非常簡單。

一個工作版本,具有簡單的uniform和位移

該程序在屏幕上創建一個矩形,水平紋理坐標從0.0128.0 該程序包含一個用於矩形的頂點着色器,以及一個片段着色器,它使用紋理坐標在矩形上繪制垂直條紋:如果紋理坐標(夾在一個uint )是奇數,它會繪制一種顏色,當紋理坐標是甚至,它畫出了另一種顏色。

// GLIUM, the crate I'll use to do "everything OpenGL"
#[macro_use]
extern crate glium;

// A simple struct to hold the vertices with their texture-coordinates.
// Nothing deviating much from the tutorials/crate-documentation.
#[derive(Copy, Clone)]
struct Vertex {
    position: [f32; 2],
    tex_coords: [f32; 2],
}

implement_vertex!(Vertex, position, tex_coords);


// The vertex shader's source. Does nothing special, except passing the
// texture coordinates along to the fragment shader.
const VERTEX_SHADER_SOURCE: &'static str = r#"
    #version 140

    in vec2 position;
    in vec2 tex_coords;
    out vec2 preserved_tex_coords;

    void main() {
        preserved_tex_coords = tex_coords;
        gl_Position = vec4(position, 0.0, 1.0);
    }
"#;

// The fragment shader. uses the texture coordinates to figure out which color to draw.
const FRAGMENT_SHADER_SOURCE: &'static str =  r#"
    #version 140

    in vec2 preserved_tex_coords;
    // FIXME: Hard-coded max number of elements. Replace by uniform buffer object
    uniform uvec4 uniform_data;
    out vec4 color;

    void main() {
        uint tex_x = uint(preserved_tex_coords.x);
        uint offset_in_vec = tex_x / 32u;
        uint uint_to_sample_from = uniform_data[offset_in_vec];
        bool the_bit = bool((uint_to_sample_from >> tex_x) & 1u);
        color = vec4(the_bit ? 1.0 : 0.5, 0.0, 0.0, 1.0);
    }
"#;

// Logic deciding whether a certain index corresponds with a 'set' bit on an 'unset' one.
// In this case, for the alternating stripes, a trivial odd/even test.
fn bit_should_be_set_at(idx: usize) -> bool {
    idx % 2 == 0
}

fn main() {
    use glium::DisplayBuild;
    let display = glium::glutin::WindowBuilder::new().build_glium().unwrap();

    // Sets up the vertices for a rectangle from -0.9 till 0.9 in both dimensions.
    // Texture coordinates go from 0.0 till 128.0 horizontally, and from 0.0 till
    // 1.0 vertically.
    let vertices_buffer = glium::VertexBuffer::new(
        &display,
        &vec![Vertex { position: [ 0.9, -0.9], tex_coords: [  0.0, 0.0] },
              Vertex { position: [ 0.9,  0.9], tex_coords: [  0.0, 1.0] },
              Vertex { position: [-0.9, -0.9], tex_coords: [128.0, 0.0] },
              Vertex { position: [-0.9,  0.9], tex_coords: [128.0, 1.0] }]).unwrap();
    // The rectangle will be drawn as a simple triangle strip using the vertices above.
    let indices_buffer = glium::IndexBuffer::new(&display,
                                                 glium::index::PrimitiveType::TriangleStrip,
                                                 &vec![0u8, 1u8, 2u8, 3u8]).unwrap();
    // Compiling the shaders defined statically above.
    let shader_program = glium::Program::from_source(&display,
                                                     VERTEX_SHADER_SOURCE,
                                                     FRAGMENT_SHADER_SOURCE,
                                                     None).unwrap();

    // Some hackyy bit-shifting to get the 128 alternating bits set up, in four u32's,
    // which glium manages to send across as an uvec4.
    let mut uniform_data = [0u32; 4];
    for idx in 0..128 {
        let single_u32 = &mut uniform_data[idx / 32];
        *single_u32 = *single_u32 >> 1;
        if bit_should_be_set_at(idx) {
            *single_u32 = *single_u32 | (1 << 31);
        }
    }

    // Trivial main loop repeatedly clearing, drawing rectangle, listening for close event.
    loop {
        use glium::Surface;
        let mut frame = display.draw();
        frame.clear_color(0.0, 0.0, 0.0, 1.0);
        frame.draw(&vertices_buffer, &indices_buffer, &shader_program,
                   &uniform! { uniform_data: uniform_data },
                   &Default::default()).unwrap();
        frame.finish().unwrap();

        for e in display.poll_events() { if let glium::glutin::Event::Closed = e { return; } }
    }
}

但這還不夠好......

這個程序工作,並顯示交替條紋的矩形,但有明確的限制,限制為128條紋(或64條紋,我猜。其他64條是“矩形的背景”)。 為了允許任意多個條帶(或者,通常,將任意多個數據傳遞給片段着色器),可以使用均勻的緩沖對象這是glium暴露的 glium repo中最相關的例子遺憾地無法在我的機器上編譯:不支持GLSL版本, buffer關鍵字在支持的版本中是語法錯誤,一般不支持計算着色器(在我的機器上使用glium) ,無頭渲染上下文。

一個不太多的工作版本,緩沖區uniform

因此,無法從該示例開始,我必須從頭開始使用文檔。 對於上面的例子,我想出了以下內容:

// Nothing changed here...
#[macro_use]
extern crate glium;

#[derive(Copy, Clone)]
struct Vertex {
    position: [f32; 2],
    tex_coords: [f32; 2],
}

implement_vertex!(Vertex, position, tex_coords);


const VERTEX_SHADER_SOURCE: &'static str = r#"
    #version 140

    in vec2 position;
    in vec2 tex_coords;
    out vec2 preserved_tex_coords;

    void main() {
        preserved_tex_coords = tex_coords;
        gl_Position = vec4(position, 0.0, 1.0);
    }
"#;
// ... up to here.

// The updated fragment shader. This one uses an entire uint per stripe, even though only one
// boolean value is stored in each.
const FRAGMENT_SHADER_SOURCE: &'static str =  r#"
    #version 140
    // examples/gpgpu.rs uses
    //     #version 430
    //     buffer layout(std140);
    // but that shader version is not supported by my machine, and the second line is
    // a syntax error in `#version 140`

    in vec2 preserved_tex_coords;

    // Judging from the GLSL standard, this is what I have to write:
    layout(std140) uniform;
    uniform uniform_data {
        // TODO: Still hard-coded max number of elements, but now arbitrary at compile-time.
        uint values[128];
    };
    out vec4 color;

    // This one now becomes much simpler: get the coordinate, clamp to uint, index into
    // uniform using tex_x, cast to bool, choose color.
    void main() {
        uint tex_x = uint(preserved_tex_coords.x);
        bool the_bit = bool(values[tex_x]);
        color = vec4(the_bit ? 1.0 : 0.5, 0.0, 0.0, 1.0);
    }
"#;


// Mostly copy-paste from glium documentation: define a Data type, which stores u32s,
// make it implement the right traits
struct Data {
    values: [u32],
}

implement_buffer_content!(Data);
implement_uniform_block!(Data, values);


// Same as before
fn bit_should_be_set_at(idx: usize) -> bool {
    idx % 2 == 0
}

// Mostly the same as before
fn main() {
    use glium::DisplayBuild;
    let display = glium::glutin::WindowBuilder::new().build_glium().unwrap();

    let vertices_buffer = glium::VertexBuffer::new(
        &display,
        &vec![Vertex { position: [ 0.9, -0.9], tex_coords: [  0.0, 0.0] },
              Vertex { position: [ 0.9,  0.9], tex_coords: [  0.0, 1.0] },
              Vertex { position: [-0.9, -0.9], tex_coords: [128.0, 0.0] },
              Vertex { position: [-0.9,  0.9], tex_coords: [128.0, 1.0] }]).unwrap();
    let indices_buffer = glium::IndexBuffer::new(&display,
                                                 glium::index::PrimitiveType::TriangleStrip,
                                                 &vec![0u8, 1u8, 2u8, 3u8]).unwrap();
    let shader_program = glium::Program::from_source(&display,
                                                     VERTEX_SHADER_SOURCE,
                                                     FRAGMENT_SHADER_SOURCE,
                                                     None).unwrap();


    // Making the UniformBuffer, with room for 128 4-byte objects (which u32s are).
    let mut buffer: glium::uniforms::UniformBuffer<Data> =
              glium::uniforms::UniformBuffer::empty_unsized(&display, 4 * 128).unwrap();
    {
        // Loop over all elements in the buffer, setting the 'bit'
        let mut mapping = buffer.map();
        for (idx, val) in mapping.values.iter_mut().enumerate() {
            *val = bit_should_be_set_at(idx) as u32;
            // This _is_ actually executed 128 times, as expected.
        }
    }

    // Iterating again, reading the buffer, reveals the alternating 'bits' are really
    // written to the buffer.

    // This loop is similar to the original one, except that it passes the buffer
    // instead of a [u32; 4].
    loop {
        use glium::Surface;
        let mut frame = display.draw();
        frame.clear_color(0.0, 0.0, 0.0, 1.0);
        frame.draw(&vertices_buffer, &indices_buffer, &shader_program,
                   &uniform! { uniform_data: &buffer },
                   &Default::default()).unwrap();
        frame.finish().unwrap();

        for e in display.poll_events() { if let glium::glutin::Event::Closed = e { return; } }
    }
}

我希望這會產生相同的條紋矩形(或者給出一些錯誤,如果我做的事情是錯誤的話會崩潰)。 相反,它顯示矩形,最右邊的四分之一為實心明亮的紅色(即,“當片段着色器讀取它時,該位似乎設置了”),其余四分之三則顯示為深紅色(即“該位未設置為片段着色器讀取它“)。

自原始發布以來更新

我真的在黑暗中刺傷,所以認為它可能是一個低級錯誤,內存排序,字節順序,緩沖區溢出/欠載等等。我嘗試了各種方法用容易辨別的位模式填充“相鄰”內存位置(例如,每三組中有一位,每四位一組,兩組后跟兩組未設置等)。 這沒有改變輸出。

其中一個明顯的方式來獲得內存“近”的uint values[128]就是把它變成了Data結構,只是在前面的values (后面values是不允許的,因為Datavalues: [u32]是動態大小)。 如上所述,這不會改變輸出。 但是,將一個正確填充的uvec4放在uniform_data緩沖區中,並使用類似於第一個示例的main函數產生原始結果。 這表明glium::uniforms::UniformBuffer<Data> 在本質上 確實工作。

因此,我更新了標題,以反映問題似乎在其他地方。

在Eli的回答之后

@Eli Friedman的回答幫助我朝着解決方案的方向前進,但我還沒到那里。

分配和填充四倍大的緩沖區確實改變了輸出,從四分之一填充矩形到完全填充矩形。 糟糕,這不是我想要的。 不過,我的着色器現在正在閱讀正確的記憶詞。 所有這些單詞都應該填充正確的位模式。 盡管如此,矩形的任何部分都沒有變成條紋。 由於bit_should_be_set_at應該設置每隔一位,我提出了一個假設,即發生的事情如下:

Bits: 1010101010101010101010101010101010101
Seen: ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   
What it looks like: all bits set

為了驗證這個假設,我改變了bit_should_be_set_at以在bit_should_be_set_at和8的倍數上返回true 。結果與我的假設一致:

Bits: 1001001001001001001001001001001001001
Seen: ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   
What it looks like: first bit set, then repeating two unset, one set.

Bits: 1000100010001000100010001000100010001
Seen: ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   
What it looks like: all bits set

Bits: 1000010000100001000010000100001000010
Seen: ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   
What it looks like: first bit set, then repeating four unset, one set.

Bits: 1000001000001000001000001000001000001
Seen: ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   
What it looks like: first bit set, then repeating two unset, one set.

Bits: 1000000100000010000001000000100000010
Seen: ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   
What it looks like: first bit set, then repeating six unset, one set.

Bits: 1000000010000000100000001000000010000
Seen: ^   ^   ^   ^   ^   ^   ^   ^   ^   ^   
What it looks like: first bit set, then every other bit set.

這個假設有意義嗎? 無論如何:看起來問題是設置數據(在Rust端),還是將其讀回(在GLSL端)?

您遇到的問題與如何分配制服有關。 uint values[128]; 沒有你認為的內存布局; 它實際上具有與uint4 values[128]相同的內存布局uint4 values[128] 請參閱https://www.opengl.org/registry/specs/ARB/uniform_buffer_object.txt第2.15.3.1.2小節。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM