1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
//! Framebuffers and render targets.
//!
//! A framebuffer is a GPU object which responsibility is to hold renders — i.e. a rasterized
//! scene. Currently, a framebuffer is the only support of rendering: you cannot render directly
//! into a texture. Instead, you have to use a [`Framebuffer`], which automatically handles for
//! you the texture creation, mapping and handling to receive renders.
//!
//! # Framebuffer creation
//!
//! Framebuffers are created via the [`Framebuffer::new`] method. Creating framebuffers require a
//! bit of explanation as it highly depends on _refinement types_. When you create a new
//! framebuffer, you are required to provide types to drive the creation and subsequent possible
//! operations you will be able to perform. Framebuffers have three important type variables:
//!
//! - `D`, a type representing a dimension and that must implement [`Dimensionable`]. That types
//!   gives information on what kind of sizes and offsets a framebuffer will operate on/with.
//! - `CS`, a _color slot_. Color slots are described in the [backend::color_slot] module.
//! - `DS`, a _depth slot_. Depth slots are described in the [backend::depth_slot] module.
//!
//! You are limited in which types you can choose — the list is visible as implementors of traits
//! in [backend::color_slot] and [backend::depth_slot].
//!
//! Once a [`Framebuffer`] is created, you can do basically two main operations on it:
//!
//! - Render things to it.
//! - Retreive color and depth slots to perform further operations.
//!
//! # Rendering to a framebuffer
//!
//! Rendering is pretty straightforward: you have to use a [`PipelineGate`] to initiate a render by
//! passing a reference on your [`Framebuffer`]. Once the pipeline is done, the [`Framebuffer`]
//! contains the result of the render.
//!
//! # Manipulating slots
//!
//! Slots’ types depend entirely on the types you choose in [`Framebuffer`]. The rule is that any
//! type that implements [`ColorSlot`] will be associated another type: that other type (in this
//! case, [`ColorSlot::ColorTextures`]) will be the type you can use to manipulate textures. The
//! same applies to [`DepthSlot`] with [`DepthSlot::DepthTexture`].
//!
//! In the case you don’t want a given slot, you can mute it with the unit type: [`()`], which will
//! effectively completely disable generating textures for that slot.
//!
//! You can access to the color slot via [`Framebuffer::color_slot`]. You can access to the depth
//! slot via [`Framebuffer::depth_slot`]. Once you get textures from the color slots, you can use
//! them as regular textures as input of next renders, for instance.
//!
//! ## Note on type generation
//!
//! Because framebuffers are highly subject to refinement typing, types are transformed at
//! compile-time by using the type-system to provide you with a good and comfortable experience.
//! If you use a single pixel format as color slot, for instance, you will get a single texture
//! (whose pixel format will be the same as the type variable you set). The dimension of the
//! texture will be set to the same as the framebuffer, too.
//!
//! Now if you use a tuple of pixel formats, you will get a tuple of textures, each having the
//! correct pixel format. That feature allows to generate complex types by using a _pretty simple_
//! input type. This is what we call _type constructors_ — type families in functional languages.
//! All this look a bit magical but the type-system ensures it’s total and not as magic as you
//! might think.
//!
//! [backend::color_slot]: crate::backend::color_slot
//! [backend::depth_slot]: crate::backend::depth_slot
//! [`PipelineGate`]: crate::pipeline::PipelineGate

use std::{error, fmt};

use crate::{
  backend::{
    color_slot::ColorSlot,
    depth_stencil_slot::DepthStencilSlot,
    framebuffer::{Framebuffer as FramebufferBackend, FramebufferBackBuffer},
  },
  context::GraphicsContext,
  texture::{Dim2, Dimensionable, Sampler, TextureError},
};

/// Typed framebuffers.
///
/// # Parametricity
///
/// - `B` is the backend type. It must implement [backend::framebuffer::Framebuffer].
/// - `D` is the dimension type. It must implement [`Dimensionable`].
/// - `CS` is the color slot type. It must implement [`ColorSlot`].
/// - `DS` is the depth slot type. It must implement [`DepthSlot`].
///
/// [backend::framebuffer::Framebuffer]: crate::backend::framebuffer::Framebuffer
pub struct Framebuffer<B, D, CS, DS>
where
  B: ?Sized + FramebufferBackend<D>,
  D: Dimensionable,
  CS: ColorSlot<B, D>,
  DS: DepthStencilSlot<B, D>,
{
  pub(crate) repr: B::FramebufferRepr,
  color_slot: CS::ColorTextures,
  depth_stencil_slot: DS::DepthStencilTexture,
}

impl<B, D, CS, DS> Framebuffer<B, D, CS, DS>
where
  B: ?Sized + FramebufferBackend<D>,
  D: Dimensionable,
  CS: ColorSlot<B, D>,
  DS: DepthStencilSlot<B, D>,
{
  /// Create a new [`Framebuffer`].
  ///
  /// The `mipmaps` argument allows to pass the number of _extra precision layers_ the texture will
  /// be created with. A precision layer contains the same image as the _base layer_ but in a lower
  /// resolution. Currently, the way the resolution is computed depends on the backend, but it is
  /// safe to assume that it’s logarithmic in base 2 — i.e. at each layer depth, the resolution
  /// is divided by 2 on each axis.
  ///
  /// # Errors
  ///
  /// It is possible that the [`Framebuffer`] cannot be created. The [`FramebufferError`] provides
  /// the reason why.
  ///
  /// # Notes
  ///
  /// You might be interested in the [`GraphicsContext::new_framebuffer`] function instead, which
  /// is the exact same function, but benefits from more type inference (based on `&mut C`).
  pub fn new<C>(
    ctx: &mut C,
    size: D::Size,
    mipmaps: usize,
    sampler: Sampler,
  ) -> Result<Self, FramebufferError>
  where
    C: GraphicsContext<Backend = B>,
  {
    unsafe {
      let mut repr = ctx
        .backend()
        .new_framebuffer::<CS, DS>(size, mipmaps, &sampler)?;
      let color_slot = CS::reify_color_textures(ctx, size, mipmaps, &sampler, &mut repr, 0)?;
      let depth_slot = DS::reify_depth_texture(ctx, size, mipmaps, &sampler, &mut repr)?;

      let repr = B::validate_framebuffer(repr)?;

      Ok(Framebuffer {
        repr,
        color_slot,
        depth_stencil_slot: depth_slot,
      })
    }
  }

  /// Get the size of the framebuffer.
  pub fn size(&self) -> D::Size {
    unsafe { B::framebuffer_size(&self.repr) }
  }

  /// Access the carried color slot.
  pub fn color_slot(&mut self) -> &mut CS::ColorTextures {
    &mut self.color_slot
  }

  /// Access the carried depth/stencil slot.
  pub fn depth_stencil_slot(&mut self) -> &mut DS::DepthStencilTexture {
    &mut self.depth_stencil_slot
  }

  /// Consume this framebuffer and return the carried slots.
  pub fn into_slots(self) -> (CS::ColorTextures, DS::DepthStencilTexture) {
    (self.color_slot, self.depth_stencil_slot)
  }

  /// Consume this framebuffer and return the carried [`ColorSlot`].
  pub fn into_color_slot(self) -> CS::ColorTextures {
    self.color_slot
  }

  /// Consume this framebuffer and return the carried [`DepthSlot`].
  pub fn into_depth_stencil_slot(self) -> DS::DepthStencilTexture {
    self.depth_stencil_slot
  }
}

impl<B> Framebuffer<B, Dim2, (), ()>
where
  B: ?Sized + FramebufferBackend<Dim2> + FramebufferBackBuffer,
{
  /// Get the _back buffer_ from the input context and the required resolution.
  pub fn back_buffer<C>(
    ctx: &mut C,
    size: <Dim2 as Dimensionable>::Size,
  ) -> Result<Self, FramebufferError>
  where
    C: GraphicsContext<Backend = B>,
  {
    unsafe { ctx.backend().back_buffer(size) }.map(|repr| Framebuffer {
      repr,
      color_slot: (),
      depth_stencil_slot: (),
    })
  }
}

/// Framebuffer error.
#[non_exhaustive]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FramebufferError {
  /// Cannot create the framebuffer on the GPU.
  CannotCreate,
  /// Texture error.
  ///
  /// This happen while creating / associating the color / depth slots.
  TextureError(TextureError),
  /// Incomplete error.
  ///
  /// This happens when finalizing the construction of the framebuffer.
  Incomplete(IncompleteReason),
  /// Cannot attach something to a framebuffer.
  UnsupportedAttachment,
}

impl FramebufferError {
  /// Cannot create the framebuffer on the GPU.
  pub fn cannot_create() -> Self {
    FramebufferError::CannotCreate
  }

  /// Texture error.
  pub fn texture_error(e: TextureError) -> Self {
    FramebufferError::TextureError(e)
  }

  /// Incomplete error.
  pub fn incomplete(e: IncompleteReason) -> Self {
    FramebufferError::Incomplete(e)
  }

  /// Cannot attach something to a framebuffer.
  pub fn unsupported_attachment() -> Self {
    FramebufferError::UnsupportedAttachment
  }
}

impl fmt::Display for FramebufferError {
  fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
    match *self {
      FramebufferError::CannotCreate => {
        f.write_str("cannot create the framebuffer on the GPU side")
      }

      FramebufferError::TextureError(ref e) => write!(f, "framebuffer texture error: {}", e),

      FramebufferError::Incomplete(ref e) => write!(f, "incomplete framebuffer: {}", e),

      FramebufferError::UnsupportedAttachment => f.write_str("unsupported framebuffer attachment"),
    }
  }
}

impl std::error::Error for FramebufferError {
  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
    match self {
      FramebufferError::CannotCreate => None,
      FramebufferError::TextureError(e) => Some(e),
      FramebufferError::Incomplete(e) => Some(e),
      FramebufferError::UnsupportedAttachment => None,
    }
  }
}

impl From<TextureError> for FramebufferError {
  fn from(e: TextureError) -> Self {
    FramebufferError::TextureError(e)
  }
}

impl From<IncompleteReason> for FramebufferError {
  fn from(e: IncompleteReason) -> Self {
    FramebufferError::Incomplete(e)
  }
}

/// Reason a framebuffer is incomplete.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum IncompleteReason {
  /// Incomplete framebuffer.
  Undefined,
  /// Incomplete attachment (color / depth).
  IncompleteAttachment,
  /// An attachment was missing.
  MissingAttachment,
  /// Incomplete draw buffer.
  IncompleteDrawBuffer,
  /// Incomplete read buffer.
  IncompleteReadBuffer,
  /// Unsupported framebuffer.
  Unsupported,
  /// Incomplete multisample configuration.
  IncompleteMultisample,
  /// Incomplete layer targets.
  IncompleteLayerTargets,
}

impl fmt::Display for IncompleteReason {
  fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
    match *self {
      IncompleteReason::Undefined => write!(f, "incomplete reason"),
      IncompleteReason::IncompleteAttachment => write!(f, "incomplete attachment"),
      IncompleteReason::MissingAttachment => write!(f, "missing attachment"),
      IncompleteReason::IncompleteDrawBuffer => write!(f, "incomplete draw buffer"),
      IncompleteReason::IncompleteReadBuffer => write!(f, "incomplete read buffer"),
      IncompleteReason::Unsupported => write!(f, "unsupported"),
      IncompleteReason::IncompleteMultisample => write!(f, "incomplete multisample"),
      IncompleteReason::IncompleteLayerTargets => write!(f, "incomplete layer targets"),
    }
  }
}

impl error::Error for IncompleteReason {}