diff --git a/sortix/lfbtextbuffer.cpp b/sortix/lfbtextbuffer.cpp index b520e18e..a215d217 100644 --- a/sortix/lfbtextbuffer.cpp +++ b/sortix/lfbtextbuffer.cpp @@ -48,28 +48,56 @@ static uint32_t ColorFromRGB(uint8_t r, uint8_t g, uint8_t b) return ret.color; } +static void LFBTextBuffer__RenderThread(void* user) +{ + ((LFBTextBuffer*) user)->RenderThread(); +} + LFBTextBuffer* CreateLFBTextBuffer(uint8_t* lfb, uint32_t lfbformat, size_t xres, size_t yres, size_t scansize) { + const size_t QUEUE_LENGTH = 1024; size_t columns = xres / (VGA_FONT_WIDTH + 1); size_t rows = yres / VGA_FONT_HEIGHT; size_t fontsize = VGA_FONT_CHARSIZE * VGA_FONT_NUMCHARS; + uint8_t* backbuf; uint8_t* font; uint16_t* chars; uint16_t* attrs; + TextBufferCmd* queue; LFBTextBuffer* ret; - if ( !(font = new uint8_t[fontsize]) ) + Process* kernel_process = Scheduler::GetKernelProcess(); + + if ( !(backbuf = new uint8_t[yres * scansize]) ) goto cleanup_done; + if ( !(font = new uint8_t[fontsize]) ) + goto cleanup_backbuf; if ( !(chars = new uint16_t[columns * rows]) ) goto cleanup_font; if ( !(attrs = new uint16_t[columns * rows]) ) goto cleanup_chars; - if ( !(ret = new LFBTextBuffer) ) + if ( !(queue = new TextBufferCmd[QUEUE_LENGTH]) ) goto cleanup_attrs; + if ( !(ret = new LFBTextBuffer) ) + goto cleanup_queue; memcpy(font, VGA::GetFont(), fontsize); + ret->queue_lock = KTHREAD_MUTEX_INITIALIZER; + ret->queue_not_full = KTHREAD_COND_INITIALIZER; + ret->queue_not_empty = KTHREAD_COND_INITIALIZER; + ret->queue_exit = KTHREAD_COND_INITIALIZER; + ret->queue_sync = KTHREAD_COND_INITIALIZER; + ret->queue_paused = KTHREAD_COND_INITIALIZER; + ret->queue_resume = KTHREAD_COND_INITIALIZER; + ret->queue = queue; + ret->queue_length = QUEUE_LENGTH; + ret->queue_offset = 0; + ret->queue_used = 0; + ret->queue_is_paused = false; + ret->queue_thread = false; ret->lfb = lfb; + ret->backbuf = backbuf; ret->lfbformat = lfbformat; ret->pixelsx = xres; ret->pixelsy = yres; @@ -92,14 +120,25 @@ LFBTextBuffer* CreateLFBTextBuffer(uint8_t* lfb, uint32_t lfbformat, ret->cursorpos = TextPos(0, 0); for ( size_t y = 0; y < yres; y++ ) memset(lfb + scansize * y, 0, lfbformat/8UL * xres); + + if ( !RunKernelThread(kernel_process, LFBTextBuffer__RenderThread, ret) ) + { + delete ret; + return NULL; + } + return ret; +cleanup_queue: + delete[] queue; cleanup_attrs: delete[] attrs; cleanup_chars: delete[] chars; cleanup_font: delete[] font; +cleanup_backbuf: + delete[] backbuf; cleanup_done: return NULL; } @@ -125,9 +164,21 @@ LFBTextBuffer::LFBTextBuffer() LFBTextBuffer::~LFBTextBuffer() { + kthread_mutex_lock(&queue_lock); + if ( queue_thread ) + { + TextBufferCmd cmd; + cmd.type = TEXTBUFCMD_EXIT; + IssueCommand(&cmd); + while ( queue_thread ) + kthread_cond_wait(&queue_exit, &queue_lock); + } + kthread_mutex_unlock(&queue_lock); + delete[] backbuf; delete[] font; delete[] chars; delete[] attrs; + delete[] queue; } size_t LFBTextBuffer::Width() const @@ -216,19 +267,77 @@ void LFBTextBuffer::RenderRange(TextPos from, TextPos to) { from = CropPosition(from); to = CropPosition(to); +#if !defined(HAS_FAST_VIDEO_MEMORY) + uint8_t* orig_lfb = lfb; + bool backbuffered = from.y != to.y; + if ( backbuffered ) + { + lfb = backbuf; + from.x = 0; + to.x = columns - 1; + } +#endif TextPos i = from; RenderChar(chars[i.y * columns + i.x], i.x, i.y); - do + while ( !(i.x==to.x && i.y==to.y) ) { i = AddToPosition(i, 1); RenderChar(chars[i.y * columns + i.x], i.x, i.y); - } while ( !(i.x==to.x && i.y==to.y) ); + } +#if !defined(HAS_FAST_VIDEO_MEMORY) + if ( backbuffered ) + { + lfb = orig_lfb; + size_t font_height = 16; + size_t font_width = 9; + size_t scanline_start = from.y * font_height; + size_t scanline_end = ((to.y+1) * font_height) - 1; + size_t linesize = font_width * sizeof(uint32_t) * columns; + for ( size_t sc = scanline_start; sc <= scanline_end; sc++ ) + { + size_t offset = sc * scansize; + memcpy(lfb + offset, backbuf + offset, linesize); + } + } +#endif +} + +void LFBTextBuffer::IssueCommand(TextBufferCmd* cmd) +{ + ScopedLock lock(&queue_lock); + while ( queue_used == queue_length ) + kthread_cond_wait(&queue_not_full, &queue_lock); + if ( !queue_used ) + kthread_cond_signal(&queue_not_empty); + queue[(queue_offset + queue_used++) % queue_length] = *cmd; +} + +void LFBTextBuffer::StopRendering() +{ + TextBufferCmd cmd; + cmd.type = TEXTBUFCMD_PAUSE; + IssueCommand(&cmd); + ScopedLock lock(&queue_lock); + while ( !queue_is_paused ) + kthread_cond_wait(&queue_paused, &queue_lock); +} + +void LFBTextBuffer::ResumeRendering() +{ + ScopedLock lock(&queue_lock); + queue_is_paused = false; + kthread_cond_signal(&queue_resume); } TextChar LFBTextBuffer::GetChar(TextPos pos) const { if ( UsablePosition(pos) ) - return EntryToTextChar(chars[pos.y * columns + pos.x]); + { + ((LFBTextBuffer*) this)->StopRendering(); + TextChar ret = EntryToTextChar(chars[pos.y * columns + pos.x]); + ((LFBTextBuffer*) this)->ResumeRendering(); + return ret; + } return {0, 0}; } @@ -236,47 +345,70 @@ void LFBTextBuffer::SetChar(TextPos pos, TextChar c) { if ( !UsablePosition(pos) ) return; - chars[pos.y * columns + pos.x] = CharToTextEntry(c); - RenderRegion(pos.x, pos.y, pos.x, pos.y); + uint16_t entry = CharToTextEntry(c); + TextBufferCmd cmd; + cmd.type = TEXTBUFCMD_CHAR; + cmd.x = pos.x; + cmd.y = pos.y; + cmd.c = entry; + IssueCommand(&cmd); } uint16_t LFBTextBuffer::GetCharAttr(TextPos pos) const { if ( UsablePosition(pos) ) - return attrs[pos.y * columns + pos.x]; + { + ((LFBTextBuffer*) this)->StopRendering(); + uint16_t ret = attrs[pos.y * columns + pos.x]; + ((LFBTextBuffer*) this)->ResumeRendering(); + return ret; + } return 0; } void LFBTextBuffer::SetCharAttr(TextPos pos, uint16_t attrval) { - if ( UsablePosition(pos) ) - attrs[pos.y * columns + pos.x] = attrval; + if ( !UsablePosition(pos) ) + return; + TextBufferCmd cmd; + cmd.type = TEXTBUFCMD_ATTR; + cmd.x = pos.x; + cmd.y = pos.y; + cmd.attr = attrval; + IssueCommand(&cmd); } bool LFBTextBuffer::GetCursorEnabled() const { - return cursorenabled; + ((LFBTextBuffer*) this)->StopRendering(); + bool ret = cursorenabled; + ((LFBTextBuffer*) this)->ResumeRendering(); + return ret; } void LFBTextBuffer::SetCursorEnabled(bool enablecursor) { - bool updatecursor = cursorenabled != enablecursor; - cursorenabled = enablecursor; - if ( updatecursor ) - RenderCharAt(cursorpos); + TextBufferCmd cmd; + cmd.type = TEXTBUFCMD_CURSOR_SET_ENABLED; + cmd.b = enablecursor; + IssueCommand(&cmd); } TextPos LFBTextBuffer::GetCursorPos() const { - return cursorpos; + ((LFBTextBuffer*) this)->StopRendering(); + TextPos ret = cursorpos; + ((LFBTextBuffer*) this)->ResumeRendering(); + return ret; } void LFBTextBuffer::SetCursorPos(TextPos newcursorpos) { - TextPos oldcursorpos = cursorpos; - cursorpos = newcursorpos; - RenderCharAt(oldcursorpos); - RenderCharAt(newcursorpos); + TextBufferCmd cmd; + cmd.type = TEXTBUFCMD_CURSOR_MOVE; + cmd.x = newcursorpos.x; + cmd.y = newcursorpos.y; + IssueCommand(&cmd); } size_t LFBTextBuffer::OffsetOfPos(TextPos pos) const @@ -288,6 +420,45 @@ void LFBTextBuffer::Scroll(ssize_t off, TextChar fillwith) { if ( !off ) return; + TextBufferCmd cmd; + cmd.type = TEXTBUFCMD_SCROLL; + cmd.scroll_offset = off; + cmd.c = CharToTextEntry(fillwith); + IssueCommand(&cmd); +} + +void LFBTextBuffer::Move(TextPos to, TextPos from, size_t numchars) +{ + to = CropPosition(to); + from = CropPosition(from); + TextBufferCmd cmd; + cmd.type = TEXTBUFCMD_MOVE; + cmd.to_x = to.x; + cmd.to_y = to.y; + cmd.from_x = from.x; + cmd.from_y = from.y; + cmd.val = numchars; + IssueCommand(&cmd); +} + +void LFBTextBuffer::Fill(TextPos from, TextPos to, TextChar fillwith, + uint16_t fillattr) +{ + from = CropPosition(from); + to = CropPosition(to); + TextBufferCmd cmd; + cmd.type = TEXTBUFCMD_FILL; + cmd.from_x = from.x; + cmd.from_y = from.y; + cmd.to_x = to.x; + cmd.to_y = to.y; + cmd.c = CharToTextEntry(fillwith); + cmd.attr = fillattr; + IssueCommand(&cmd); +} + +void LFBTextBuffer::DoScroll(ssize_t off, uint16_t entry) +{ bool neg = 0 < off; size_t absoff = off < 0 ? -off : off; if ( rows < absoff ) @@ -297,14 +468,12 @@ void LFBTextBuffer::Scroll(ssize_t off, TextChar fillwith) TextPos fillfrom = neg ? TextPos{0, rows-absoff} : TextPos{0, 0}; TextPos fillto = neg ? TextPos{columns-1, rows-1} : TextPos{columns-1, absoff-1}; size_t scrollchars = columns * (rows-absoff); - Move(scrollto, scrollfrom, scrollchars); - Fill(fillfrom, fillto, fillwith, 0); + DoMove(scrollto, scrollfrom, scrollchars); + DoFill(fillfrom, fillto, entry, 0); } -void LFBTextBuffer::Move(TextPos to, TextPos from, size_t numchars) +void LFBTextBuffer::DoMove(TextPos to, TextPos from, size_t numchars) { - to = CropPosition(to); - from = CropPosition(from); size_t dest = OffsetOfPos(to); size_t src = OffsetOfPos(from); if ( dest < src ) @@ -315,22 +484,163 @@ void LFBTextBuffer::Move(TextPos to, TextPos from, size_t numchars) for ( size_t i = 0; i < numchars; i++ ) chars[dest + numchars-1 - i] = chars[src + numchars-1 - i], attrs[dest + numchars-1 - i] = attrs[src + numchars-1 - i]; - TextPos toend = AddToPosition(to, numchars); - RenderRange(to, toend); } -void LFBTextBuffer::Fill(TextPos from, TextPos to, TextChar fillwith, - uint16_t fillattr) +void LFBTextBuffer::DoFill(TextPos from, TextPos to, uint16_t fillwith, + uint16_t fillattr) { - from = CropPosition(from); - to = CropPosition(to); size_t start = OffsetOfPos(from); size_t end = OffsetOfPos(to); - size_t entry = CharToTextEntry(fillwith); for ( size_t i = start; i <= end; i++ ) - chars[i] = entry, + chars[i] = fillwith, attrs[i] = fillattr; - RenderRange(from, to); +} + +void LFBTextBuffer::RenderThread() +{ + queue_is_paused = false; + size_t offset = 0; + size_t amount = 0; + bool exit_requested = false; + bool sync_requested = false; + bool pause_requested = false; + + while ( true ) + { + kthread_mutex_lock(&queue_lock); + + if ( queue_used == queue_length && amount ) + kthread_cond_signal(&queue_not_full); + + queue_used -= amount; + queue_offset = (queue_offset + amount) % queue_length; + + if ( !queue_used ) + { + if ( exit_requested ) + { + queue_thread = false; + kthread_cond_signal(&queue_exit); + kthread_mutex_unlock(&queue_lock); + return; + } + + if ( sync_requested ) + { + kthread_cond_signal(&queue_sync); + sync_requested = false; + } + + if ( pause_requested ) + { + queue_is_paused = true; + kthread_cond_signal(&queue_paused); + while ( queue_is_paused ) + kthread_cond_wait(&queue_resume, &queue_lock); + pause_requested = false; + } + } + + while ( !queue_used ) + kthread_cond_wait(&queue_not_empty, &queue_lock); + + amount = queue_used; + offset = queue_offset; + + kthread_mutex_unlock(&queue_lock); + + TextPos render_from(columns - 1, rows - 1); + TextPos render_to(0, 0); + + for ( size_t i = 0; i < amount; i++ ) + { + TextBufferCmd* cmd = &queue[(offset + i) % queue_length]; + switch ( cmd->type ) + { + case TEXTBUFCMD_EXIT: + exit_requested = true; + break; + case TEXTBUFCMD_SYNC: + sync_requested = true; + break; + case TEXTBUFCMD_PAUSE: + pause_requested = true; + break; + case TEXTBUFCMD_CHAR: + { + TextPos pos(cmd->x, cmd->y); + chars[pos.y * columns + pos.x] = cmd->c; + if ( IsTextPosBeforeTextPos(pos, render_from) ) + render_from = pos; + if ( IsTextPosAfterTextPos(pos, render_to) ) + render_to = pos; + } break; + case TEXTBUFCMD_ATTR: + { + TextPos pos(cmd->x, cmd->y); + attrs[pos.y * columns + pos.x] = cmd->attr; + } break; + case TEXTBUFCMD_CURSOR_SET_ENABLED: + if ( cmd->b != cursorenabled ) + { + cursorenabled = cmd->b; + if ( IsTextPosBeforeTextPos(cursorpos, render_from) ) + render_from = cursorpos; + if ( IsTextPosAfterTextPos(cursorpos, render_to) ) + render_to = cursorpos; + } + break; + case TEXTBUFCMD_CURSOR_MOVE: + { + TextPos pos(cmd->x, cmd->y); + if ( cursorpos.x != pos.x || cursorpos.y != pos.y ) + { + if ( IsTextPosBeforeTextPos(cursorpos, render_from) ) + render_from = cursorpos; + if ( IsTextPosAfterTextPos(cursorpos, render_to) ) + render_to = cursorpos; + cursorpos = pos; + if ( IsTextPosBeforeTextPos(cursorpos, render_from) ) + render_from = cursorpos; + if ( IsTextPosAfterTextPos(cursorpos, render_to) ) + render_to = cursorpos; + } + } break; + case TEXTBUFCMD_MOVE: + { + TextPos to(cmd->to_x, cmd->to_y); + TextPos from(cmd->from_x, cmd->from_y); + size_t numchars = cmd->val; + DoMove(to, from, numchars); + TextPos toend = AddToPosition(to, numchars); + if ( IsTextPosBeforeTextPos(to, render_from) ) + render_from = to; + if ( IsTextPosAfterTextPos(toend, render_to) ) + render_to = toend; + } break; + case TEXTBUFCMD_FILL: + { + TextPos from(cmd->from_x, cmd->from_y); + TextPos to(cmd->to_x, cmd->to_y); + DoFill(from, to, cmd->c, cmd->attr); + if ( IsTextPosBeforeTextPos(from, render_from) ) + render_from = from; + if ( IsTextPosAfterTextPos(to, render_to) ) + render_to = to; + } break; + case TEXTBUFCMD_SCROLL: + { + ssize_t off = cmd->scroll_offset; + DoScroll(off, cmd->c); + render_from = {0, 0}; + render_to = {columns-1, rows-1}; + } break; + } + } + + if ( !IsTextPosBeforeTextPos(render_to, render_from) ) + RenderRange(render_from, render_to); + } } } // namespace Sortix diff --git a/sortix/lfbtextbuffer.h b/sortix/lfbtextbuffer.h index 4f1811e6..726ccb38 100644 --- a/sortix/lfbtextbuffer.h +++ b/sortix/lfbtextbuffer.h @@ -25,32 +25,38 @@ #ifndef SORTIX_LFBTEXTBUFFER_H #define SORTIX_LFBTEXTBUFFER_H +#include #include namespace Sortix { -// Needed information: -// Linear frame bufer -// - Pointer -// - Format (bit depth) -// - X resolution, Y resolution, scanline size -// Font -// - Pointer to our copy of the font. -// Color table for each VGA color in -// Number of columns and rows. -// Table of characters and attributes. - -struct LFBTextBufferInfo +enum TextBufferCmdType { - uint8_t* lfb; - size_t lfbformat; - size_t xres; - size_t yres; - size_t columns; - size_t rows; - uint16_t* chars; - uint16_t* attrs; - uint8_t* vgafont; + TEXTBUFCMD_EXIT = 0, + TEXTBUFCMD_SYNC, + TEXTBUFCMD_PAUSE, + TEXTBUFCMD_CHAR, + TEXTBUFCMD_ATTR, + TEXTBUFCMD_CURSOR_SET_ENABLED, + TEXTBUFCMD_CURSOR_MOVE, + TEXTBUFCMD_MOVE, + TEXTBUFCMD_FILL, + TEXTBUFCMD_SCROLL, +}; + +struct TextBufferCmd +{ + union { TextBufferCmdType type; size_t align; }; + union { size_t x; size_t from_x; ssize_t scroll_offset; }; + union { size_t y; size_t from_y; }; + union { size_t to_x; }; + union { size_t to_y; }; + union + { + bool b; + struct { uint16_t c; uint16_t attr; }; + size_t val; + }; }; class LFBTextBuffer : public TextBuffer @@ -78,6 +84,9 @@ public: virtual TextPos GetCursorPos() const; virtual void SetCursorPos(TextPos newcursorpos); +public: + virtual void RenderThread(); + private: void RenderChar(uint16_t vgachar, size_t posx, size_t posy); void RenderCharAt(TextPos pos); @@ -87,9 +96,29 @@ private: size_t OffsetOfPos(TextPos pos) const; TextPos CropPosition(TextPos pos) const; TextPos AddToPosition(TextPos pos, size_t count); + void DoScroll(ssize_t off, uint16_t entry); + void DoMove(TextPos to, TextPos from, size_t numchars); + void DoFill(TextPos from, TextPos to, uint16_t fillwith, uint16_t fillattr); + void IssueCommand(TextBufferCmd* cmd); + void StopRendering(); + void ResumeRendering(); private: + kthread_mutex_t queue_lock; + kthread_cond_t queue_not_full; + kthread_cond_t queue_not_empty; + kthread_cond_t queue_exit; + kthread_cond_t queue_sync; + kthread_cond_t queue_paused; + kthread_cond_t queue_resume; + TextBufferCmd* queue; + size_t queue_length; + size_t queue_offset; + size_t queue_used; + bool queue_is_paused; + bool queue_thread; uint8_t* lfb; + uint8_t* backbuf; uint8_t* font; uint16_t* chars; uint16_t* attrs;