pixelpwnr_render/renderer/
stats_renderer.rs

1use draw_state::state::{Blend, BlendChannel, BlendValue, Equation, Factor};
2use gfx_glutin::WindowUpdateExt;
3use glutin::{PossiblyCurrent, WindowedContext};
4use parking_lot::Mutex;
5use std::cmp::max;
6use std::iter::Extend;
7use std::sync::Arc;
8
9use gfx::format::RenderFormat;
10use gfx::handle::{DepthStencilView, RenderTargetView};
11use gfx::traits::FactoryExt;
12use gfx::{self, *};
13
14use gfx_text::{Error as GfxTextError, HorizontalAnchor, Renderer as TextRenderer, VerticalAnchor};
15use old_school_gfx_glutin_ext as gfx_glutin;
16
17use super::ref_values::RefValuesWrapper;
18use crate::primitive::create_quad;
19use crate::renderer::{ColorFormat, DepthFormat, R};
20use crate::vertex::*;
21
22/// White color definition with 4 channels.
23const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
24
25const BLEND: Blend = Blend {
26    color: BlendChannel {
27        equation: Equation::Add,
28        source: Factor::Zero,
29        destination: Factor::ZeroPlus(BlendValue::ConstAlpha),
30    },
31    alpha: BlendChannel {
32        equation: Equation::Add,
33        source: Factor::Zero,
34        destination: Factor::Zero,
35    },
36};
37
38// Screen shader data pipeline
39gfx_defines! {
40    pipeline bg_pipe {
41        vbuf: gfx::VertexBuffer<Vertex> = (),
42        out: gfx::BlendTarget<ColorFormat> = (
43            "Target0",
44            gfx::state::ColorMask::all(),
45            BLEND,
46        ),
47        ref_values: RefValuesWrapper = RefValuesWrapper::new(),
48    }
49}
50
51pub struct StatsRenderer<F: Factory<R> + Clone> {
52    /// The corner to render the stats in.
53    #[allow(unused)]
54    corner: Corner,
55
56    /// The rendering offset.
57    offset: (u32, u32),
58
59    /// The rendering padding.
60    padding: i32,
61
62    /// The column spacing amount.
63    col_spacing: i32,
64
65    /// The text to render.
66    text: Arc<Mutex<String>>,
67
68    /// The text renderer.
69    renderer: Option<TextRenderer<R, F>>,
70
71    /// A factory to build new model instances if required.
72    factory: Option<F>,
73
74    /// The dimensions the rendering window has, used for text placement.
75    window_dimensions: Option<(f32, f32)>,
76
77    /// The depth stencil for background rendering.
78    bg_depth: Option<DepthStencilView<R, DepthFormat>>,
79
80    /// The PSO for background rendering.
81    bg_pso: Option<PipelineState<R, bg_pipe::Meta>>,
82
83    /// The vertex slice for the background quad.
84    bg_slice: Option<Slice<R>>,
85
86    /// The background rendering data.
87    bg_data: Option<bg_pipe::Data<R>>,
88}
89
90impl<F: Factory<R> + Clone> StatsRenderer<F> {
91    /// Construct a new stats renderer.
92    pub fn new(corner: Corner) -> Self {
93        StatsRenderer {
94            corner,
95            offset: (0, 0),
96            padding: 0,
97            col_spacing: 0,
98            text: Arc::new(Mutex::new(String::new())),
99            renderer: None,
100            factory: None,
101            window_dimensions: None,
102            bg_depth: None,
103            bg_pso: None,
104            bg_slice: None,
105            bg_data: None,
106        }
107    }
108
109    /// Initialize the renderer.
110    pub fn init(
111        &mut self,
112        mut factory: F,
113        window_dimensions: (f32, f32),
114        main_color: RenderTargetView<R, ColorFormat>,
115        main_depth: DepthStencilView<R, DepthFormat>,
116        font_size: u8,
117        offset: (u32, u32),
118        padding: i32,
119        col_spacing: i32,
120    ) -> Result<(), GfxTextError> {
121        // Set the window dimensions, offset and padding
122        self.window_dimensions = Some(window_dimensions);
123        self.offset = offset;
124        self.padding = padding;
125        self.col_spacing = col_spacing;
126
127        // Build the text renderer
128        self.renderer = Some(
129            gfx_text::new(factory.clone())
130                .with_size(font_size)
131                .build()?,
132        );
133
134        // Create a shader pipeline for the stats background
135        self.bg_pso = Some(
136            factory
137                .create_pipeline_simple(
138                    include_bytes!(concat!(
139                        env!("CARGO_MANIFEST_DIR"),
140                        "/shaders/stats_bg.glslv"
141                    )),
142                    include_bytes!(concat!(
143                        env!("CARGO_MANIFEST_DIR"),
144                        "/shaders/stats_bg.glslf"
145                    )),
146                    bg_pipe::new(),
147                )
148                .unwrap(),
149        );
150
151        // Create a background plane
152        let bg_plane = create_quad((-1f32, 0f32), (0.2f32, 0.95f32));
153        let (vertex_buffer, slice) = bg_plane.create_vertex_buffer(&mut factory);
154
155        // Store the slice, and build the background pipe data
156        self.bg_slice = Some(slice);
157        self.bg_data = Some(bg_pipe::Data {
158            vbuf: vertex_buffer,
159            out: main_color,
160            ref_values: (),
161        });
162
163        // Set the factory and depth stencil
164        self.factory = Some(factory);
165        self.bg_depth = Some(main_depth);
166
167        Ok(())
168    }
169
170    /// Get a reference to the text that is rendered.
171    pub fn text(&self) -> Arc<Mutex<String>> {
172        self.text.clone()
173    }
174
175    /// Check whether any text is set to render.
176    pub fn has_text(&self) -> bool {
177        self.text.lock().trim().is_empty()
178    }
179
180    /// Set the text that is rendered.
181    pub fn set_text(&self, text: String) {
182        *self.text.lock() = text;
183    }
184
185    /// Draw the renderer to the given context.
186    ///
187    /// This method should be called once each render loop iteration,
188    /// to properly draw the stats.
189    pub fn draw<C: CommandBuffer<R>, T: RenderFormat>(
190        &mut self,
191        encoder: &mut Encoder<R, C>,
192        target: &RenderTargetView<R, T>,
193    ) -> Result<(), GfxTextError> {
194        // Do not draw if no renderer is available yet,
195        // or if there is no text to draw
196        if self.renderer.is_none() || self.has_text() {
197            return Ok(());
198        }
199
200        // Unwrap the renderer
201        let renderer = self.renderer.as_mut().unwrap();
202
203        // Draw formatted text on the text scene
204        let bounds = Self::scene_draw_format(
205            self.offset,
206            self.padding,
207            self.col_spacing,
208            renderer,
209            &self.text.lock(),
210        );
211
212        // Draw the background quad, if there are some bounds
213        if bounds != (0f32, 0f32) {
214            if self.bg_slice.is_some() && self.bg_pso.is_some() && self.bg_data.is_some() {
215                // Get the window dimensions
216                let win = self.window_dimensions.unwrap();
217
218                // Determine the position and size of the background quad
219                let w = bounds.0 / win.0 * 2f32;
220                let h = bounds.1 / win.1 * 2f32;
221                let x = -1f32 + self.offset.0 as f32 / win.0 * 2f32;
222                let y = 1f32 - self.offset.1 as f32 / win.1 * 2f32 - h;
223
224                // Rebuild the vertex buffer and slice data
225                let (vertex_buffer, slice) = create_quad((x, y), (w, h))
226                    .create_vertex_buffer(self.factory.as_mut().unwrap());
227
228                *self.bg_slice.as_mut().unwrap() = slice;
229                self.bg_data.as_mut().unwrap().vbuf = vertex_buffer;
230
231                encoder.draw(
232                    self.bg_slice.as_ref().unwrap(),
233                    self.bg_pso.as_ref().unwrap(),
234                    self.bg_data.as_ref().unwrap(),
235                );
236            }
237        }
238
239        // Draw the text scene
240        renderer.draw(encoder, target)
241    }
242
243    /// Draw text in a formatted way.
244    /// This method allows a string to be rendered as table.
245    /// Rows are separated by `\n`, while columns are separated by `\t`.
246    ///
247    /// The drawing bounds are returned.
248    fn scene_draw_format(
249        pos: (u32, u32),
250        padding: i32,
251        col_spacing: i32,
252        renderer: &mut TextRenderer<R, F>,
253        text: &str,
254    ) -> (f32, f32) {
255        Self::scene_draw_table(
256            pos,
257            padding,
258            col_spacing,
259            renderer,
260            text.split("\n")
261                .map(|row| row.split("\t").collect())
262                .collect(),
263        )
264    }
265
266    /// Draw a table of text with the given `renderer`.
267    /// The text table to draw should be defined in the `text` vectors:
268    /// `Rows(Columns)`
269    ///
270    /// The drawing bounds are returned.
271    fn scene_draw_table(
272        pos: (u32, u32),
273        padding: i32,
274        col_spacing: i32,
275        renderer: &mut TextRenderer<R, F>,
276        text: Vec<Vec<&str>>,
277    ) -> (f32, f32) {
278        // Build a table of text bounds
279        let bounds: Vec<Vec<(i32, i32)>> = text
280            .iter()
281            .map(|col| col.iter().map(|text| renderer.measure(text)).collect())
282            .collect();
283
284        // Find the maximum height for each row
285        let rows_max: Vec<i32> = bounds
286            .iter()
287            .map(|col| col.iter().map(|size| size.1).max().unwrap_or(0))
288            .collect();
289
290        // Find the maximum width for each column
291        let mut cols_max: Vec<i32> = bounds
292            .iter()
293            .map(|row| row.iter().map(|size| size.0).collect())
294            .fold(Vec::new(), |acc: Vec<i32>, row: Vec<i32>| {
295                // Iterate over widths in acc and row,
296                // select the largest one
297                let mut out: Vec<i32> = acc
298                    .iter()
299                    .zip(row.iter())
300                    .map(|(a, b)| max(*a, *b))
301                    .collect();
302
303                // Extend the output if there are any widths left
304                let out_len = out.len();
305                if out_len < acc.len() || out_len < row.len() {
306                    out.extend(acc.iter().skip(out_len));
307                    out.extend(row.iter().skip(out_len));
308                }
309
310                out
311            });
312        cols_max
313            .iter_mut()
314            .rev()
315            .skip(1)
316            .map(|width| *width += col_spacing)
317            .count();
318
319        // Render each text
320        for (row, text) in text.iter().enumerate() {
321            for (col, text) in text.iter().enumerate() {
322                // Find the coordinate to use
323                let (mut x, mut y): (i32, i32) = (
324                    cols_max.iter().take(col).sum::<i32>(),
325                    rows_max.iter().take(row).sum::<i32>(),
326                );
327
328                // Add the offset and additional spacing
329                x += pos.0 as i32 + padding;
330                y += pos.1 as i32 + padding;
331
332                // Render the text
333                renderer.add_anchored(
334                    text,
335                    [x, y],
336                    HorizontalAnchor::Left,
337                    VerticalAnchor::Top,
338                    WHITE,
339                );
340            }
341        }
342
343        // Find the total width and height, return it
344        (
345            cols_max.iter().sum::<i32>() as f32 + padding as f32 * 2f32,
346            rows_max.iter().sum::<i32>() as f32 + padding as f32 * 2f32,
347        )
348    }
349
350    /// Update the stats rendering view, and the window dimensions.
351    /// This should be called when the GL rendering window is resized.
352    // TODO: also update the text view here
353    pub fn update_views(
354        &mut self,
355        window: &WindowedContext<PossiblyCurrent>,
356        dimensions: (f32, f32),
357    ) {
358        // Update the views
359        if let Some(data) = self.bg_data.as_mut() {
360            window.update_gfx(&mut data.out, self.bg_depth.as_mut().unwrap());
361        }
362
363        // Update the window dimensions
364        self.window_dimensions = Some(dimensions);
365    }
366}
367
368/// The corner to render stats in.
369pub enum Corner {
370    /// The top left corner of the screen.
371    TopLeft,
372
373    /// The top right corner of the screen.
374    TopRight,
375
376    /// The bottom left corner of the screen.
377    BottomLeft,
378
379    /// The bottom right corner of the screen.
380    BottomRight,
381}