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}