glium/vertex/
transform_feedback.rs

1use std::{ mem, fmt };
2use std::error::Error;
3
4use crate::version::Api;
5use crate::version::Version;
6use crate::context::CommandContext;
7use crate::backend::Facade;
8use crate::BufferExt;
9use crate::GlObject;
10use crate::ContextExt;
11use crate::CapabilitiesSource;
12use crate::TransformFeedbackSessionExt;
13use crate::buffer::{Buffer, BufferAnySlice};
14use crate::index::PrimitiveType;
15use crate::program::OutputPrimitives;
16use crate::program::Program;
17use crate::vertex::Vertex;
18
19use crate::gl;
20
21/// Transform feedback allows you to obtain in a buffer the list of the vertices generated by
22/// the vertex shader, geometry shader, or tessellation evaluation shader of your program. This
23/// is usually used to cache the result in order to draw the vertices multiple times with multiple
24/// different fragment shaders.
25///
26/// To use transform feedback, you must create a transform feedback session. A transform feedback
27/// session mutably borrows the buffer where the data will be written. Each draw command submitted
28/// with a session will continue to append data after the data written by the previous draw command.
29/// You can only use the data when the session is destroyed.
30///
31/// # Notes
32///
33/// Here are a few things to note if you aren't familiar with transform feedback:
34///
35/// - The program you use must have transform feedback enabled, either with attributes in the
36///   vertex shader's source code (for recent OpenGL versions only) or by indicating a list of
37///   vertex attributes when building the program.
38///
39/// - A transform feedback session is bound to a specific program and buffer. You can't switch
40///   them once the session has been created. An error is generated if you draw with a different
41///   program than the one you created the session with.
42///
43/// - The transform feedback process doesn't necessarily fill the whole buffer. To retrieve the
44///   number of vertices that are written to the buffer, use a query object (see the
45///   `draw_parameters` module). It is however usually easy to determine in advance the number of
46///   vertices that will be written based on the input data.
47///
48/// - The buffer will obtain either a list of points, a list of lines (two vertices), or a list of
49///   triangles (three vertices). If you draw a triangle strip or a triangle fan for example,
50///   individual triangles will be written to the buffer (meaning that some vertices will be
51///   duplicated).
52///
53/// - You can use the same session multiple times in a row, in which case the data will continue
54///   to be pushed in the buffer after the existing data. However you must always use the same type
55///   of primitives and the same program.
56///
57/// # Example
58///
59/// ```no_run
60/// # use glium::{implement_vertex, uniform};
61/// # use glium::Surface;
62/// # use glutin::surface::{ResizeableSurface, SurfaceTypeTrait};
63/// # fn example<T>(display: glium::Display<T>, program: glium::Program,
64/// #            vb: glium::vertex::VertexBufferAny, ib: glium::index::IndexBuffer<u16>)
65/// #     where T: SurfaceTypeTrait + ResizeableSurface {
66/// #[derive(Copy, Clone, Debug, PartialEq)]
67/// struct Vertex {
68///     output_val: (f32, f32),
69/// }
70///
71/// implement_vertex!(Vertex, output_val);
72///
73/// let mut out_buffer: glium::VertexBuffer<Vertex> = glium::VertexBuffer::empty(&display, 6).unwrap();
74///
75/// {
76///     let session = glium::vertex::TransformFeedbackSession::new(&display, &program,
77///                                                                &mut out_buffer).unwrap();
78///
79///     let params = glium::DrawParameters {
80///         transform_feedback: Some(&session),
81///         .. Default::default()
82///     };
83///
84///     display.draw().draw(&vb, &ib, &program, &uniform!{}, &params).unwrap();
85/// }
86///
87/// let result: Vec<Vertex> = out_buffer.read().unwrap();
88/// println!("List of generated vertices: {:?}", result);
89/// # }
90/// ```
91#[derive(Debug)]
92pub struct TransformFeedbackSession<'a> {
93    buffer: BufferAnySlice<'a>,
94    program: &'a Program,
95}
96
97/// Error that can happen when creating a `TransformFeedbackSession`.
98#[derive(Debug, Clone)]
99pub enum TransformFeedbackSessionCreationError {
100    /// Transform feedback is not supported by the OpenGL implementation.
101    NotSupported,
102
103    /// The format of the output doesn't match what the program is expected to output.
104    WrongVertexFormat,
105}
106
107impl fmt::Display for TransformFeedbackSessionCreationError {
108    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
109        use self::TransformFeedbackSessionCreationError::*;
110        let desc = match *self {
111            NotSupported =>
112                "Transform feedback is not supported by the OpenGL implementation",
113            WrongVertexFormat =>
114                "The format of the output doesn't match what the program is expected to output",
115        };
116        fmt.write_str(desc)
117    }
118}
119
120impl Error for TransformFeedbackSessionCreationError {}
121
122/// Returns true if transform feedback is supported by the OpenGL implementation.
123#[inline]
124pub fn is_transform_feedback_supported<F: ?Sized>(facade: &F) -> bool where F: Facade {
125    let context = facade.get_context();
126
127    context.get_version() >= &Version(Api::Gl, 3, 0) ||
128    context.get_version() >= &Version(Api::GlEs, 3, 0) ||
129    context.get_extensions().gl_ext_transform_feedback
130}
131
132impl<'a> TransformFeedbackSession<'a> {
133    /// Builds a new transform feedback session.
134    ///
135    /// TODO: this constructor should ultimately support passing multiple buffers of different
136    ///       types
137    pub fn new<F: ?Sized, V>(facade: &F, program: &'a Program, buffer: &'a mut Buffer<[V]>)
138                     -> Result<TransformFeedbackSession<'a>, TransformFeedbackSessionCreationError>
139                     where F: Facade, V: Vertex + Copy + Send + 'static
140    {
141        if !is_transform_feedback_supported(facade) {
142            return Err(TransformFeedbackSessionCreationError::NotSupported);
143        }
144
145        if !program.transform_feedback_matches(&<V as Vertex>::build_bindings(),
146                                               mem::size_of::<V>())
147        {
148            return Err(TransformFeedbackSessionCreationError::WrongVertexFormat);
149        }
150
151        Ok(TransformFeedbackSession {
152            buffer: buffer.as_slice_any(),
153            program,
154        })
155    }
156}
157
158impl<'a> TransformFeedbackSessionExt for TransformFeedbackSession<'a> {
159    fn bind(&self, ctxt: &mut CommandContext<'_>, draw_primitives: PrimitiveType) {
160        // TODO: check that the state matches what is required
161        if ctxt.state.transform_feedback_enabled.is_some() {
162            unimplemented!();
163        }
164
165        // FIXME: use the memory barrier system
166        self.buffer.bind_to_transform_feedback(ctxt, 0);
167
168        unsafe {
169            let primitives = match (self.program.get_output_primitives(), draw_primitives) {
170                (Some(OutputPrimitives::Points), _) => gl::POINTS,
171                (Some(OutputPrimitives::Lines), _) => gl::LINES,
172                (Some(OutputPrimitives::Triangles), _) => gl::TRIANGLES,
173                (Some(OutputPrimitives::Quads), _) => panic!(),         // TODO: return a proper error
174                (None, PrimitiveType::Points) => gl::POINTS,
175                (None, PrimitiveType::LinesList) => gl::LINES,
176                (None, PrimitiveType::LinesListAdjacency) => gl::LINES,
177                (None, PrimitiveType::LineStrip) => gl::LINES,
178                (None, PrimitiveType::LineStripAdjacency) => gl::LINES,
179                (None, PrimitiveType::LineLoop) => gl::LINES,
180                (None, PrimitiveType::TrianglesList) => gl::TRIANGLES,
181                (None, PrimitiveType::TrianglesListAdjacency) => gl::TRIANGLES,
182                (None, PrimitiveType::TriangleStrip) => gl::TRIANGLES,
183                (None, PrimitiveType::TriangleStripAdjacency) => gl::TRIANGLES,
184                (None, PrimitiveType::TriangleFan) => gl::TRIANGLES,
185                (None, PrimitiveType::Patches { .. }) => unreachable!(),
186            };
187
188            ctxt.gl.BeginTransformFeedback(primitives);
189            ctxt.state.transform_feedback_enabled = Some(primitives);
190            ctxt.state.transform_feedback_paused = false;
191        }
192    }
193
194    #[inline]
195    fn unbind(ctxt: &mut CommandContext<'_>) {
196        if ctxt.state.transform_feedback_enabled.is_none() {
197            return;
198        }
199
200        unsafe {
201            ctxt.gl.EndTransformFeedback();
202            ctxt.state.transform_feedback_enabled = None;
203            ctxt.state.transform_feedback_paused = false;
204        }
205    }
206
207    fn ensure_buffer_out_of_transform_feedback(ctxt: &mut CommandContext<'_>, buffer: gl::types::GLuint) {
208        if ctxt.state.transform_feedback_enabled.is_none() {
209            return;
210        }
211
212        let mut needs_unbind = false;
213        for elem in ctxt.state.indexed_transform_feedback_buffer_bindings.iter_mut() {
214            if elem.buffer == buffer {
215                needs_unbind = true;
216                break;
217            }
218        }
219
220        if needs_unbind {
221            TransformFeedbackSession::unbind(ctxt);
222        }
223    }
224}
225
226impl<'a> Drop for TransformFeedbackSession<'a> {
227    #[inline]
228    fn drop(&mut self) {
229        // Since the session can be mem::forget'ed, the code in buffer/alloc.rs ensures that the
230        // buffer isn't used by transform feedback.
231        // However we end the session now anyway.
232        let mut ctxt = self.buffer.get_context().make_current();
233        Self::ensure_buffer_out_of_transform_feedback(&mut ctxt, self.buffer.get_id());
234    }
235}