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!{}, ¶ms).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}