luminance/
pipeline.rs

1//! Graphics pipelines.
2//!
3//! Graphics pipelines are the means used to describe — and hence perform — renders. They
4//! provide a way to describe how resources should be shared and used to produce a single
5//! pixel frame.
6//!
7//! # Pipelines and graphs
8//!
9//! luminance has a very particular way of doing graphics. It represents a typical _graphics
10//! pipeline_ via a typed graph that is embedded into your code. Graphs are used to create a
11//! dependency between resources your GPU needs to have in order to perform a render. It might
12//! be weird at first but you’ll see how simple and easy it actually is. If you want to perform
13//! a simple draw call of a triangle, you need several resources:
14//!
15//! - A [`Tess`] that represents the triangle — assuming you don’t cheat with an attributeless
16//!   render ;). It holds three vertices.
17//! - A shader [`Program`], for shading the triangle with a constant color, for short and simple.
18//! - A [`Framebuffer`], to accept and hold the actual render.
19//! - A [`RenderState`], to state how the render should be performed.
20//! - And finally, a [`PipelineState`], which allows even more customization on how the pipeline
21//!   runs.
22//!
23//! The terminology used in luminance is as follows: a graphics pipeline is a graph in which nodes
24//! are called _gates_. A gate represents a particular resource exposed as a shared scarce resource
25//! for child, nested nodes. For instance, you can share framebuffer in a [`PipelineGate`]: all
26//! nodes that are children of that framebuffer node will then be able to render into that
27//! framebuffer.
28//!
29//! This design is well-typed: when you enter a given gate, the set of operations and children nodes
30//! you can create is directly dependent on the parent node.
31//!
32//! # PipelineGate and Pipeline
33//!
34//! A [`PipelineGate`] is the gate allowing to share a [`Framebuffer`].
35//!
36//! A [`PipelineGate`] represents a whole graphics pipeline as seen as just above. It is created by
37//! a [`GraphicsContext`] when you ask to create a pipeline gate. A [`PipelineGate`] is typically
38//! destroyed at the end of the current frame, but that’s not a general rule.
39//!
40//! Such an object gives you access, via the [`PipelineGate::pipeline`], to two other objects:
41//!
42//! - A [`ShadingGate`], explained below.
43//! - A [`Pipeline`].
44//!
45//! A [`Pipeline`] is a special object you can use to handle some specific scarce resources, such as
46//! _textures_ and _shader data. Those are treated a bit specifically on the backend, so you have to
47//! use the [`Pipeline`] interface to deal with them. Those scarce resources can be shared at different
48//! depth in the graphics pipeline, which is the reason why they are exposed via this [`Pipeline`]
49//! object, that you can pass down the graph.
50//!
51//! Creating a [`PipelineGate`] requires two resources: a [`Framebuffer`] to render to, and a
52//! [`PipelineState`], allowing to customize how the pipeline will perform renders at runtime. This gate
53//! will then do a couple of things on the backend, depending mainly on the [`PipelineState`] you pass.
54//! For instance, framebuffer clearing, sRGB conversion or scissor test is done at that level.
55//!
56//! # ShadingGate
57//!
58//! A [`ShadingGate`] is the gate allowing to share a shader [`Program`].
59//!
60//! When you enter a [`PipelineGate`], you’re handed a [`ShadingGate`]. A [`ShadingGate`] is an object
61//! that allows you to create _shader_ nodes in the graphics pipeline. You have no other way to go deeper
62//! in the graph.
63//!
64//! A shader [`Program`] is typically an object you create at initialization or at specific moment in time
65//! (i.e. you don’t create them on each frame, that would be super costly) that tells the GPU how vertices
66//! should be transformed; how primitives should be moved and generated, how tessellation occurs and
67//! how fragment (i.e. pixels) are computed / shaded — hence the name.
68//!
69//! At that level (i.e. in that closure), you are given three objects:
70//!
71//! - A [`RenderGate`], discussed below.
72//! - A [`ProgramInterface`], which has as type parameter the type of uniform your shader
73//!   [`Program`] defines.
74//! - The uniform interface the [`Program`] was made to work with.
75//!
76//! The [`ProgramInterface`] is the only way for you to access your _uniform interface_. More on
77//! this in the dedicated section. It also provides you with the [`ProgramInterface::query`]
78//! method, that allows you to perform _dynamic uniform lookup_.
79//!
80//! Once you have entered this gate, you know that everything nested will be shaded with the shared shader
81//! [`Program`].
82//!
83//! # RenderGate
84//!
85//! A [`RenderGate`] is the gate allowing to prepare renders by sharing [`RenderState`].
86//!
87//! A [`RenderGate`] is the second to last gate you will be handling. It allows you to create
88//! _render state_ nodes in your graph, creating a new level for you to render tessellations with
89//! an obvious, final gate: the [`TessGate`].
90//!
91//! The kind of object that node manipulates is [`RenderState`]. A [`RenderState`] — a bit like for
92//! [`PipelineGate`] with [`PipelineState`] — enables to customize how a render of a specific set
93//! of objects (i.e. tessellations) will occur. It’s a bit more specific to renders than pipelines and
94//! will allow customizing aspects like _blending_, _depth test_, _backface culling_, etc.
95//!
96//! # TessGate
97//!
98//! A [`TessGate`] is the final gate, allowing to share [`Tess`].
99//!
100//! The [`TessGate`] is the final gate you use in a graphics pipeline. It’s used to create _tessellation
101//! nodes_. Those are used to render actual [`Tess`]. You cannot go any deeper in the graph at that stage.
102//!
103//! [`TessGate`]s don’t immediately use [`Tess`] as inputs. They use [`TessView`]. That type is
104//! a simple immutable view into a [`Tess`]. It can be obtained from a [`Tess`] via the [`View`] trait or
105//! built explicitly.
106//!
107//! [`Tess`]: crate::tess::Tess
108//! [`Program`]: crate::shader::Program
109//! [`Framebuffer`]: crate::framebuffer::Framebuffer
110//! [`RenderState`]: crate::render_state::RenderState
111//! [`PipelineState`]: crate::pipeline::PipelineState
112//! [`ShadingGate`]: crate::shading_gate::ShadingGate
113//! [`RenderGate`]: crate::render_gate::RenderGate
114//! [`ProgramInterface`]: crate::shader::ProgramInterface
115//! [`ProgramInterface::query`]: crate::shader::ProgramInterface::query
116//! [`TessGate`]: crate::tess_gate::TessGate
117//! [`TessView`]: crate::tess::TessView
118//! [`View`]: crate::tess::View
119
120use std::{
121  error, fmt,
122  marker::PhantomData,
123  ops::{Deref, DerefMut},
124};
125
126use crate::{
127  backend::{
128    color_slot::ColorSlot,
129    depth_stencil_slot::DepthStencilSlot,
130    framebuffer::Framebuffer as FramebufferBackend,
131    pipeline::{Pipeline as PipelineBackend, PipelineBase, PipelineShaderData, PipelineTexture},
132  },
133  context::GraphicsContext,
134  framebuffer::Framebuffer,
135  pixel::Pixel,
136  scissor::ScissorRegion,
137  shader::ShaderData,
138  shading_gate::ShadingGate,
139  texture::{Dimensionable, Texture},
140};
141
142/// Possible errors that might occur in a graphics [`Pipeline`].
143#[non_exhaustive]
144#[derive(Debug, Eq, PartialEq)]
145pub enum PipelineError {}
146
147impl fmt::Display for PipelineError {
148  fn fmt(&self, _: &mut fmt::Formatter) -> Result<(), fmt::Error> {
149    Ok(())
150  }
151}
152
153impl error::Error for PipelineError {}
154
155/// The viewport being part of the [`PipelineState`].
156#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
157pub enum Viewport {
158  /// The whole viewport is used. The position and dimension of the viewport rectangle are
159  /// extracted from the framebuffer.
160  Whole,
161  /// The viewport is specific and the rectangle area is user-defined.
162  Specific {
163    /// The lower position on the X axis to start the viewport rectangle at.
164    x: u32,
165    /// The lower position on the Y axis to start the viewport rectangle at.
166    y: u32,
167    /// The width of the viewport.
168    width: u32,
169    /// The height of the viewport.
170    height: u32,
171  },
172}
173
174/// Various customization options for pipelines.
175#[non_exhaustive]
176#[derive(Clone, Debug)]
177pub struct PipelineState {
178  /// Color to use when clearing color buffers.
179  ///
180  /// Set this to `Some(color)` to use that color to clear the [`Framebuffer`] when running a [`PipelineGate`]. Set it
181  /// to `None` not to clear the framebuffer when running the [`PipelineGate`].
182  ///
183  /// An example of not setting the clear color is if you want to accumulate renders in a [`Framebuffer`] (for instance
184  /// for a paint-like application).
185  pub clear_color: Option<[f32; 4]>,
186
187  /// Depth value to use when clearing the depth buffer.
188  ///
189  /// Set this to `Some(depth)` to use that depth to clear the [`Framebuffer`] depth buffer.
190  pub clear_depth: Option<f32>,
191
192  /// Stencil value to use when clearing the stencil buffer.
193  ///
194  /// Set this to `Some(stencil)` to use that stencil to clear the [`Framebuffer`] stencil buffer.
195  pub clear_stencil: Option<i32>,
196
197  /// Viewport to use when rendering.
198  pub viewport: Viewport,
199
200  /// Whether [sRGB](https://en.wikipedia.org/wiki/SRGB) support should be enabled.
201  ///
202  /// When this is set to `true`, shader outputs that go in [`Framebuffer`] for each of the color slots have sRGB pixel
203  /// formats are assumed to be in the linear RGB color space. The pipeline will then convert that linear color outputs
204  /// to sRGB to be stored in the [`Framebuffer`].
205  ///
206  /// Typical examples are when you are rendering into an image that is to be displayed to on screen: the
207  /// [`Framebuffer`] can use sRGB color pixel formats and the shader doesn’t have to worry about converting from linear
208  /// color space into sRGB color space, as the pipeline will do that for you.
209  pub srgb_enabled: bool,
210
211  /// Whether to use scissor test when clearing buffers.
212  pub clear_scissor: Option<ScissorRegion>,
213}
214
215impl Default for PipelineState {
216  /// Default [`PipelineState`]:
217  ///
218  /// - Clear color is `Some([0., 0., 0., 1.])`.
219  /// - Depth value is `Some(1.)`.
220  /// - Stencil value is `Some(0)`.
221  /// - The viewport uses the whole framebuffer’s.
222  /// - sRGB encoding is disabled.
223  /// - No scissor test is performed.
224  fn default() -> Self {
225    PipelineState {
226      clear_color: Some([0., 0., 0., 1.]),
227      clear_depth: Some(1.),
228      clear_stencil: Some(0),
229      viewport: Viewport::Whole,
230      srgb_enabled: false,
231      clear_scissor: None,
232    }
233  }
234}
235
236impl PipelineState {
237  /// Create a default [`PipelineState`].
238  ///
239  /// See the documentation of the [`Default`] for further details.
240  pub fn new() -> Self {
241    Self::default()
242  }
243
244  /// Get the clear color, if any.
245  pub fn clear_color(&self) -> Option<&[f32; 4]> {
246    self.clear_color.as_ref()
247  }
248
249  /// Set the clear color.
250  pub fn set_clear_color(self, clear_color: impl Into<Option<[f32; 4]>>) -> Self {
251    Self {
252      clear_color: clear_color.into(),
253      ..self
254    }
255  }
256
257  /// Get the clear depth, if any.
258  pub fn clear_depth(&self) -> Option<f32> {
259    self.clear_depth
260  }
261
262  /// Set the clear depth.
263  pub fn set_clear_depth(self, clear_depth: impl Into<Option<f32>>) -> Self {
264    Self {
265      clear_depth: clear_depth.into(),
266      ..self
267    }
268  }
269
270  /// Get the clear stencil, if any.
271  pub fn clear_stencil(&self) -> Option<i32> {
272    self.clear_stencil
273  }
274
275  /// Set the clear stencil.
276  pub fn set_clear_stencil(self, clear_stencil: impl Into<Option<i32>>) -> Self {
277    Self {
278      clear_stencil: clear_stencil.into(),
279      ..self
280    }
281  }
282
283  /// Get the viewport.
284  pub fn viewport(&self) -> Viewport {
285    self.viewport
286  }
287
288  /// Set the viewport.
289  pub fn set_viewport(self, viewport: Viewport) -> Self {
290    Self { viewport, ..self }
291  }
292
293  /// Check whether sRGB linearization is enabled.
294  pub fn is_srgb_enabled(&self) -> bool {
295    self.srgb_enabled
296  }
297
298  /// Enable sRGB linearization.
299  pub fn enable_srgb(self, srgb_enabled: bool) -> Self {
300    Self {
301      srgb_enabled,
302      ..self
303    }
304  }
305
306  /// Get the scissor configuration, if any.
307  pub fn scissor(&self) -> &Option<ScissorRegion> {
308    &self.clear_scissor
309  }
310
311  /// Set the scissor configuration.
312  pub fn set_scissor(self, scissor: impl Into<Option<ScissorRegion>>) -> Self {
313    Self {
314      clear_scissor: scissor.into(),
315      ..self
316    }
317  }
318}
319
320/// A GPU pipeline handle.
321///
322/// A [`Pipeline`] is a special object that is provided as soon as one enters a [`PipelineGate`].
323/// It is used to dynamically modify the behavior of the running graphics pipeline. That includes,
324/// for instance, obtaining _bound resources_, like buffers and textures, for subsequent uses in
325/// shader stages.
326///
327/// # Parametricity
328///
329/// - `B` is the backend type. It must implement [`PipelineBase`].
330pub struct Pipeline<'a, B>
331where
332  B: ?Sized + PipelineBase,
333{
334  repr: B::PipelineRepr,
335  _phantom: PhantomData<&'a mut ()>,
336}
337
338impl<'a, B> Pipeline<'a, B>
339where
340  B: PipelineBase,
341{
342  /// Bind a texture.
343  ///
344  /// Once the texture is bound, the [`BoundTexture`] object has to be dropped / die in order to bind the texture again.
345  pub fn bind_texture<D, P>(
346    &'a self,
347    texture: &'a mut Texture<B, D, P>,
348  ) -> Result<BoundTexture<'a, B, D, P>, PipelineError>
349  where
350    B: PipelineTexture<D, P>,
351    D: Dimensionable,
352    P: Pixel,
353  {
354    unsafe {
355      B::bind_texture(&self.repr, &texture.repr).map(|repr| BoundTexture {
356        repr,
357        _phantom: PhantomData,
358      })
359    }
360  }
361
362  /// Bind a shader data.
363  ///
364  /// Once the shader data is bound, the [`BoundShaderData`] object has to be dropped / die in order to bind the shader
365  /// data again.
366  pub fn bind_shader_data<T>(
367    &'a self,
368    shader_data: &'a mut ShaderData<B, T>,
369  ) -> Result<BoundShaderData<'a, B, T>, PipelineError>
370  where
371    B: PipelineShaderData<T>,
372  {
373    unsafe {
374      B::bind_shader_data(&self.repr, &shader_data.repr).map(|repr| BoundShaderData {
375        repr,
376        _phantom: PhantomData,
377      })
378    }
379  }
380}
381
382/// Top-most node in a graphics pipeline.
383///
384/// [`PipelineGate`] nodes represent the “entry-points” of graphics pipelines. They are used
385/// with a [`Framebuffer`] to render to and a [`PipelineState`] to customize the overall behavior
386/// of the pipeline.
387///
388/// # Parametricity
389///
390/// - `B`, the backend type.
391pub struct PipelineGate<'a, B> {
392  backend: &'a mut B,
393}
394
395impl<'a, B> PipelineGate<'a, B> {
396  /// Create a new [`PipelineGate`].
397  pub fn new<C>(ctx: &'a mut C) -> Self
398  where
399    C: GraphicsContext<Backend = B>,
400  {
401    PipelineGate {
402      backend: ctx.backend(),
403    }
404  }
405
406  /// Enter a pipeline node.
407  ///
408  /// This method is the entry-point in a graphics pipeline. It takes a [`Framebuffer`] and a
409  /// [`PipelineState`] and a closure that allows to go deeper in the pipeline (i.e. resource
410  /// graph). The closure is passed a [`Pipeline`] for you to dynamically alter the pipeline and a
411  /// [`ShadingGate`] to enter shading nodes.
412  ///
413  /// # Errors
414  ///
415  /// [`PipelineError`] might be thrown for various reasons, depending on the backend you use.
416  /// However, this method doesn’t return [`PipelineError`] directly: instead, it returns
417  /// `E: From<PipelineError>`. This allows you to inject your own error type in the argument
418  /// closure, allowing for a grainer control of errors inside the pipeline.
419  pub fn pipeline<E, D, CS, DS, F>(
420    &mut self,
421    framebuffer: &Framebuffer<B, D, CS, DS>,
422    pipeline_state: &PipelineState,
423    f: F,
424  ) -> Render<E>
425  where
426    B: FramebufferBackend<D> + PipelineBackend<D>,
427    D: Dimensionable,
428    CS: ColorSlot<B, D>,
429    DS: DepthStencilSlot<B, D>,
430    F: for<'b> FnOnce(Pipeline<'b, B>, ShadingGate<'b, B>) -> Result<(), E>,
431    E: From<PipelineError>,
432  {
433    let render = || {
434      unsafe {
435        self
436          .backend
437          .start_pipeline(&framebuffer.repr, pipeline_state);
438      }
439
440      let pipeline = unsafe {
441        self.backend.new_pipeline().map(|repr| Pipeline {
442          repr,
443          _phantom: PhantomData,
444        })?
445      };
446
447      let shading_gate = ShadingGate {
448        backend: self.backend,
449      };
450
451      f(pipeline, shading_gate)
452    };
453
454    Render(render())
455  }
456}
457
458/// Output of a [`PipelineGate`].
459///
460/// This type is used as a proxy over `Result<(), E>`, which it defers to. It is needed so that
461/// you can seamlessly call the [`assume`] method
462///
463/// [`assume`]: crate::pipeline::Render::assume
464pub struct Render<E>(Result<(), E>);
465
466impl<E> Render<E> {
467  /// Turn a [`Render`] into a [`Result`].
468  #[inline]
469  pub fn into_result(self) -> Result<(), E> {
470    self.0
471  }
472}
473
474impl Render<PipelineError> {
475  /// Assume the error type is [`PipelineError`].
476  ///
477  /// Most of the time, users will not provide their own error types for pipelines. Rust doesn’t
478  /// have default type parameters for methods, so this function is needed to inform the type
479  /// system to default the error type to [`PipelineError`].
480  #[inline]
481  pub fn assume(self) -> Self {
482    self
483  }
484}
485
486impl<E> From<Render<E>> for Result<(), E> {
487  fn from(render: Render<E>) -> Self {
488    render.0
489  }
490}
491
492impl<E> Deref for Render<E> {
493  type Target = Result<(), E>;
494
495  fn deref(&self) -> &Self::Target {
496    &self.0
497  }
498}
499
500impl<E> DerefMut for Render<E> {
501  fn deref_mut(&mut self) -> &mut Self::Target {
502    &mut self.0
503  }
504}
505
506/// Opaque shader data binding.
507///
508/// This type represents a bound [`ShaderData`] via [`BoundShaderData`]. It can be used along with a [`Uniform`] to
509/// customize a shader’s behavior.
510///
511/// # Parametricity
512///
513/// - `T` is the type of the carried item by the [`ShaderData`].
514///
515/// # Notes
516///
517/// You shouldn’t try to do store / cache or do anything special with that value. Consider it an opaque object.
518///
519/// [`Uniform`]: crate::shader::Uniform
520#[derive(Debug)]
521pub struct ShaderDataBinding<T> {
522  binding: u32,
523  _phantom: PhantomData<*const T>,
524}
525
526impl<T> ShaderDataBinding<T> {
527  /// Access the underlying binding value.
528  ///
529  /// # Notes
530  ///
531  /// That value shouldn’t be read nor store, as it’s only meaningful for backend implementations.
532  pub fn binding(self) -> u32 {
533    self.binding
534  }
535}
536
537/// A _bound_ [`ShaderData`].
538///
539/// # Parametricity
540///
541/// - `B` is the backend type. It must implement [`ShaderData`](crate::backend::shader::ShaderData).
542/// - `T` is the carried item type.
543///
544/// # Notes
545///
546/// Once a [`ShaderData`] is bound, it can be used and passed around to shaders. In order to do so, you will need to
547/// pass a [`ShaderDataBinding`] to your [`ProgramInterface`]. That value is unique to each [`BoundShaderData`] and
548/// should always be asked — you shouldn’t cache them, for instance.
549///
550/// Getting a [`ShaderDataBinding`] is a cheap operation and is performed via the [`BoundShaderData::binding`] method.
551///
552/// [`ProgramInterface`]: crate::shader::ProgramInterface
553pub struct BoundShaderData<'a, B, T>
554where
555  B: PipelineShaderData<T>,
556{
557  pub(crate) repr: B::BoundShaderDataRepr,
558  _phantom: PhantomData<&'a ()>,
559}
560
561impl<'a, B, T> BoundShaderData<'a, B, T>
562where
563  B: PipelineShaderData<T>,
564{
565  /// Obtain a [`ShaderDataBinding`] object that can be used to refer to this bound shader data in shader stages.
566  ///
567  /// # Notes
568  ///
569  /// You shouldn’t try to do store / cache or do anything special with that value. Consider it
570  /// an opaque object.
571  pub fn binding(&self) -> ShaderDataBinding<T> {
572    let binding = unsafe { B::shader_data_binding(&self.repr) };
573    ShaderDataBinding {
574      binding,
575      _phantom: PhantomData,
576    }
577  }
578}
579
580/// Opaque texture binding.
581///
582/// This type represents a bound [`Texture`] via [`BoundTexture`]. It can be used along with a
583/// [`Uniform`] to customize a shader’s behavior.
584///
585/// # Parametricity
586///
587/// - `D` is the dimension of the original texture. It must implement [`Dimensionable`] in most
588///   useful methods.
589/// - `S` is the sampler type. It must implement [`SamplerType`] in most useful methods.
590///
591/// # Notes
592///
593/// You shouldn’t try to do store / cache or do anything special with that value. Consider it
594/// an opaque object.
595///
596/// [`Uniform`]: crate::shader::Uniform
597/// [`SamplerType`]: crate::pixel::SamplerType
598#[derive(Debug)]
599pub struct TextureBinding<D, S> {
600  binding: u32,
601  _phantom: PhantomData<*const (D, S)>,
602}
603
604impl<D, S> TextureBinding<D, S> {
605  /// Access the underlying binding value.
606  ///
607  /// # Notes
608  ///
609  /// That value shouldn’t be read nor store, as it’s only meaningful for backend implementations.
610  pub fn binding(self) -> u32 {
611    self.binding
612  }
613}
614
615/// A _bound_ [`Texture`].
616///
617/// # Parametricity
618///
619/// - `B` is the backend type. It must implement [`PipelineTexture`].
620/// - `D` is the dimension. It must implement [`Dimensionable`].
621/// - `P` is the pixel type. It must implement [`Pixel`].
622///
623/// # Notes
624///
625/// Once a [`Texture`] is bound, it can be used and passed around to shaders. In order to do so,
626/// you will need to pass a [`TextureBinding`] to your [`ProgramInterface`]. That value is unique
627/// to each [`BoundTexture`] and should always be asked — you shouldn’t cache them, for instance.
628///
629/// Getting a [`TextureBinding`] is a cheap operation and is performed via the
630/// [`BoundTexture::binding`] method.
631///
632/// [`ProgramInterface`]: crate::shader::ProgramInterface
633pub struct BoundTexture<'a, B, D, P>
634where
635  B: PipelineTexture<D, P>,
636  D: Dimensionable,
637  P: Pixel,
638{
639  pub(crate) repr: B::BoundTextureRepr,
640  _phantom: PhantomData<&'a ()>,
641}
642
643impl<'a, B, D, P> BoundTexture<'a, B, D, P>
644where
645  B: PipelineTexture<D, P>,
646  D: Dimensionable,
647  P: Pixel,
648{
649  /// Obtain a [`TextureBinding`] object that can be used to refer to this bound texture in shader
650  /// stages.
651  ///
652  /// # Notes
653  ///
654  /// You shouldn’t try to do store / cache or do anything special with that value. Consider it
655  /// an opaque object.
656  pub fn binding(&self) -> TextureBinding<D, P::SamplerType> {
657    let binding = unsafe { B::texture_binding(&self.repr) };
658    TextureBinding {
659      binding,
660      _phantom: PhantomData,
661    }
662  }
663}