The idle thread is now actually run when the system is idle because it truly goes idle. The idle thread is made power efficient by using the hlt instruction rather than a busy loop. The new futex(2) system call is used to implement fast user-space mutexes, condition variables, and semaphores. The same backend and design is used as kutexes for truly sleeping kernel mutexes and condition variables. The new exit_thread(2) flag EXIT_THREAD_FUTEX_WAKE wakes a futex. Sleeping on clocks in the kernel now uses timers for true sleep. The interrupt worker thread now truly sleeps when idle. Kernel threads are now named. This is a compatible ABI change.
815 lines
20 KiB
C++
815 lines
20 KiB
C++
/*
|
|
* Copyright (c) 2012, 2013, 2014, 2015, 2016, 2021 Jonas 'Sortie' Termansen.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* lfbtextbuffer.cpp
|
|
* An indexable text buffer rendered to a graphical linear frame buffer.
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include <sortix/vga.h>
|
|
|
|
#include <sortix/kernel/kernel.h>
|
|
#include <sortix/kernel/kthread.h>
|
|
#include <sortix/kernel/refcount.h>
|
|
#include <sortix/kernel/scheduler.h>
|
|
#include <sortix/kernel/textbuffer.h>
|
|
#include <sortix/kernel/thread.h>
|
|
|
|
#include "vga.h"
|
|
#include "lfbtextbuffer.h"
|
|
|
|
namespace Sortix {
|
|
|
|
static uint32_t boldify(uint32_t color)
|
|
{
|
|
int b = color >> 0 & 0xFF;
|
|
int g = color >> 8 & 0xFF;
|
|
int r = color >> 16 & 0xFF;
|
|
b += 63;
|
|
if ( 255 < b )
|
|
b = 255;
|
|
g += 63;
|
|
if ( 255 < g )
|
|
g = 255;
|
|
r += 63;
|
|
if ( 255 < r )
|
|
r = 255;
|
|
return (uint32_t) b << 0 | (uint32_t) g << 8 | (uint32_t) r << 16;
|
|
}
|
|
|
|
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;
|
|
TextChar* chars;
|
|
TextBufferCmd* queue;
|
|
LFBTextBuffer* ret;
|
|
|
|
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 TextChar[columns * rows]) )
|
|
goto cleanup_font;
|
|
if ( !(queue = new TextBufferCmd[QUEUE_LENGTH]) )
|
|
goto cleanup_chars;
|
|
if ( !(ret = new LFBTextBuffer) )
|
|
goto cleanup_queue;
|
|
|
|
memcpy(font, VGA::GetFont(), fontsize);
|
|
ret->execute_lock = KTHREAD_MUTEX_INITIALIZER;
|
|
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->bytes_per_pixel = (lfbformat + 7) / 8;
|
|
ret->pixelsx = xres;
|
|
ret->pixelsy = yres;
|
|
ret->scansize = scansize;
|
|
ret->columns = columns;
|
|
ret->rows = rows;
|
|
ret->font = font;
|
|
memset(chars, 0, sizeof(chars[0]) * columns * rows);
|
|
ret->chars = chars;
|
|
ret->cursorenabled = true;
|
|
ret->cursorpos = TextPos(0, 0);
|
|
ret->emergency_state = false;
|
|
ret->invalidated = false;
|
|
ret->need_clear = true;
|
|
ret->exit_after_pause = false;
|
|
|
|
if ( !kernel_process )
|
|
return ret;
|
|
|
|
ret->queue_thread = true; // Visible to new thread.
|
|
if ( !RunKernelThread(kernel_process, LFBTextBuffer__RenderThread, ret,
|
|
"console") )
|
|
ret->queue_thread = false;
|
|
|
|
return ret;
|
|
|
|
cleanup_queue:
|
|
delete[] queue;
|
|
cleanup_chars:
|
|
delete[] chars;
|
|
cleanup_font:
|
|
delete[] font;
|
|
cleanup_backbuf:
|
|
delete[] backbuf;
|
|
cleanup_done:
|
|
return NULL;
|
|
}
|
|
|
|
void LFBTextBuffer::SpawnThreads()
|
|
{
|
|
if ( queue_thread )
|
|
return;
|
|
Process* kernel_process = Scheduler::GetKernelProcess();
|
|
queue_thread = true; // Visible to new thread.
|
|
if ( !RunKernelThread(kernel_process, LFBTextBuffer__RenderThread, this,
|
|
"console") )
|
|
queue_thread = false;
|
|
}
|
|
|
|
LFBTextBuffer::LFBTextBuffer()
|
|
{
|
|
}
|
|
|
|
LFBTextBuffer::~LFBTextBuffer()
|
|
{
|
|
if ( queue_thread )
|
|
{
|
|
kthread_mutex_lock(&queue_lock);
|
|
if ( queue_is_paused )
|
|
{
|
|
queue_is_paused = false;
|
|
exit_after_pause = true;
|
|
kthread_cond_signal(&queue_resume);
|
|
}
|
|
else
|
|
{
|
|
TextBufferCmd cmd;
|
|
cmd.type = TEXTBUFCMD_EXIT;
|
|
kthread_mutex_unlock(&queue_lock);
|
|
IssueCommand(&cmd);
|
|
kthread_mutex_lock(&queue_lock);
|
|
}
|
|
while ( queue_thread )
|
|
kthread_cond_wait(&queue_exit, &queue_lock);
|
|
kthread_mutex_unlock(&queue_lock);
|
|
}
|
|
delete[] backbuf;
|
|
delete[] font;
|
|
delete[] chars;
|
|
delete[] queue;
|
|
}
|
|
|
|
size_t LFBTextBuffer::Width()
|
|
{
|
|
return columns;
|
|
}
|
|
|
|
size_t LFBTextBuffer::Height()
|
|
{
|
|
return rows;
|
|
}
|
|
|
|
bool LFBTextBuffer::UsablePosition(TextPos pos) const
|
|
{
|
|
return pos.x < columns && pos.y < rows;
|
|
}
|
|
|
|
TextPos LFBTextBuffer::CropPosition(TextPos pos) const
|
|
{
|
|
if ( columns <= pos.x )
|
|
pos.x = columns - 1;
|
|
if ( rows <= pos.y )
|
|
pos.y = rows -1;
|
|
return pos;
|
|
}
|
|
|
|
TextPos LFBTextBuffer::AddToPosition(TextPos pos, size_t count)
|
|
{
|
|
size_t index = pos.y * columns + pos.x + count;
|
|
TextPos ret(index % columns, index / columns);
|
|
return CropPosition(ret);
|
|
}
|
|
|
|
void LFBTextBuffer::RenderChar(TextChar textchar, size_t posx, size_t posy)
|
|
{
|
|
if ( columns <= posx || rows <= posy )
|
|
return;
|
|
// TODO: Support other resolutions.
|
|
if ( lfbformat != 24 && lfbformat != 32 )
|
|
return;
|
|
// TODO: Support other font sizes.
|
|
if ( VGA_FONT_WIDTH != 8UL )
|
|
return;
|
|
bool drawcursor = cursorenabled && posx == cursorpos.x && posy == cursorpos.y;
|
|
uint32_t fgcolor = textchar.fg;
|
|
uint32_t bgcolor = textchar.bg;
|
|
if ( textchar.attr & ATTR_BOLD )
|
|
fgcolor = boldify(fgcolor);
|
|
int remap = VGA::MapWideToVGAFont(textchar.c);
|
|
const uint8_t* charfont = VGA::GetCharacterFont(font, remap);
|
|
size_t pixelyoff = rows * VGA_FONT_HEIGHT;
|
|
size_t pixelxoff = posx * (VGA_FONT_WIDTH+1);
|
|
for ( size_t y = 0; y < VGA_FONT_HEIGHT; y++ )
|
|
{
|
|
size_t pixely = posy * VGA_FONT_HEIGHT + y;
|
|
uint8_t linebitmap = charfont[y];
|
|
uint8_t* line = (uint8_t*) (lfb + pixely * scansize);
|
|
size_t bytesxoff = bytes_per_pixel * pixelxoff;
|
|
for ( size_t x = 0; x < VGA_FONT_WIDTH; x++ )
|
|
{
|
|
uint32_t color = linebitmap & 1 << (7-x) ? fgcolor : bgcolor;
|
|
line[bytesxoff++] = color >> 0;
|
|
line[bytesxoff++] = color >> 8;
|
|
line[bytesxoff++] = color >> 16;
|
|
if ( lfbformat == 32 )
|
|
line[bytesxoff++] = color >> 24;
|
|
}
|
|
uint32_t lastcolor = bgcolor;
|
|
if ( 0xB0 <= remap && remap <= 0xDF && (linebitmap & 1) )
|
|
lastcolor = fgcolor;
|
|
line[bytesxoff++] = lastcolor >> 0;
|
|
line[bytesxoff++] = lastcolor >> 8;
|
|
line[bytesxoff++] = lastcolor >> 16;
|
|
if ( lfbformat == 32 )
|
|
line[bytesxoff++] = lastcolor >> 24;
|
|
if ( unlikely(posx + 1 == columns) )
|
|
{
|
|
for ( size_t x = pixelxoff + VGA_FONT_WIDTH + 1; x < pixelsx; x++ )
|
|
{
|
|
line[bytesxoff++] = bgcolor >> 0;
|
|
line[bytesxoff++] = bgcolor >> 8;
|
|
line[bytesxoff++] = bgcolor >> 16;
|
|
if ( lfbformat == 32 )
|
|
line[bytesxoff++] = bgcolor >> 24;
|
|
}
|
|
}
|
|
}
|
|
if ( unlikely(posy + 1 == rows) )
|
|
{
|
|
size_t width = VGA_FONT_WIDTH + 1;
|
|
if ( unlikely(posx + 1 == columns) )
|
|
width = pixelsx - pixelxoff;
|
|
for ( size_t y = pixelyoff; y < pixelsy; y++ )
|
|
{
|
|
size_t bytesxoff = bytes_per_pixel * pixelxoff;
|
|
uint8_t* line = (uint8_t*) (lfb + y * scansize);
|
|
for ( size_t x = 0; x < width; x++ )
|
|
{
|
|
line[bytesxoff++] = bgcolor >> 0;
|
|
line[bytesxoff++] = bgcolor >> 8;
|
|
line[bytesxoff++] = bgcolor >> 16;
|
|
if ( lfbformat == 32 )
|
|
line[bytesxoff++] = bgcolor >> 24;
|
|
}
|
|
}
|
|
}
|
|
if ( likely(!drawcursor) && !(textchar.attr & ATTR_UNDERLINE) )
|
|
return;
|
|
size_t underlines = VGA_FONT_HEIGHT - (!drawcursor ? 1 : 0);
|
|
for ( size_t y = VGA_FONT_HEIGHT - 2; y < underlines; y++ )
|
|
{
|
|
size_t pixely = posy * VGA_FONT_HEIGHT + y;
|
|
uint8_t* line = (uint8_t*) (lfb + pixely * scansize);
|
|
size_t bytesxoff = bytes_per_pixel * pixelxoff;
|
|
for ( size_t x = 0; x < VGA_FONT_WIDTH+1; x++ )
|
|
{
|
|
line[bytesxoff++] = fgcolor >> 0;
|
|
line[bytesxoff++] = fgcolor >> 8;
|
|
line[bytesxoff++] = fgcolor >> 16;
|
|
if ( lfbformat == 32 )
|
|
line[bytesxoff++] = fgcolor >> 24;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LFBTextBuffer::RenderCharAt(TextPos pos)
|
|
{
|
|
RenderChar(chars[pos.y * columns + pos.x], pos.x, pos.y);
|
|
}
|
|
|
|
void LFBTextBuffer::RenderRegion(size_t c1, size_t r1, size_t c2, size_t r2)
|
|
{
|
|
for ( size_t y = r1; y <= r2; y++ )
|
|
for ( size_t x = c1; x <= c2; x++ )
|
|
RenderChar(chars[y * columns + x], x, y);
|
|
}
|
|
|
|
void LFBTextBuffer::RenderRange(TextPos from, TextPos to)
|
|
{
|
|
from = CropPosition(from);
|
|
to = CropPosition(to);
|
|
uint8_t* orig_lfb = lfb;
|
|
bool backbuffered = from.y != to.y;
|
|
if ( backbuffered )
|
|
{
|
|
lfb = backbuf;
|
|
from.x = 0;
|
|
to.x = columns - 1;
|
|
}
|
|
TextPos i = from;
|
|
RenderChar(chars[i.y * columns + i.x], i.x, i.y);
|
|
while ( !(i.x==to.x && i.y==to.y) )
|
|
{
|
|
i = AddToPosition(i, 1);
|
|
RenderChar(chars[i.y * columns + i.x], i.x, i.y);
|
|
}
|
|
if ( backbuffered )
|
|
{
|
|
lfb = orig_lfb;
|
|
size_t scanline_start = from.y * VGA_FONT_HEIGHT;
|
|
size_t scanline_end = ((to.y+1) * VGA_FONT_HEIGHT) - 1;
|
|
if ( to.y + 1 == rows )
|
|
scanline_end = pixelsy - 1;
|
|
for ( size_t sc = scanline_start; sc <= scanline_end; sc++ )
|
|
{
|
|
size_t offset = sc * scansize;
|
|
memcpy(lfb + offset, backbuf + offset, pixelsx * bytes_per_pixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LFBTextBuffer::IssueCommand(TextBufferCmd* cmd, bool already_locked)
|
|
{
|
|
if ( invalidated )
|
|
{
|
|
invalidated = false;
|
|
TextBufferCmd newcmd;
|
|
newcmd.type = TEXTBUFCMD_REDRAW;
|
|
IssueCommand(&newcmd, already_locked);
|
|
}
|
|
if ( !queue_thread || emergency_state )
|
|
{
|
|
bool exit_requested = false;
|
|
bool sync_requested = false;
|
|
bool pause_requested = false;
|
|
TextPos render_from(columns - 1, rows - 1);
|
|
TextPos render_to(0, 0);
|
|
ExecuteCommand(cmd, exit_requested, sync_requested, pause_requested, render_from, render_to);
|
|
if ( !IsTextPosBeforeTextPos(render_to, render_from) )
|
|
RenderRange(render_from, render_to);
|
|
return;
|
|
}
|
|
if ( !already_locked )
|
|
kthread_mutex_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;
|
|
if ( !already_locked )
|
|
kthread_mutex_unlock(&queue_lock);
|
|
}
|
|
|
|
bool LFBTextBuffer::StopRendering()
|
|
{
|
|
if ( !queue_thread || emergency_state )
|
|
return false;
|
|
TextBufferCmd cmd;
|
|
cmd.type = TEXTBUFCMD_PAUSE;
|
|
ScopedLock lock(&queue_lock);
|
|
if ( queue_is_paused )
|
|
return false;
|
|
IssueCommand(&cmd, true);
|
|
while ( !queue_is_paused )
|
|
kthread_cond_wait(&queue_paused, &queue_lock);
|
|
return true;
|
|
}
|
|
|
|
void LFBTextBuffer::ResumeRendering()
|
|
{
|
|
if ( !queue_thread || emergency_state )
|
|
return;
|
|
ScopedLock lock(&queue_lock);
|
|
if ( !queue_is_paused )
|
|
return;
|
|
queue_is_paused = false;
|
|
kthread_cond_signal(&queue_resume);
|
|
}
|
|
|
|
TextChar LFBTextBuffer::GetChar(TextPos pos)
|
|
{
|
|
if ( UsablePosition(pos) )
|
|
{
|
|
bool was_rendering = StopRendering();
|
|
TextChar ret = chars[pos.y * columns + pos.x];
|
|
if ( was_rendering )
|
|
ResumeRendering();
|
|
return ret;
|
|
}
|
|
return {0, 0, 0, 0, 0};
|
|
}
|
|
|
|
void LFBTextBuffer::SetChar(TextPos pos, TextChar c)
|
|
{
|
|
if ( !UsablePosition(pos) )
|
|
return;
|
|
TextBufferCmd cmd;
|
|
cmd.type = TEXTBUFCMD_CHAR;
|
|
cmd.x = pos.x;
|
|
cmd.y = pos.y;
|
|
cmd.c = c;
|
|
IssueCommand(&cmd);
|
|
}
|
|
|
|
bool LFBTextBuffer::GetCursorEnabled()
|
|
{
|
|
bool was_rendering = StopRendering();
|
|
bool ret = cursorenabled;
|
|
if ( was_rendering )
|
|
ResumeRendering();
|
|
return ret;
|
|
}
|
|
|
|
void LFBTextBuffer::SetCursorEnabled(bool enablecursor)
|
|
{
|
|
TextBufferCmd cmd;
|
|
cmd.type = TEXTBUFCMD_CURSOR_SET_ENABLED;
|
|
cmd.b = enablecursor;
|
|
IssueCommand(&cmd);
|
|
}
|
|
|
|
TextPos LFBTextBuffer::GetCursorPos()
|
|
{
|
|
bool was_rendering = StopRendering();
|
|
TextPos ret = cursorpos;
|
|
if ( was_rendering )
|
|
ResumeRendering();
|
|
return ret;
|
|
}
|
|
|
|
void LFBTextBuffer::SetCursorPos(TextPos newcursorpos)
|
|
{
|
|
TextBufferCmd cmd;
|
|
cmd.type = TEXTBUFCMD_CURSOR_MOVE;
|
|
cmd.x = newcursorpos.x;
|
|
cmd.y = newcursorpos.y;
|
|
IssueCommand(&cmd);
|
|
}
|
|
|
|
void LFBTextBuffer::Invalidate()
|
|
{
|
|
invalidated = true;
|
|
}
|
|
|
|
size_t LFBTextBuffer::OffsetOfPos(TextPos pos) const
|
|
{
|
|
return pos.y * columns + pos.x;
|
|
}
|
|
|
|
void LFBTextBuffer::Scroll(ssize_t off, TextChar fillwith)
|
|
{
|
|
if ( !off )
|
|
return;
|
|
TextBufferCmd cmd;
|
|
cmd.type = TEXTBUFCMD_SCROLL;
|
|
cmd.scroll_offset = off;
|
|
cmd.c = 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)
|
|
{
|
|
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 = fillwith;
|
|
IssueCommand(&cmd);
|
|
}
|
|
|
|
void LFBTextBuffer::DoScroll(ssize_t off, TextChar entry)
|
|
{
|
|
bool neg = 0 < off;
|
|
size_t absoff = off < 0 ? -off : off;
|
|
if ( rows < absoff )
|
|
absoff = rows;
|
|
TextPos scrollfrom = neg ? TextPos{0, absoff} : TextPos{0, 0};
|
|
TextPos scrollto = neg ? TextPos{0, 0} : TextPos{0, absoff};
|
|
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);
|
|
DoMove(scrollto, scrollfrom, scrollchars);
|
|
DoFill(fillfrom, fillto, entry);
|
|
}
|
|
|
|
void LFBTextBuffer::DoMove(TextPos to, TextPos from, size_t numchars)
|
|
{
|
|
size_t dest = OffsetOfPos(to);
|
|
size_t src = OffsetOfPos(from);
|
|
if ( dest < src )
|
|
for ( size_t i = 0; i < numchars; i++ )
|
|
chars[dest + i] = chars[src + i];
|
|
else if ( src < dest )
|
|
for ( size_t i = 0; i < numchars; i++ )
|
|
chars[dest + numchars-1 - i] = chars[src + numchars-1 - i];
|
|
}
|
|
|
|
void LFBTextBuffer::DoFill(TextPos from, TextPos to, TextChar fillwith)
|
|
{
|
|
size_t start = OffsetOfPos(from);
|
|
size_t end = OffsetOfPos(to);
|
|
for ( size_t i = start; i <= end; i++ )
|
|
chars[i] = fillwith;
|
|
}
|
|
|
|
bool LFBTextBuffer::IsCommandIdempotent(const TextBufferCmd* cmd) const
|
|
{
|
|
switch ( cmd->type )
|
|
{
|
|
case TEXTBUFCMD_EXIT: return true;
|
|
case TEXTBUFCMD_SYNC: return true;
|
|
case TEXTBUFCMD_PAUSE: return true;
|
|
case TEXTBUFCMD_CHAR: return true;
|
|
case TEXTBUFCMD_CURSOR_SET_ENABLED: return true;
|
|
case TEXTBUFCMD_CURSOR_MOVE: return true;
|
|
case TEXTBUFCMD_MOVE: return false;
|
|
case TEXTBUFCMD_FILL: return true;
|
|
case TEXTBUFCMD_SCROLL: return false;
|
|
case TEXTBUFCMD_REDRAW: return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
void LFBTextBuffer::ExecuteCommand(TextBufferCmd* cmd,
|
|
bool& exit_requested,
|
|
bool& sync_requested,
|
|
bool& pause_requested,
|
|
TextPos& render_from,
|
|
TextPos& render_to)
|
|
{
|
|
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_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);
|
|
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;
|
|
case TEXTBUFCMD_REDRAW:
|
|
{
|
|
render_from = {0, 0};
|
|
render_to = {columns-1, rows-1};
|
|
} break;
|
|
}
|
|
}
|
|
|
|
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;
|
|
if ( exit_after_pause )
|
|
{
|
|
queue_thread = false;
|
|
kthread_cond_signal(&queue_exit);
|
|
kthread_mutex_unlock(&queue_lock);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
while ( !queue_used )
|
|
kthread_cond_wait(&queue_not_empty, &queue_lock);
|
|
|
|
amount = queue_used;
|
|
offset = queue_offset;
|
|
|
|
kthread_mutex_unlock(&queue_lock);
|
|
|
|
execute_amount = amount;
|
|
|
|
kthread_mutex_lock(&execute_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];
|
|
ExecuteCommand(cmd, exit_requested, sync_requested, pause_requested, render_from, render_to);
|
|
}
|
|
|
|
kthread_mutex_unlock(&execute_lock);
|
|
|
|
if ( !IsTextPosBeforeTextPos(render_to, render_from) )
|
|
RenderRange(render_from, render_to);
|
|
}
|
|
}
|
|
|
|
bool LFBTextBuffer::EmergencyIsImpaired()
|
|
{
|
|
return !emergency_state;
|
|
}
|
|
|
|
bool LFBTextBuffer::EmergencyRecoup()
|
|
{
|
|
if ( !emergency_state )
|
|
emergency_state = true;
|
|
|
|
if ( !kthread_mutex_trylock(&queue_lock) )
|
|
return false;
|
|
kthread_mutex_unlock(&queue_lock);
|
|
|
|
if ( !kthread_mutex_trylock(&execute_lock) )
|
|
{
|
|
for ( size_t i = 0; i < execute_amount; i++ )
|
|
{
|
|
TextBufferCmd* cmd = &queue[(queue_offset + i) % queue_length];
|
|
if ( !IsCommandIdempotent(cmd) )
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
kthread_mutex_unlock(&execute_lock);
|
|
|
|
TextPos render_from(0, 0);
|
|
TextPos render_to(columns - 1, rows - 1);
|
|
|
|
for ( size_t i = 0; i < queue_used; i++ )
|
|
{
|
|
bool exit_requested = false;
|
|
bool sync_requested = false;
|
|
bool pause_requested = false;
|
|
TextBufferCmd* cmd = &queue[(queue_offset + i) % queue_length];
|
|
ExecuteCommand(cmd, exit_requested, sync_requested, pause_requested,
|
|
render_from, render_to);
|
|
}
|
|
|
|
queue_used = 0;
|
|
queue_offset = 0;
|
|
|
|
RenderRange(render_from, render_to);
|
|
|
|
return true;
|
|
}
|
|
|
|
void LFBTextBuffer::EmergencyReset()
|
|
{
|
|
// TODO: Reset everything here!
|
|
|
|
Fill(TextPos{0, 0}, TextPos{columns-1, rows-1}, TextChar{0, 0, 0, 0, 0});
|
|
SetCursorPos(TextPos{0, 0});
|
|
}
|
|
|
|
void LFBTextBuffer::Resume()
|
|
{
|
|
if ( need_clear )
|
|
{
|
|
for ( size_t y = 0; y < pixelsy; y++ )
|
|
memset(lfb + scansize * y, 0, bytes_per_pixel * pixelsx);
|
|
need_clear = false;
|
|
}
|
|
ResumeRendering();
|
|
}
|
|
|
|
void LFBTextBuffer::Pause()
|
|
{
|
|
StopRendering();
|
|
}
|
|
|
|
} // namespace Sortix
|