Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new graphics layer #722

Closed
wants to merge 32 commits into from
Closed

Conversation

ouz-a
Copy link
Contributor

@ouz-a ouz-a commented Dec 9, 2022

Porthole

Porthole, is new graphics layer for the Theseus OS, inspired by the old graphic's stack written from ground up Porthole has several goals:

  • Make it easy to use.
  • KISS (E.g; We won't write code that is going increase performance by %2 while making it harder to understand)
  • Performance is high priority.
  • It should be modular.

Design

Detailed design of Porthole will be written here.

Tasks:

  • Display pixels
  • Display text
  • Resizable windows
  • Create the initial PR
  • Handle keyboard
  • Handle rendering order
  • Error handling
  • Replace old graphics stack entirely.

This is very early phase code most of the it going change after reviews/discussions

Copy link
Member

@kevinaboos kevinaboos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I left some comments; the biggest issue is the way that offset calculations and bounds checking happens on a per-pixel basis, sometimes more than once per pixel.

@@ -60,11 +60,11 @@ pub static GRAPHIC_INFO: Mutex<GraphicInfo> = Mutex::new(GraphicInfo::new());
#[repr(packed)]
pub struct GraphicInfo {
/// The visible width of the screen, in pixels.
width: u16,
pub width: u16,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are private to avoid accidental modification; there are some accessor functions that you can use instead: https://www.theseus-os.com/Theseus/doc/multicore_bringup/struct.GraphicInfo.html#method.width

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed that

let buffer_height = rect.height / CHARACTER_HEIGHT;
let (x, y) = (rect.x, rect.y);

let some_slice = slice.as_bytes();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason why you're converting the string to a byte slice here? I would think it's better to keep it as a string so you can more easily handle wide or multi-byte characters (e.g., emoji, other unicode chars). Reducing it to a byte slice removes important information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's how old api worked to support basic text displaying, supporting multi-byte characters is future work.

buffer: BorrowedSliceMappedPages<u32, Mutable>,
}
impl FrameBuffer {
fn init_front_buffer() -> Result<FrameBuffer, &'static str> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a good idea to use two separate types for this:

  1. A type to represent the real physical framebuffer (or framebuffers, in the case of double buffering) that is mapped to actual graphics memory
  2. A type to represent virtual framebuffers that are just arbitrary chunks of memory

The idea is that, by design, we can prevent accidentally reading from a real framebuffer by not implementing functions that do that, like get_pixel(). Only the virtual framebuffer type would allow reading from it, while the real/physical framebuffer type would only allow writing to it.

for y in rect.start_y()..rect.end_y() {
for x in rect.start_x()..rect.end_x() {
if x > 0 && x < self.width as isize && y > 0 && y < self.height as isize {
self.draw_something(x, y, 0xF123999);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one of the main goals of an API for drawing things should be to allow the callers to easily avoid the expensive bounds checks and calculations of pixel offsets on a per-pixel basis. Here, you're forcing that to occur not just once, but twice per pixel, which is a lot of overhead.

Instead of using indexing (which requires bound checking), use iterators.

@kevinaboos kevinaboos marked this pull request as draft December 12, 2022 18:29
@ouz-a ouz-a force-pushed the pr-branch branch 2 times, most recently from f1ca63a to e5b5928 Compare December 14, 2022 12:56
Copy link
Member

@kevinaboos kevinaboos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leaving a partial review to share notes

}

pub fn display_window_title(window: &mut MutexGuard<Window>, fg_color: Color, bg_color: Color) {
if let Some(title) = window.title.clone() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this eagerly clones the whole title String every time. Instead move the .clone() operation into the inside of the branch

}

pub fn print_string(
window: &mut MutexGuard<Window>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume here you mean &mut Window, since there's no real reason to take a mutable reference to a MutexGuard. The only reason you'd pass a guard is if you needed the lock to be released within this function, and if that was the case, you'd need to pass it by ownership

}

/// FrameBuffer with no actual physical memory mapped,
/// used for Window and WindowManager's backbuffer.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that in the future when we support true double buffering using graphics memory, this will not be true (and thus this type shouldn't be used to represent that backbuffer in graphics memory).

let w_it = window.return_framebuffer_iterator();
let f = buffer_indexer(&mut self.buffer, self.width, window_screen);

for (w_color, pixel) in w_it.zip(f) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the right idea and, ultimately, the end point for what i was hoping to see once you have obtained an iterator over the window region (source) and an iterator over the framebuffer region (destination).

fn draw_unchecked(&mut self, x: isize, y: isize, col: Color) {
let x = x;
let y = y;
unsafe {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

definitely no need to use unsafe here or anywhere else in this entire crate.

}

// We don't want user to draw on top a border
pub fn return_drawable_area(&self) -> Rect {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider caching the values of rectangular bounding boxes for things like the title rectangle, drawable areas, etc, since they only need to change rarely upon a window resize action.
Then, when the window actually gets resized, you can recalculate them. A great design pattern for this is to use an Option<Rect> with an accessor function like get_or_insert()

Copy link

@m-hugo m-hugo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and you have many methods and fields that should be pub but aren't
or you could remove every pub if you don't care about modularity

})
}

fn copy_windows_into_main_vbuffer(&mut self, window: &mut MutexGuard<Window>) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should really be a Window method

Suggested change
fn copy_windows_into_main_vbuffer(&mut self, window: &mut MutexGuard<Window>) {
fn copy_into_vbuffer(&mut self, vbuf: &mut VirtualFrameBuffer){

}
}

fn draw_unchecked(&mut self, x: isize, y: isize, col: Color) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove unsafety

@kevinaboos
Copy link
Member

Closing in favor of #823

@kevinaboos kevinaboos closed this Jan 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants