2

I'm trying to set up a pipeline to do some raymarching-rendering.

At first I set up a vertex and a geometry shader to take just 1 arbitrary float, and make a quad so I can use just the fragment shader, and input data via passing it through all shaders or uniforms.

But then I came across the compute shader, and found some tutorials but all just did nearly the same, making a quad and rendering compute-shaders output to it, I think that's pretty stupid, if you have the possibility to render how you want with the compute shader and still do tricks to get your result.

After some further research i found the function 'glFramebufferImage2D' and as far as I understood it, it attaches an ImageTexture (the one I wrote to with my compute shader in my case) to the Framebuffer (the buffer thats displayed as far as i understood it). So i dont have to do the quad generating trick. But the code I wrote just shows a black screen. Did i get something wrong? or did i missed something in my code?

This is my code: (I'm not worrying about warnings and detaching shader-programs yet. I just wanted to test the concept for now.)

main.rs

extern crate sdl2;
extern crate gl;

use sdl2::pixels::Color;
use std::ffi::{CStr, CString};


fn main() {

    let width = 512;
    let height = 512;

    let sdl = sdl2::init().unwrap();
    let video_sys = sdl.video().unwrap();

    let gl_attr = video_sys.gl_attr();
    gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
    gl_attr.set_context_version(4, 1);

    let window = video_sys.window("test", width, height).opengl().build().unwrap();
    let gl_ctx = window.gl_create_context().unwrap();

    let gl = gl::load_with(|s| video_sys.gl_get_proc_address(s) as *const std::os::raw::c_void);


    unsafe {
        gl::Viewport(0, 0, width as gl::types::GLsizei, height as gl::types::GLsizei);
        gl::ClearColor(0.0, 0.0, 0.0, 1.0);
    }

    let shader = unsafe { gl::CreateShader(gl::COMPUTE_SHADER) };
    unsafe {
        gl::ShaderSource(shader, 1, &CString::new(include_str!("screen.comp")).unwrap().as_ptr(), std::ptr::null());
        gl::CompileShader(shader);
    }

    let program = unsafe { gl::CreateProgram() };
    unsafe {
        gl::AttachShader(program, shader);
        gl::LinkProgram(program);
        gl::UseProgram(program);
    }

    let mut tex_out : gl::types::GLuint = 0;
    unsafe {
        gl::GenTextures(1, &mut tex_out);
        gl::ActiveTexture(gl::TEXTURE0);
        gl::BindTexture(gl::TEXTURE_2D, tex_out);
        gl::TexImage2D(
            gl::TEXTURE_2D,
            0,
            gl::RGBA32F as gl::types::GLint,
            width as gl::types::GLsizei,
            height as gl::types::GLsizei,
            0,
            gl::RGBA,
            gl::FLOAT,
            std::ptr::null()
        );
        gl::BindImageTexture(0, tex_out, 0, gl::FALSE, 0, gl::WRITE_ONLY, gl::RGBA32F);
    }


    let mut event_pump = sdl.event_pump().unwrap();

    'main: loop {

        for event in event_pump.poll_iter() {
            match event {
                sdl2::event::Event::Quit {..} => break 'main,
                _ => {},
            }
        }

        unsafe {
            gl::DispatchCompute(width as gl::types::GLuint, height as gl::types::GLuint, 1);
            gl::MemoryBarrier(gl::SHADER_IMAGE_ACCESS_BARRIER_BIT);
            gl::FramebufferTexture2D(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, tex_out, 0);
        }

        window.gl_swap_window();

    }

}

screen.comp

#version 430
layout(local_size_x = 1, local_size_y = 1) in;
layout(rgba32f, binding = 0) uniform image2D img_output;

void main() {

    vec4 pixel = vec4(1.0, 1.0, 1.0, 1.0);

    ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy);

    imageStore(img_output, pixel_coords, pixel);

}

EDIT: the working code: (with some color test)

main.rs:

extern crate sdl2;
extern crate gl;

use std::ffi::{CStr, CString};


struct Renderer {
    width : u32 ,
    height : u32 ,
    shader : gl::types::GLuint ,
    program : gl::types::GLuint ,
}

impl Renderer {

    fn new(width : u32, height : u32, shader_name : &CStr) -> Self {

        unsafe {
            gl::Viewport(0, 0, width as gl::types::GLsizei, height as gl::types::GLsizei);
            gl::ClearColor(0.0, 0.0, 0.0, 1.0);
        }

        let shader = unsafe { gl::CreateShader(gl::COMPUTE_SHADER) };
        unsafe {
            gl::ShaderSource(shader, 1, &shader_name.as_ptr(), std::ptr::null());
            gl::CompileShader(shader);
        }

        let program = unsafe { gl::CreateProgram() };
        unsafe {
            gl::AttachShader(program, shader);
            gl::LinkProgram(program);
            gl::UseProgram(program);
        }

        let mut tex_out : gl::types::GLuint = 0;
        unsafe {
            gl::GenTextures(1, &mut tex_out);
            gl::ActiveTexture(gl::TEXTURE0);
            gl::BindTexture(gl::TEXTURE_2D, tex_out);
            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as gl::types::GLint);
            gl::TexImage2D(
                gl::TEXTURE_2D,
                0,
                gl::RGBA32F as gl::types::GLint,
                width as gl::types::GLsizei,
                height as gl::types::GLsizei,
                0,
                gl::RGBA,
                gl::FLOAT,
                std::ptr::null()
            );
            gl::BindImageTexture(0, tex_out, 0, gl::FALSE, 0, gl::WRITE_ONLY, gl::RGBA32F);
        }

        let mut fbo : gl::types::GLuint = 0;
        unsafe {
            gl::GenFramebuffers(1, &mut fbo );
            gl::BindFramebuffer(gl::FRAMEBUFFER, fbo );
            gl::FramebufferTexture2D(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, tex_out, 0);
            gl::BindFramebuffer(gl::READ_FRAMEBUFFER, fbo);
            gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, 0);
        }

        let resolution = unsafe { gl::GetUniformLocation(program, CString::new("iResolution").unwrap().as_ptr()) };
        unsafe { gl::Uniform2i(resolution, width as gl::types::GLint, height as gl::types::GLint); }

        Self {
            width : width ,
            height : height ,
            shader : shader ,
            program : program ,
        }

    }

    fn get_input(&self, name : &str) -> gl::types::GLint {
        unsafe { gl::GetUniformLocation(self.program, CString::new(name).unwrap().as_ptr()) }
    }

    fn input1f(&self, ptr : gl::types::GLint, f : gl::types::GLfloat) {
        unsafe { gl::Uniform1f(ptr, f) };
    }

    fn draw(&self) {

        unsafe {
            gl::DispatchCompute(self.width as gl::types::GLuint, self.height as gl::types::GLuint, 1);
            gl::MemoryBarrier(gl::SHADER_IMAGE_ACCESS_BARRIER_BIT);
            gl::BlitFramebuffer(0, 0, self.width as gl::types::GLint, self.height as gl::types::GLint, 0, 0, self.width as gl::types::GLint, self.height as gl::types::GLint, gl::COLOR_BUFFER_BIT, gl::LINEAR);
        }

    }

}

impl Drop for Renderer {

    fn drop(&mut self) {
        unsafe {
            gl::DeleteShader(self.shader);
            gl::DeleteProgram(self.program);
        }
    }

}


fn main() {

    let width = 512;
    let height = 512;

    let sdl = sdl2::init().unwrap();
    let video_sys = sdl.video().unwrap();

    let gl_attr = video_sys.gl_attr();
    gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
    gl_attr.set_context_version(4, 1);

    let window = video_sys.window("test", width, height).opengl().build().unwrap();
    let _gl_ctx = window.gl_create_context().unwrap();

    let _gl = gl::load_with(|s| video_sys.gl_get_proc_address(s) as *const std::os::raw::c_void);

    let render = Renderer::new(width, height, &CString::new(include_str!("screen.comp")).unwrap());
    let time_attr = render.get_input("time");

    let mut event_pump = sdl.event_pump().unwrap();

    let mut time = 0.0;

    'main: loop {

        for event in event_pump.poll_iter() {
            match event {
                sdl2::event::Event::Quit {..} => break 'main,
                _ => {},
            }
        }

        time += 0.01;
        if time > 1.0 {
            time = 0.0;
        }

        render.input1f(time_attr, time);
        render.draw();
        window.gl_swap_window();

    }

}

screen.comp:

#version 430
layout(local_size_x = 1, local_size_y = 1) in;
layout(rgba32f, binding = 0) uniform image2D img;
uniform ivec2 iResolution;

uniform float time;

void main() {

    ivec2 iCoords = ivec2(gl_GlobalInvocationID.xy);

    vec2 uv = vec2(iCoords) / vec2(iResolution);

    vec4 pixel = vec4(uv, time, 1.0);

    imageStore(img, iCoords, pixel);

}
0

2 Answers 2

3

There is an issue when you generate the texture. The initial value of TEXTURE_MIN_FILTER is NEAREST_MIPMAP_LINEAR. If you don't change it and you don't create mipmaps, then the texture is not "complete".
Set the TEXTURE_MIN_FILTER to either NEAREST or LINEAR to solve the issue:

gl::BindTexture(gl::TEXTURE_2D, tex_out);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR)

You've rendered an image from a compute shader to a texture object, but in order to blit that texture to the screen, you must copy that texture image to the color plane of the default framebuffer. It is not possible to change the default framebuffer's color plane simply by attaching a texture.

Instead, you need to create a named Framebuffer Object (glGenFramebuffers, glBindFramebuffer) and attach the texture object to the color plane of the framebuffer (glFramebufferTexture2D).

let mut fbo : gl::types::GLuint = 0;
gl::GenFramebuffers(1, &mut fbo);
gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
gl::FramebufferTexture2D(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, tex_out, 0)

Bind this framebuffer to the target GL_READ_FRAMEBUFFER (glBindFramebuffer) and bind the default frame buffer (zero) to the target GL_DRAW_FRAMEBUFFER.

gl::BindFramebuffer(gl::READ_FRAMEBUFFER, fbo);
gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, 0);

Finally, in the render loop, use glBlitFramebuffer to copy the content of the named frambuffer object (the texture) to the color plane of the default framebuffer.

gl::DispatchCompute(width as gl::types::GLuint, height as gl::types::GLuint, 1);
gl::MemoryBarrier(gl::SHADER_IMAGE_ACCESS_BARRIER_BIT);
gl::BlitFramebuffer(
    0, 0, width as gl::types::GLsizei, height as gl::types::GLsizei,
    0, 0, width as gl::types::GLsizei, height as gl::types::GLsizei,
    gl::COLOR_BUFFER_BIT, gl::LINEAR);

Note, by the use of glBlitFramebuffer, the size of the viewport can be different to the size of the texture image.
This approach won't work if the format of the texture would be integral. It works, because the format of the texture image is floating point and the format of the default framebuffer is fixed-point.

Sign up to request clarification or add additional context in comments.

10 Comments

Nice catch! An edited version of the source would be better still. :-)
I'm still learning Rust too, but will be happy to proof-read & test your attempt. It's the least I can do, seeing as I've been trying to find this bug since shortly after it was posted. ;-) Basically, you just need to have the GL-code wrapped in the "unsafe"-keyword.
so if i got that right the bindings are permanent and the only thing I have to do in my drawing loop is dispatch the shader wait for barrier bit and blit buffer?
Looks like eylion made the same fixes I did. Anyway, it's running for me now.
@Jackalope if your code worked could you post it? Mine doesnt work jet. I still get a black screen. I even added some error feedback from compiling the shader, to exclude errors in the shader.
|
1

Here's a revision with the suggested fixes and a few polishes:

extern crate sdl2;
extern crate gl;

//use sdl2::pixels::Color;
//use std::ffi::{CStr, CString};
use std::ffi::{CString};

fn main() {

let width = 512;
let height = 512;

let sdl = sdl2::init().unwrap();
let video_sys = sdl.video().unwrap();

let gl_attr = video_sys.gl_attr();
gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
gl_attr.set_context_version(4, 1);

let window = video_sys.window("test", width, height).opengl().build().unwrap();
let _gl_ctx = window.gl_create_context().unwrap();

let _gl = gl::load_with(|s| video_sys.gl_get_proc_address(s) as *const std::os::raw::c_void);


unsafe {
    gl::Viewport(0, 0, width as gl::types::GLsizei, height as gl::types::GLsizei);
    gl::ClearColor(0.1, 0.30, 0.50, 1.0);

    let shader = gl::CreateShader(gl::COMPUTE_SHADER);
    gl::ShaderSource(shader, 1, &CString::new(include_str!("screen.comp")).unwrap().as_ptr(), std::ptr::null());
    gl::CompileShader(shader);

    let program = gl::CreateProgram();
    gl::AttachShader(program, shader);
    gl::LinkProgram(program);
    gl::UseProgram(program);

    let mut tex_out : gl::types::GLuint = 0;

    gl::GenTextures(1, &mut tex_out);
    gl::ActiveTexture(gl::TEXTURE0);
    gl::BindTexture(gl::TEXTURE_2D, tex_out);
    gl::TexImage2D( gl::TEXTURE_2D, 0, gl::RGBA32F as gl::types::GLint, width as gl::types::GLsizei, height as gl::types::GLsizei, 0, gl::RGBA, gl::FLOAT, std::ptr::null() );
    let mut event_pump = sdl.event_pump().unwrap();
    let mut fbo: gl::types::GLuint = 0;

    gl::BindImageTexture(0, tex_out, 0, gl::FALSE, 0, gl::WRITE_ONLY, gl::RGBA32F);
    gl::GenFramebuffers(1, &mut fbo);
    gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
    gl::FramebufferTexture2D(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, tex_out, 0);
    gl::BindFramebuffer(gl::READ_FRAMEBUFFER, fbo);
    gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, 0);



    'main: loop {

        for event in event_pump.poll_iter() {
            match event {
                sdl2::event::Event::Quit {..} => break 'main,
                _ => {},
            }
        }

        gl::Clear(gl::COLOR_BUFFER_BIT);
        gl::DispatchCompute(width as gl::types::GLuint, height as gl::types::GLuint, 1);
        gl::MemoryBarrier(gl::SHADER_IMAGE_ACCESS_BARRIER_BIT);
        gl::BlitFramebuffer(
            0, 0, width as gl::types::GLsizei, height as gl::types::GLsizei,
            0, 0, width as gl::types::GLsizei, height as gl::types::GLsizei,
            gl::COLOR_BUFFER_BIT, gl::LINEAR);

        window.gl_swap_window();
    }


  }

}

2 Comments

well, the program runs, but it still doesnt seem to work jet. I get a blue screen, which is the background color you defined (gl::ClearColor(0.1, 0.30, 0.50, 1.0)), but not the full white texture the compute-shader schould output
I was getting a white screen at one point, but the exit-function wasn't working. I'll return to this soon...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.