Skip to content

Add new graphics layer#722

Closed
ouz-a wants to merge 32 commits into
theseus-os:theseus_mainfrom
ouz-a:pr-branch
Closed

Add new graphics layer#722
ouz-a wants to merge 32 commits into
theseus-os:theseus_mainfrom
ouz-a:pr-branch

Conversation

@ouz-a
Copy link
Copy Markdown
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
Copy Markdown
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.

Comment thread kernel/multicore_bringup/src/lib.rs Outdated
pub struct GraphicInfo {
/// The visible width of the screen, in pixels.
width: u16,
pub width: u16,
Copy link
Copy Markdown
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
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed that

Comment thread applications/porthole/src/lib.rs Outdated
let buffer_height = rect.height / CHARACTER_HEIGHT;
let (x, y) = (rect.x, rect.y);

let some_slice = slice.as_bytes();
Copy link
Copy Markdown
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
Copy Markdown
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.

Comment thread applications/porthole/src/lib.rs Outdated
buffer: BorrowedSliceMappedPages<u32, Mutable>,
}
impl FrameBuffer {
fn init_front_buffer() -> Result<FrameBuffer, &'static str> {
Copy link
Copy Markdown
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.

Comment thread applications/porthole/src/lib.rs Outdated
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
Copy Markdown
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
Copy Markdown
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

Comment thread applications/porthole/src/lib.rs Outdated
}

pub fn display_window_title(window: &mut MutexGuard<Window>, fg_color: Color, bg_color: Color) {
if let Some(title) = window.title.clone() {
Copy link
Copy Markdown
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

Comment thread applications/porthole/src/lib.rs Outdated
}

pub fn print_string(
window: &mut MutexGuard<Window>,
Copy link
Copy Markdown
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

Comment thread applications/porthole/src/lib.rs Outdated
}

/// FrameBuffer with no actual physical memory mapped,
/// used for Window and WindowManager's backbuffer.
Copy link
Copy Markdown
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).

Comment thread applications/porthole/src/lib.rs Outdated
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
Copy Markdown
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).

Comment thread applications/porthole/src/lib.rs Outdated
fn draw_unchecked(&mut self, x: isize, y: isize, col: Color) {
let x = x;
let y = y;
unsafe {
Copy link
Copy Markdown
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.

Comment thread applications/porthole/src/lib.rs Outdated
}

// We don't want user to draw on top a border
pub fn return_drawable_area(&self) -> Rect {
Copy link
Copy Markdown
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
Copy Markdown

@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

Comment thread applications/porthole/src/lib.rs Outdated
})
}

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

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){

Comment thread applications/porthole/src/lib.rs Outdated
}
}

fn draw_unchecked(&mut self, x: isize, y: isize, col: Color) {
Copy link
Copy Markdown
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
Copy Markdown
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