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
//! This crate defines a `Framebuffer` structure, which is effectively a region of memory
//! that is interpreted as a 2-D array of pixels.

#![no_std]

pub mod pixel;
use core::{ops::{DerefMut, Deref}, hash::{Hash, Hasher}};
use log::{info, debug};
use memory::{PteFlags, PteFlagsArch, PhysicalAddress, Mutable, BorrowedSliceMappedPages};
use shapes::Coord;
pub use pixel::*;

/// Initializes the final framebuffer based on graphics mode info obtained during boot.
/// 
/// The final framebuffer represents the actual pixel content displayed on screen,
/// as its memory is directly mapped to the display device's underlying physical memory.
pub fn init<P: Pixel>() -> Result<Framebuffer<P>, &'static str> {
    // Attempt to get the below 3 items of graphic mode info.
    let mut width_height_paddr: Option<(usize, usize, PhysicalAddress)> = None;
    // Take possession of the early framebuffer and obtain graphic info from it.
    if let Some(early_fb) = early_printer::take() {
        width_height_paddr = Some((
            early_fb.width as usize,
            early_fb.height as usize,
            early_fb.paddr,
        ));
        // Here: the early framebuffer's underlying mapping is dropped.
    }

    // If we booted up other CPUs, we may have switched to a better graphics mode.
    if let Some(gi) = multicore_bringup::get_graphic_info() {
        let paddr = PhysicalAddress::new(gi.physical_address() as usize)
            .ok_or("Graphic mode physical address was invalid")?;
        let width = gi.width() as usize;
        let height = gi.height() as usize;
        width_height_paddr = Some((width, height, paddr));
    }
    
    let (width, height, paddr) = width_height_paddr
        .ok_or("Failed to get graphic mode information!")?;
    info!("Graphical framebuffer info: {} x {}, at paddr {:#X}",
        width, height, paddr,
    );
    Framebuffer::new(width, height, Some(paddr))
}

/// A framebuffer is a region of memory interpreted as a 2-D array of pixels.
/// The memory buffer is a rectangular region with a width and height.
pub struct Framebuffer<P: Pixel> {
    width: usize,
    height: usize,
    buffer: BorrowedSliceMappedPages<P, Mutable>,
} 
impl<P: Pixel> Hash for Framebuffer<P> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.width.hash(state);
        self.height.hash(state);
        self.buffer.deref().hash(state);
    }
}

impl<P: Pixel> Framebuffer<P> {
    /// Creates a new framebuffer with rectangular dimensions of `width * height`, 
    /// specified in number of pixels.
    ///
    /// If `physical_address` is `Some`, the returned framebuffer will be a real physical one,
    /// i.e., mapped to the physical memory at that address, which is typically hardware graphics memory.
    /// In this case, we attempt to map the memory as "write-combining", which only works
    /// on x86 if the Page Attribute Table feature is enabled.
    /// Otherwise, we map the real physical framebuffer memory with all caching disabled.
    ///
    /// If `physical_address` is `None`, the returned framebuffer is a "virtual" one 
    /// that renders to a randomly-allocated chunk of memory.
    pub fn new(
        width: usize,
        height: usize,
        physical_address: Option<PhysicalAddress>,
    ) -> Result<Framebuffer<P>, &'static str> {
        let kernel_mmi_ref = memory::get_kernel_mmi_ref().ok_or("KERNEL_MMI was not yet initialized!")?;            
        let size = width * height * core::mem::size_of::<P>();
        let pages = memory::allocate_pages_by_bytes(size)
            .ok_or("could not allocate pages for a new framebuffer")?;

        let mapped_framebuffer = if let Some(address) = physical_address {
            // For best performance, we map the real physical framebuffer memory
            // as write-combining using the PAT (on x86 only).
            // If PAT isn't available, fall back to disabling caching altogether.
            let mut flags: PteFlagsArch = PteFlags::new()
                .valid(true)
                .writable(true)
                .into();

            #[cfg(target_arch = "x86_64")] {
                if page_attribute_table::is_supported() {
                    flags = flags.pat_index(
                        page_attribute_table::MemoryCachingType::WriteCombining.pat_slot_index()
                    );
                    info!("Using PAT write-combining mapping for real physical framebuffer memory");
                } else {
                    flags = flags.device_memory(true);
                    info!("Falling back to cache-disable mapping for real physical framebuffer memory");
                }
            }
            #[cfg(not(target_arch = "x86_64"))] {
                flags = flags.device_memory(true);
            }

            let frames = memory::allocate_frames_by_bytes_at(address, size)
                .map_err(|_e| "Couldn't allocate frames for the final framebuffer")?;
            let fb_mp = kernel_mmi_ref.lock().page_table.map_allocated_pages_to(
                pages,
                frames,
                flags,
            )?;
            debug!("Mapped real physical framebuffer: {fb_mp:?}");
            fb_mp
        } else {
            kernel_mmi_ref.lock().page_table.map_allocated_pages(
                pages,
                PteFlags::new().valid(true).writable(true),
            )?
        };

        Ok(Framebuffer {
            width,
            height,
            buffer: mapped_framebuffer.into_borrowed_slice_mut(0, width * height)
                .map_err(|(|_mp, s)| s)?,
        })
    }

    /// Returns a mutable reference to this framebuffer's memory as a slice of pixels.
    pub fn buffer_mut(&mut self) -> &mut [P] {
        &mut self.buffer
    }

    /// Returns a reference to this framebuffer's memory as a slice of pixels.
    pub fn buffer(&self) -> &[P] {
        &self.buffer
    }

    /// Returns the `(width, height)` of this framebuffer.
    pub fn get_size(&self) -> (usize, usize) {
        (self.width, self.height)
    }

    /// Composites `src` to the buffer starting from `index`.
    pub fn composite_buffer(&mut self, src: &[P], index: usize) {
        let len = src.len();
        let dest_end = index + len;
        Pixel::composite_buffer(src, &mut self.buffer_mut()[index..dest_end]);
    }

    /// Draw a pixel at the given coordinate. 
    /// The `pixel` will be blended with the existing pixel value
    /// at that `coordinate` in this framebuffer.
    pub fn draw_pixel(&mut self, coordinate: Coord, pixel: P) {
        if let Some(index) = self.index_of(coordinate) {
            self.buffer[index] = pixel.blend(self.buffer[index]);
        }
    }

    /// Overwites a pixel at the given coordinate in this framebuffer
    /// instead of blending it like [`draw_pixel`](#method.draw_pixel).
    pub fn overwrite_pixel(&mut self, coordinate: Coord, pixel: P) {
        self.draw_pixel(coordinate, pixel)
    }

    /// Returns the pixel value at the given `coordinate` in this framebuffer.
    pub fn get_pixel(&self, coordinate: Coord) -> Option<P> {
        self.index_of(coordinate).map(|i| self.buffer[i])
    }

    /// Fills (overwrites) the entire framebuffer with the given `pixel` value.
    pub fn fill(&mut self, pixel: P) {
        for p in self.buffer.deref_mut() {
            *p = pixel;
        }
    }

    /// Returns the index of the given `coordinate` in this framebuffer,
    /// if this framebuffer [`contains`](#method.contains) the `coordinate` within its bounds.
    pub fn index_of(&self, coordinate: Coord) -> Option<usize> {
        if self.contains(coordinate) {
            Some((self.width * coordinate.y as usize) + coordinate.x as usize)
        } else {
            None
        }
    }

    /// Checks if the given `coordinate` is within the framebuffer's bounds.
    /// The `coordinate` is relative to the origin coordinate of `(0, 0)` being the top-left point of the framebuffer.
    pub fn contains(&self, coordinate: Coord) -> bool {
        coordinate.x >= 0
            && coordinate.x < (self.width as isize)
            && coordinate.y >= 0
            && coordinate.y < (self.height as isize)
    }

    /// Checks if a framebuffer overlaps with an area.
    /// # Arguments
    /// * `coordinate`: the top-left corner of the area relative to the origin(top-left point) of the framebuffer.
    /// * `width`: the width of the area in number of pixels.
    /// * `height`: the height of the area in number of pixels.
    pub fn overlaps_with(&mut self, coordinate: Coord, width: usize, height: usize) -> bool {
        coordinate.x < self.width as isize
            && coordinate.x + width as isize >= 0
            && coordinate.y < self.height as isize
            && coordinate.y + height as isize >= 0
    }

}