ゼロからOS自作入門 10章
ウィンドウ
ウィンドウっぽいものを出す
下回りから徐々にアプリケーションになってきた
もっとマウス
マウスポインタが画面端に来たときにそこで止まるようにする
void Window::DrawTo(FrameBuffer& dst, Vector2D<int> position) {
if (!transparent_color_) {
dst.Copy(position, shadow_buffer_);
return;
}
const auto tc = transparent_color_.value();
auto& writer = dst.Writer();
for (int y = std::max(0, 0 - position.y);
y < std::min(Height(), writer.Height() - position.y); ++y) {
for (int x = std::max(0, 0 - position.x);
x < std::min(Width(), writer.Width() - position.x); ++x) {
const auto c = At(x, y);
if (c != tc) {
writer.Write(position + Vector2D<int>{x, y}, c);
}
}
}
}
これでマウスポインタの描画範囲を画面内に収めることが出来る。つまり、マウスポインタのサイズ分だけしか書き込みをおこなわない
次に画面端を超えてマウスポインタ移動するバグを直す
unsigned int mouse_layer_id;
Vector2D<int> screen_size;
Vector2D<int> mouse_position;
void MouseObserver(int8_t displacement_x, int8_t displacement_y) {
auto newpos = mouse_position + Vector2D<int>{displacement_x, displacement_y};
newpos = ElementMin(newpos, screen_size + Vector2D<int>{-1, -1});
mouse_position = ElementMax(newpos, {0, 0});
layer_manager->Move(mouse_layer_id, mouse_position);
layer_manager->Draw();
// Add Timer to measure mouse calc time
// auto newpos = mouse_position + Vector2D<int>{displacement_x, displacement_y};
// newpos = ElementMin(newpos, screen_size + Vector2D<int>{-1, -1});
// mouse_position = ElementMax(newpos, {0, 0});
// layer_manager->Move(mouse_layer_id, mouse_position);
// StartLAPICTimer();
// layer_manager->Draw();
// auto elapsed = LAPICTimerElapsed();
// StopLAPICTimer();
// printk("MouseObserver: elapsed = %u\n", elapsed);
}
ここでは、マウスポインタを動かして、その結果が画面内に収まるようにしている。画面の左上と右下の座標と比較して、画面名に収まる最大の値を取るようにしている。
template <typename T>
Vector2D<T> ElementMax(const Vector2D<T>& lhs, const Vector2D<T>& rhs) {
return {std::max(lhs.x, rhs.x), std::max(lhs.y, rhs.y)};
}
template <typename T>
Vector2D<T> ElementMin(const Vector2D<T>& lhs, const Vector2D<T>& rhs) {
return {std::min(lhs.x, rhs.x), std::min(lhs.y, rhs.y)};
}
あとはMain関数内で画面サイズを screen_size
に設定する
はじめてのウィンドウ
ウィンドウ領域を描画+その上に文字を書く
なので、main関数はこれを追加
auto main_window = std::make_shared<Window>(
160, 68, frame_buffer_config.pixel_format);
DrawWindow(*main_window->Writer(), "Hello Window");
WriteString(*main_window->Writer(), {24, 28}, "Welcome to", {0, 0, 0});
WriteString(*main_window->Writer(), {24, 44}, " MikanOS world!", {0, 0, 0});
そして、これを重畳表示の最上位に持ってくる
auto bglayer_id =
layer_manager->NewLayer().SetWindow(bgwindow).Move({0, 0}).ID();
mouse_layer_id =
layer_manager->NewLayer().SetWindow(mouse_window).Move({200, 200}).ID();
auto main_window_layer_id =
layer_manager->NewLayer().SetWindow(main_window).Move({300, 100}).ID();
layer_manager->UpDown(bglayer_id, 0);
layer_manager->UpDown(mouse_layer_id, 1);
layer_manager->UpDown(main_window_layer_id, 1);
layer_manager->Draw();
で、DrawWindow()
が無いので実装する
namespace {
const int kCloseButtonWidth = 16;
const int kCloseButtonHeight = 14;
const char close_button[kCloseButtonHeight][kCloseButtonWidth + 1] = {
"...............@",
".:::::::::::::$@",
".:::::::::::::$@",
".:::@@::::@@::$@",
".::::@@::@@:::$@",
".:::::@@@@::::$@",
".::::::@@:::::$@",
".:::::@@@@::::$@",
".::::@@::@@:::$@",
".:::@@::::@@::$@",
".:::::::::::::$@",
".:::::::::::::$@",
".$$$$$$$$$$$$$$@",
"@@@@@@@@@@@@@@@@",
};
constexpr PixelColor ToColor(uint32_t c) {
return {
static_cast<uint8_t>((c >> 16) & 0xff),
static_cast<uint8_t>((c >> 8) & 0xff),
static_cast<uint8_t>(c & 0xff)
};
}
}
void DrawWindow(PixelWriter& writer, const char* title) {
auto fill_rect = [&writer](Vector2D<int> pos, Vector2D<int> size, uint32_t c) {
FillRectangle(writer, pos, size, ToColor(c));
};
const auto win_w = writer.Width();
const auto win_h = writer.Height();
fill_rect({0, 0}, {win_w, 1}, 0xc6c6c6);
fill_rect({1, 1}, {win_w - 2, 1}, 0xffffff);
fill_rect({0, 0}, {1, win_h}, 0xc6c6c6);
fill_rect({1, 1}, {1, win_h - 2}, 0xffffff);
fill_rect({win_w - 2, 1}, {1, win_h - 2}, 0x848484);
fill_rect({win_w - 1, 0}, {1, win_h}, 0x000000);
fill_rect({2, 2}, {win_w - 4, win_h - 4}, 0xc6c6c6);
fill_rect({3, 3}, {win_w - 6, 18}, 0x000084);
fill_rect({1, win_h - 2}, {win_w - 2, 1}, 0x848484);
fill_rect({0, win_h - 1}, {win_w, 1}, 0x000000);
WriteString(writer, {24, 4}, title, ToColor(0xffffff));
for (int y = 0; y < kCloseButtonHeight; ++y) {
for (int x = 0; x < kCloseButtonWidth; ++x) {
PixelColor c = ToColor(0xffffff);
if (close_button[y][x] == '@') {
c = ToColor(0x000000);
} else if (close_button[y][x] == '$') {
c = ToColor(0x848484);
} else if (close_button[y][x] == ':') {
c = ToColor(0xc6c6c6);
}
writer.Write({win_w - 5 - kCloseButtonWidth + x, 5 + y}, c);
}
}
}
auto fill_rect = [&writer](Vector2D<int> pos, Vector2D<int> size, uint32_t c) {
FillRectangle(writer, pos, size, ToColor(c));
};
ここでFillRectangle
関数をfill_rect
に読み替えている。これを使って細かい描画処理を行っている
XWindowがうまく動かなかった。再起動しようと思うので今日はここまで
うごいた
高速カウンタ
最初はタイマーで動いているかと思ったけど、違ったな。マウス操作でイベントが飛んできたときにカウントアップされる仕組みだった。
違った。普通に毎サイクルカウントアップしていた。
まぁ、目的を果たしている(カウントアップが確認できること)最低限の実装ってことかな。
チラチラ解消
FillRectangle(*main_window->Writer(), {24, 28}, {8 * 10, 16}, {0xc6, 0xc6, 0xc6});
WriteString(*main_window->Writer(), {24, 28}, str, {0, 0, 0});
ここで再描画をしているのが原因か。まぁそうだな。だからウィンドウ領域だけを描画するようにするのか。
まずはDrawの改良
void LayerManager::Draw(const Rectangle<int>& area) const {
for (auto layer : layer_stack_) {
layer->DrawTo(*screen_, area);
}
}
void LayerManager::Draw(unsigned int id) const {
bool draw = false;
Rectangle<int> window_area;
for (auto layer : layer_stack_) {
if (layer->ID() == id) {
window_area.size = layer->GetWindow()->Size();
window_area.pos = layer->GetPosition();
draw = true;
}
if (draw) {
layer->DrawTo(*screen_, window_area);
}
}
}
これまでのDrawと描画するIDを指定する2つ。
- 全てのレイヤを描画するのも領域を指定できるようにする
- IDを指定するものは特定のIDより上位のものを再描画する
あれ? コンソールのバックグラウンドが黒くなってしまった。
直ったー
void Window::DrawTo(FrameBuffer& dst, Vector2D<int> position, const Rectangle<int>& area) {
if (!transparent_color_) {
Rectangle<int> window_area{position, Size()};
Rectangle<int> intersection = area & window_area;
dst.Copy(intersection.pos, shadow_buffer_, {intersection.pos - position, intersection.size});
return;
}
const auto tc = transparent_color_.value();
auto& writer = dst.Writer();
for (int y = std::max(0, 0 - position.y);
y < std::min(Height(), writer.Height() - position.y);
++y) {
for (int x = std::max(0, 0 - position.x);
x < std::min(Width(), writer.Width() - position.x);
++x) {
const auto c = At(x, y);
if (c != tc) {
writer.Write(position + Vector2D<int>{x, y}, c);
}
}
}
}
ここでareaを指定して特定の領域だけ描画処理を行っている
Copyの説明が優しくないな。3つの領域が重なっている部分はわかるけど、この3つが何を表していて、そして、重なっている部分は何なのかよくわからない。
- 修正前
Error FrameBuffer::Copy(Vector2D<int> dst_pos, const FrameBuffer& src) {
if (config_.pixel_format != src.config_.pixel_format) {
return MAKE_ERROR(Error::kUnknownPixelFormat);
}
const auto bytes_per_pixel = BytesPerPixel(config_.pixel_format);
if (bytes_per_pixel <= 0) {
return MAKE_ERROR(Error::kUnknownPixelFormat);
}
const auto dst_size = FrameBufferSize(config_);
const auto src_size = FrameBufferSize(src.config_);
const Vector2D<int> dst_start = ElementMax(dst_pos, {0, 0});
const Vector2D<int> dst_end = ElementMin(dst_pos + src_size, dst_size);
uint8_t* dst_buf = FrameAddrAt(dst_start, config_);
const uint8_t* src_buf = FrameAddrAt({0, 0}, src.config_);
for (int y = dst_start.y; y < dst_end.y; ++y) {
memcpy(dst_buf, src_buf, bytes_per_pixel * (dst_end.x - dst_start.x));
dst_buf += BytesPerScanLine(config_);
src_buf += BytesPerScanLine(src.config_);
}
return MAKE_ERROR(Error::kSuccess);
}
これは元画像と転送先が重畳している部分だけ(領域がある部分だけ)を書き込んでいる
- 修正後
Error FrameBuffer::Copy(Vector2D<int> dst_pos, const FrameBuffer& src,
const Rectangle<int>& src_area) {
if (config_.pixel_format != src.config_.pixel_format) {
return MAKE_ERROR(Error::kUnknownPixelFormat);
}
const auto bytes_per_pixel = BytesPerPixel(config_.pixel_format);
if (bytes_per_pixel <= 0) {
return MAKE_ERROR(Error::kUnknownPixelFormat);
}
const Rectangle<int> src_area_shifted{dst_pos, src_area.size};
const Rectangle<int> src_outline{dst_pos - src_area.pos, FrameBufferSize(src.config_)};
const Rectangle<int> dst_outline{{0, 0}, FrameBufferSize(config_)};
const auto copy_area = dst_outline & src_outline & src_area_shifted;
const auto src_start_pos = copy_area.pos - (dst_pos - src_area.pos);
uint8_t* dst_buf = FrameAddrAt(copy_area.pos, config_);
const uint8_t* src_buf = FrameAddrAt(src_start_pos, src.config_);
for (int y = 0; y < copy_area.size.y; ++y) {
memcpy(dst_buf, src_buf, bytes_per_pixel * copy_area.size.x);
dst_buf += BytesPerScanLine(config_);
src_buf += BytesPerScanLine(src.config_);
}
return MAKE_ERROR(Error::kSuccess);
}
そうなると
変数 | 意味 |
---|---|
src_area_shifted | 描画対象領域 |
src_outline | 描画対象の画像 |
dst_outline | 転送先 |
になるんだろうな。
ここで、「src_area_shifted 」と「src_outline」が一致しない意味がわからない
わからない時は聞いてみよう。
バックバッファ
複数のフレームが重なっている部分は最も上位にあるものは一番最後に反映される。そのため、中間層のレイヤーが更新され、再描画するときにちらつきが発生する。
これを解決する方法として、バックバッファがある。これは2つの画面を用意する
- フロントバッファ:画面に投影されているバッファ
- バックバッファ:裏の画面で書き込みを行うバッファ
そして、バックバッファに書き込みが終わったときにバッファを切り替える
こうすることで画面のチラツキが抑えられる
描画を
- バックバッファに書き込み
- バックバッファの内容をフロントバッファにコピー
これで課題を解決している
mutable
メンバ関数にconst修飾子を付けると、メンバ関数内でメンバ変数を変更していけないが、mutable演算子をつけることでその制約を回避できる
ウィンドウのドラッグ移動
ついにウィンドウが動く
ちょっと難しかったけど、動いた。そして別のバグを見つけた。多分重畳の優先順位の問題なんだろうな。
ウィンドウだけドラッグ移動
レイヤに移動可能かどうかの属性をもたせる
属性をもたせるだけだから簡単