fast3d_glium_renderer/
glium_renderer.rs

1use std::borrow::Cow;
2
3use crate::opengl_program::ShaderVersion;
4use fast3d::output::{ShaderConfig, ShaderId};
5use fast3d::{
6    output::{
7        gfx::{BlendComponent, BlendFactor, BlendOperation, BlendState, CompareFunction, Face},
8        models::{OutputFogParams, OutputSampler, OutputStencil, OutputTexture, OutputUniforms},
9    },
10    RenderData,
11};
12use fast3d_gbi::defines::WrapMode;
13use glam::Vec4Swizzles;
14use glium::buffer::{Buffer, BufferAny, BufferMode, BufferType};
15use glium::{
16    draw_parameters::{DepthClamp, PolygonOffset},
17    implement_uniform_block, implement_vertex,
18    index::{NoIndices, PrimitiveType},
19    program::ProgramCreationInput,
20    texture::{RawImage2d, Texture2d},
21    uniforms::{
22        MagnifySamplerFilter, MinifySamplerFilter, SamplerBehavior, SamplerWrapFunction,
23        UniformValue, Uniforms,
24    },
25    vertex::{AttributeType, VertexBufferAny},
26    BackfaceCullingMode, BlendingFunction, DepthTest, Display, DrawParameters, Frame,
27    LinearBlendingFactor, Program, Surface, VertexBuffer,
28};
29
30use super::opengl_program::OpenGLProgram;
31
32struct TextureData {
33    texture: Texture2d,
34    sampler: Option<SamplerBehavior>,
35}
36
37impl TextureData {
38    pub fn new(texture: Texture2d) -> Self {
39        Self {
40            texture,
41            sampler: None,
42        }
43    }
44}
45
46#[repr(C)]
47#[derive(Copy, Clone)]
48struct VertexUniforms {
49    screen_size: [f32; 2],
50    _padding: [f32; 2],
51    projection: [[f32; 4]; 4],
52}
53
54impl VertexUniforms {
55    pub fn new(screen_size: [f32; 2], projection: [[f32; 4]; 4]) -> Self {
56        Self {
57            screen_size,
58            _padding: [0.0; 2],
59            projection,
60        }
61    }
62}
63
64implement_uniform_block!(VertexUniforms, screen_size, projection);
65
66#[repr(C)]
67#[derive(Copy, Clone)]
68struct VertexWithFogUniforms {
69    screen_size: [f32; 2],
70    _padding: [f32; 2],
71    projection: [[f32; 4]; 4],
72    fog_multiplier: f32,
73    fog_offset: f32,
74}
75
76impl VertexWithFogUniforms {
77    pub fn new(
78        screen_size: [f32; 2],
79        projection: [[f32; 4]; 4],
80        fog_multiplier: f32,
81        fog_offset: f32,
82    ) -> Self {
83        Self {
84            screen_size,
85            _padding: [0.0; 2],
86            projection,
87            fog_multiplier,
88            fog_offset,
89        }
90    }
91}
92
93implement_uniform_block!(
94    VertexWithFogUniforms,
95    screen_size,
96    projection,
97    fog_multiplier,
98    fog_offset
99);
100
101#[repr(C)]
102#[derive(Copy, Clone)]
103struct BlendUniforms {
104    blend_color: [f32; 4],
105}
106
107implement_uniform_block!(BlendUniforms, blend_color);
108
109#[repr(C)]
110#[derive(Copy, Clone)]
111struct BlendWithFogUniforms {
112    blend_color: [f32; 4],
113    fog_color: [f32; 3],
114    _padding: f32,
115}
116
117implement_uniform_block!(BlendWithFogUniforms, blend_color, fog_color);
118
119#[repr(C)]
120#[derive(Copy, Clone)]
121struct CombineUniforms {
122    prim_color: [f32; 4],
123    env_color: [f32; 4],
124    key_center: [f32; 3],
125    _padding: f32,
126    key_scale: [f32; 3],
127    _padding2: f32,
128    prim_lod_frac: f32,
129    uk4: f32,
130    uk5: f32,
131}
132
133implement_uniform_block!(
134    CombineUniforms,
135    prim_color,
136    env_color,
137    key_center,
138    key_scale,
139    prim_lod_frac,
140    uk4,
141    uk5
142);
143
144#[repr(C)]
145#[derive(Copy, Clone)]
146struct FrameUniforms {
147    frame_count: i32,
148    frame_height: i32,
149}
150
151implement_uniform_block!(FrameUniforms, frame_count, frame_height);
152
153#[repr(C)]
154#[derive(Copy, Clone)]
155struct Vertex {
156    position: [f32; 4],
157    color: [f32; 4],
158}
159
160implement_vertex!(Vertex, position location(0), color location(1));
161
162#[repr(C)]
163#[derive(Copy, Clone)]
164struct VertexWithTexture {
165    position: [f32; 4],
166    color: [f32; 4],
167    tex_coord: [f32; 2],
168}
169
170implement_vertex!(VertexWithTexture, position location(0), color location(1), tex_coord location(2));
171
172#[derive(Default)]
173struct UniformVec<'a, 'b> {
174    pub uniforms: Vec<(&'a str, UniformValue<'b>)>,
175}
176
177impl Uniforms for UniformVec<'_, '_> {
178    fn visit_values<'a, F: FnMut(&str, UniformValue<'a>)>(&'a self, mut func: F) {
179        for uniform in &self.uniforms {
180            func(uniform.0, uniform.1);
181        }
182    }
183}
184
185pub struct GliumRenderer<'a> {
186    pub shader_cache: rustc_hash::FxHashMap<ShaderId, OpenGLProgram<Program>>,
187    current_shader: Option<ShaderId>,
188
189    textures: Vec<TextureData>,
190    active_texture: usize,
191    current_texture_ids: [usize; 2],
192
193    frame_count: i32,
194    current_height: i32,
195    screen_size: [u32; 2],
196
197    draw_params: DrawParameters<'a>,
198}
199
200fn blend_component_to_glium(component: BlendComponent) -> BlendingFunction {
201    match component.operation {
202        BlendOperation::Add => BlendingFunction::Addition {
203            source: blend_factor_to_glium(component.src_factor),
204            destination: blend_factor_to_glium(component.dst_factor),
205        },
206        BlendOperation::Subtract => BlendingFunction::Subtraction {
207            source: blend_factor_to_glium(component.src_factor),
208            destination: blend_factor_to_glium(component.dst_factor),
209        },
210        BlendOperation::ReverseSubtract => BlendingFunction::ReverseSubtraction {
211            source: blend_factor_to_glium(component.src_factor),
212            destination: blend_factor_to_glium(component.dst_factor),
213        },
214        BlendOperation::Min => BlendingFunction::Min,
215        BlendOperation::Max => BlendingFunction::Max,
216    }
217}
218
219fn blend_factor_to_glium(factor: BlendFactor) -> LinearBlendingFactor {
220    match factor {
221        BlendFactor::Zero => LinearBlendingFactor::Zero,
222        BlendFactor::One => LinearBlendingFactor::One,
223        BlendFactor::Src => LinearBlendingFactor::SourceColor,
224        BlendFactor::OneMinusSrc => LinearBlendingFactor::OneMinusSourceColor,
225        BlendFactor::SrcAlpha => LinearBlendingFactor::SourceAlpha,
226        BlendFactor::OneMinusSrcAlpha => LinearBlendingFactor::OneMinusSourceAlpha,
227        BlendFactor::Dst => LinearBlendingFactor::DestinationColor,
228        BlendFactor::OneMinusDst => LinearBlendingFactor::OneMinusDestinationColor,
229        BlendFactor::DstAlpha => LinearBlendingFactor::DestinationAlpha,
230        BlendFactor::OneMinusDstAlpha => LinearBlendingFactor::OneMinusDestinationAlpha,
231        BlendFactor::SrcAlphaSaturated => LinearBlendingFactor::SourceAlphaSaturate,
232        BlendFactor::Constant => LinearBlendingFactor::ConstantColor,
233        BlendFactor::OneMinusConstant => LinearBlendingFactor::OneMinusConstantColor,
234    }
235}
236
237fn clamp_to_glium(clamp: WrapMode) -> SamplerWrapFunction {
238    if clamp == WrapMode::Clamp {
239        return SamplerWrapFunction::Clamp;
240    } else if clamp == WrapMode::MirrorRepeat {
241        return SamplerWrapFunction::Mirror;
242    }
243
244    SamplerWrapFunction::Repeat
245}
246
247impl<'a> GliumRenderer<'a> {
248    pub fn new(screen_size: [u32; 2]) -> Self {
249        Self {
250            shader_cache: rustc_hash::FxHashMap::default(),
251            current_shader: None,
252
253            textures: Vec::new(),
254            active_texture: 0,
255            current_texture_ids: [0; 2],
256
257            frame_count: 0,
258            current_height: 0,
259            screen_size,
260
261            draw_params: DrawParameters {
262                ..Default::default()
263            },
264        }
265    }
266
267    /// Starts a new frame.
268    /// Should be called before any drawing is done.
269    pub fn start_frame(&mut self, target: &mut Frame) {
270        self.frame_count += 1;
271
272        target.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
273
274        self.draw_params = DrawParameters {
275            ..Default::default()
276        };
277    }
278
279    /// Sets the screen size for rendering.
280    ///
281    /// The easiest way of doing this is to take every [`WindowEvent::Resized`]
282    /// that is received and pass its [`dpi::PhysicalSize`] into this function.
283    pub fn resize(&mut self, screen_size: [u32; 2]) {
284        self.screen_size = screen_size;
285    }
286
287    /// Render the contents of a given [`RenderData`] to the screen.
288    pub fn render_rcp_output(
289        &mut self,
290        render_data: &mut RenderData,
291        display: &Display,
292        frame: &mut Frame,
293    ) {
294        // omit the last draw call, because we know we that's an extra from the last flush
295        // for draw_call in &self.rcp_output.draw_calls[..self.rcp_output.draw_calls.len() - 1] {
296        for draw_call in render_data
297            .draw_calls
298            .iter()
299            .take(render_data.draw_calls.len() - 1)
300        {
301            assert!(!draw_call.vbo.vbo.is_empty());
302
303            self.set_cull_mode(draw_call.cull_mode);
304            self.set_depth_stencil_params(draw_call.stencil);
305            self.set_blend_state(draw_call.blend_state);
306            self.set_viewport(&draw_call.viewport);
307            self.set_scissor(draw_call.scissor);
308
309            // select the shader program
310            self.select_program(display, draw_call.shader_id, draw_call.shader_config);
311
312            // loop through textures and bind them
313            for (index, hash) in draw_call.texture_indices.iter().enumerate() {
314                if let Some(hash) = hash {
315                    let texture = render_data.texture_cache.get_mut(*hash).unwrap();
316                    self.bind_texture(display, index, texture);
317                }
318            }
319
320            // loop through samplers and bind them
321            for (index, sampler) in draw_call.samplers.iter().enumerate() {
322                if let Some(sampler) = sampler {
323                    self.bind_sampler(index, sampler);
324                }
325            }
326
327            // draw triangles
328            self.draw_triangles(
329                display,
330                frame,
331                draw_call.projection_matrix,
332                &draw_call.fog,
333                &draw_call.vbo.vbo,
334                draw_call.vbo.num_tris,
335                &draw_call.uniforms,
336            );
337        }
338    }
339
340    // MARK: - Helpers
341
342    fn set_cull_mode(&mut self, cull_mode: Option<Face>) {
343        self.draw_params.backface_culling = match cull_mode {
344            Some(Face::Front) => BackfaceCullingMode::CullCounterClockwise,
345            Some(Face::Back) => BackfaceCullingMode::CullClockwise,
346            None => BackfaceCullingMode::CullingDisabled,
347        }
348    }
349
350    fn set_depth_stencil_params(&mut self, params: Option<OutputStencil>) {
351        self.draw_params.depth = if let Some(params) = params {
352            glium::Depth {
353                test: match params.depth_compare {
354                    CompareFunction::Never => DepthTest::Ignore,
355                    CompareFunction::Less => DepthTest::IfLess,
356                    CompareFunction::Equal => DepthTest::IfEqual,
357                    CompareFunction::LessEqual => DepthTest::IfLessOrEqual,
358                    CompareFunction::Greater => DepthTest::IfMore,
359                    CompareFunction::NotEqual => DepthTest::IfNotEqual,
360                    CompareFunction::GreaterEqual => DepthTest::IfMoreOrEqual,
361                    CompareFunction::Always => DepthTest::Overwrite,
362                },
363                write: params.depth_write_enabled,
364                clamp: DepthClamp::Clamp,
365                ..Default::default()
366            }
367        } else {
368            glium::Depth {
369                clamp: DepthClamp::Clamp,
370                ..Default::default()
371            }
372        };
373
374        self.draw_params.polygon_offset = if let Some(params) = params {
375            PolygonOffset {
376                factor: if params.polygon_offset { -2.0 } else { 0.0 },
377                units: if params.polygon_offset { 2.0 } else { 0.0 },
378                fill: true,
379                ..Default::default()
380            }
381        } else {
382            PolygonOffset {
383                ..Default::default()
384            }
385        };
386    }
387
388    fn set_blend_state(&mut self, blend_state: Option<BlendState>) {
389        self.draw_params.blend = if let Some(blend_state) = blend_state {
390            glium::Blend {
391                color: blend_component_to_glium(blend_state.color),
392                alpha: blend_component_to_glium(blend_state.alpha),
393                ..Default::default()
394            }
395        } else {
396            glium::Blend {
397                ..Default::default()
398            }
399        };
400    }
401
402    fn set_viewport(&mut self, viewport: &glam::Vec4) {
403        self.draw_params.viewport = Some(glium::Rect {
404            left: viewport.x as u32,
405            bottom: viewport.y as u32,
406            width: viewport.z as u32,
407            height: viewport.w as u32,
408        });
409
410        self.current_height = viewport.w as i32;
411    }
412
413    fn set_scissor(&mut self, scissor: [u32; 4]) {
414        self.draw_params.scissor = Some(glium::Rect {
415            left: scissor[0],
416            bottom: scissor[1],
417            width: scissor[2],
418            height: scissor[3],
419        });
420    }
421
422    fn select_program(
423        &mut self,
424        display: &Display,
425        shader_id: ShaderId,
426        shader_config: ShaderConfig,
427    ) {
428        // check if the shader is already loaded
429        if self.current_shader == Some(shader_id) {
430            return;
431        }
432
433        // unload the current shader
434        if self.current_shader.is_some() {
435            self.current_shader = None;
436        }
437
438        // check if the shader is in the cache
439        if self.shader_cache.contains_key(&shader_id) {
440            self.current_shader = Some(shader_id);
441            return;
442        }
443
444        // create the shader and add it to the cache
445        let mut program = OpenGLProgram::new(shader_config);
446        program.init();
447        program.preprocess(&ShaderVersion::GLSL410); // 410 is latest version supported by macOS
448
449        let source = ProgramCreationInput::SourceCode {
450            vertex_shader: &program.preprocessed_vertex,
451            fragment_shader: &program.preprocessed_frag,
452            geometry_shader: None,
453            tessellation_control_shader: None,
454            tessellation_evaluation_shader: None,
455            transform_feedback_varyings: None,
456            outputs_srgb: true, // workaround to avoid glium doing gamma correction
457            uses_point_size: false,
458        };
459
460        program.compiled_program = Some(Program::new(display, source).unwrap());
461
462        self.current_shader = Some(shader_id);
463        self.shader_cache.insert(shader_id, program);
464    }
465
466    fn bind_texture(&mut self, display: &Display, tile: usize, texture: &mut OutputTexture) {
467        // check if we've already uploaded this texture to the GPU
468        if let Some(texture_id) = texture.device_id {
469            // trace!("Texture found in GPU cache");
470            self.active_texture = tile;
471            self.current_texture_ids[tile] = texture_id as usize;
472
473            return;
474        }
475
476        // Create the texture
477        let raw_texture =
478            RawImage2d::from_raw_rgba(texture.data.clone(), (texture.width, texture.height));
479        let native_texture = Texture2d::new(display, raw_texture).unwrap();
480
481        self.active_texture = tile;
482        self.current_texture_ids[tile] = self.textures.len();
483        texture.device_id = Some(self.textures.len() as u32);
484
485        self.textures.push(TextureData::new(native_texture));
486    }
487
488    fn bind_sampler(&mut self, tile: usize, sampler: &OutputSampler) {
489        if let Some(texture_data) = self.textures.get_mut(self.current_texture_ids[tile]) {
490            let wrap_s = clamp_to_glium(sampler.clamp_s);
491            let wrap_t = clamp_to_glium(sampler.clamp_t);
492
493            let native_sampler = SamplerBehavior {
494                minify_filter: if sampler.linear_filter {
495                    MinifySamplerFilter::Linear
496                } else {
497                    MinifySamplerFilter::Nearest
498                },
499                magnify_filter: if sampler.linear_filter {
500                    MagnifySamplerFilter::Linear
501                } else {
502                    MagnifySamplerFilter::Nearest
503                },
504                wrap_function: (wrap_s, wrap_t, SamplerWrapFunction::Repeat),
505                ..Default::default()
506            };
507
508            texture_data.sampler = Some(native_sampler);
509        }
510    }
511
512    #[allow(clippy::too_many_arguments)]
513    fn draw_triangles(
514        &self,
515        display: &Display,
516        target: &mut Frame,
517        projection_matrix: glam::Mat4,
518        fog: &OutputFogParams,
519        vbo: &[u8],
520        num_tris: usize,
521        uniforms: &OutputUniforms,
522    ) {
523        // Grab current program
524        let program = self
525            .shader_cache
526            .get(&self.current_shader.unwrap())
527            .unwrap();
528
529        // Setup vertex buffer
530        let mut vertex_format_data = vec![
531            (
532                Cow::Borrowed("aVtxPos"),
533                0,
534                -1,
535                AttributeType::F32F32F32F32,
536                false,
537            ),
538            (
539                Cow::Borrowed("aVtxColor"),
540                4 * ::std::mem::size_of::<f32>(),
541                -1,
542                AttributeType::F32F32F32F32,
543                false,
544            ),
545        ];
546
547        if program.get_define_bool("USE_TEXTURE0") || program.get_define_bool("USE_TEXTURE1") {
548            vertex_format_data.push((
549                Cow::Borrowed("aTexCoord"),
550                8 * ::std::mem::size_of::<f32>(),
551                -1,
552                AttributeType::F32F32,
553                false,
554            ));
555        }
556
557        let vertex_buffer = if program.get_define_bool("USE_TEXTURE0")
558            || program.get_define_bool("USE_TEXTURE1")
559        {
560            let vertex_array = unsafe {
561                std::slice::from_raw_parts(vbo.as_ptr() as *const VertexWithTexture, num_tris * 3)
562            };
563            let buffer = VertexBuffer::new(display, vertex_array).unwrap();
564            VertexBufferAny::from(buffer)
565        } else {
566            let vertex_array =
567                unsafe { std::slice::from_raw_parts(vbo.as_ptr() as *const Vertex, num_tris * 3) };
568            let buffer = VertexBuffer::new(display, vertex_array).unwrap();
569            VertexBufferAny::from(buffer)
570        };
571
572        // Setup uniforms
573
574        let vtx_uniform_buf = if program.get_define_bool("USE_FOG") {
575            let data = VertexWithFogUniforms::new(
576                [self.screen_size[0] as f32, self.screen_size[1] as f32],
577                projection_matrix.to_cols_array_2d(),
578                fog.multiplier as f32,
579                fog.offset as f32,
580            );
581
582            let buffer = Buffer::new(
583                display,
584                &data,
585                BufferType::UniformBuffer,
586                BufferMode::Default,
587            )
588            .unwrap();
589            BufferAny::from(buffer)
590        } else {
591            let data = VertexUniforms::new(
592                [self.screen_size[0] as f32, self.screen_size[1] as f32],
593                projection_matrix.to_cols_array_2d(),
594            );
595
596            let buffer = Buffer::new(
597                display,
598                &data,
599                BufferType::UniformBuffer,
600                BufferMode::Default,
601            )
602            .unwrap();
603            BufferAny::from(buffer)
604        };
605
606        let blend_uniform_buf = if program.get_define_bool("USE_FOG") {
607            let data = BlendWithFogUniforms {
608                blend_color: uniforms.blend.blend_color.to_array(),
609                fog_color: uniforms.blend.fog_color.xyz().to_array(),
610                _padding: 0.0,
611            };
612
613            let buffer = Buffer::new(
614                display,
615                &data,
616                BufferType::UniformBuffer,
617                BufferMode::Default,
618            )
619            .unwrap();
620            BufferAny::from(buffer)
621        } else {
622            let data = BlendUniforms {
623                blend_color: uniforms.blend.blend_color.to_array(),
624            };
625
626            let buffer = Buffer::new(
627                display,
628                &data,
629                BufferType::UniformBuffer,
630                BufferMode::Default,
631            )
632            .unwrap();
633            BufferAny::from(buffer)
634        };
635
636        let combine_uniform_buf = {
637            let data = CombineUniforms {
638                prim_color: uniforms.combine.prim_color.to_array(),
639                env_color: uniforms.combine.env_color.to_array(),
640                _padding: 0.0,
641                key_center: uniforms.combine.key_center.to_array(),
642                key_scale: uniforms.combine.key_scale.to_array(),
643                _padding2: 0.0,
644                prim_lod_frac: uniforms.combine.prim_lod.x,
645                uk4: uniforms.combine.convert_k4,
646                uk5: uniforms.combine.convert_k5,
647            };
648
649            let buffer = Buffer::new(
650                display,
651                &data,
652                BufferType::UniformBuffer,
653                BufferMode::Default,
654            )
655            .unwrap();
656            BufferAny::from(buffer)
657        };
658
659        let frame_uniform_buf = if program.get_define_bool("USE_ALPHA")
660            && program.get_define_bool("ALPHA_COMPARE_DITHER")
661        {
662            let data = FrameUniforms {
663                frame_count: self.frame_count,
664                frame_height: self.current_height,
665            };
666
667            Some(
668                Buffer::new(
669                    display,
670                    &data,
671                    BufferType::UniformBuffer,
672                    BufferMode::Default,
673                )
674                .unwrap(),
675            )
676        } else {
677            None
678        };
679
680        // Setup uniforms
681        let mut shader_uniforms = vec![
682            (
683                "Uniforms",
684                UniformValue::Block(vtx_uniform_buf.as_slice_any(), |_block| Ok(())),
685            ),
686            (
687                "BlendUniforms",
688                UniformValue::Block(blend_uniform_buf.as_slice_any(), |_block| Ok(())),
689            ),
690            (
691                "CombineUniforms",
692                UniformValue::Block(combine_uniform_buf.as_slice_any(), |_block| Ok(())),
693            ),
694        ];
695
696        if program.get_define_bool("USE_TEXTURE0") {
697            let texture = self.textures.get(self.current_texture_ids[0]).unwrap();
698            shader_uniforms.push((
699                "uTex0",
700                UniformValue::Texture2d(&texture.texture, texture.sampler),
701            ));
702        }
703
704        if program.get_define_bool("USE_TEXTURE1") {
705            let texture = self.textures.get(self.current_texture_ids[1]).unwrap();
706            shader_uniforms.push((
707                "uTex1",
708                UniformValue::Texture2d(&texture.texture, texture.sampler),
709            ));
710        }
711
712        if program.get_define_bool("USE_ALPHA") && program.get_define_bool("ALPHA_COMPARE_DITHER") {
713            let frame_uniform_buf = frame_uniform_buf.as_ref();
714            shader_uniforms.push((
715                "FrameUniforms",
716                UniformValue::Block(frame_uniform_buf.unwrap().as_slice_any(), |_block| Ok(())),
717            ));
718        }
719
720        // Draw triangles
721        target
722            .draw(
723                &vertex_buffer,
724                NoIndices(PrimitiveType::TrianglesList),
725                program.compiled_program.as_ref().unwrap(),
726                &UniformVec {
727                    uniforms: shader_uniforms,
728                },
729                &self.draw_params,
730            )
731            .unwrap();
732    }
733}