Closed22

ゼロからOS自作入門 10章

ackyacky

ウィンドウ

ウィンドウっぽいものを出す

下回りから徐々にアプリケーションになってきた

ackyacky

もっとマウス

マウスポインタが画面端に来たときにそこで止まるようにする

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);
      }
    }
  }
}

これでマウスポインタの描画範囲を画面内に収めることが出来る。つまり、マウスポインタのサイズ分だけしか書き込みをおこなわない

ackyacky

次に画面端を超えてマウスポインタ移動するバグを直す

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)};
}
ackyacky

あとはMain関数内で画面サイズを screen_size に設定する

ackyacky

はじめてのウィンドウ

ウィンドウ領域を描画+その上に文字を書く

ackyacky

なので、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();
ackyacky

で、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);
    }
  }
}
ackyacky
  auto fill_rect = [&writer](Vector2D<int> pos, Vector2D<int> size, uint32_t c) {
    FillRectangle(writer, pos, size, ToColor(c));
  };

ここでFillRectangle関数をfill_rectに読み替えている。これを使って細かい描画処理を行っている

ackyacky

XWindowがうまく動かなかった。再起動しようと思うので今日はここまで


うごいた

ackyacky

高速カウンタ

最初はタイマーで動いているかと思ったけど、違ったな。マウス操作でイベントが飛んできたときにカウントアップされる仕組みだった。

違った。普通に毎サイクルカウントアップしていた。

まぁ、目的を果たしている(カウントアップが確認できること)最低限の実装ってことかな。

ackyacky

チラチラ解消

    FillRectangle(*main_window->Writer(), {24, 28}, {8 * 10, 16}, {0xc6, 0xc6, 0xc6});
    WriteString(*main_window->Writer(), {24, 28}, str, {0, 0, 0});

ここで再描画をしているのが原因か。まぁそうだな。だからウィンドウ領域だけを描画するようにするのか。

ackyacky

まずは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より上位のものを再描画する
ackyacky

あれ? コンソールのバックグラウンドが黒くなってしまった。

直ったー

ackyacky
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を指定して特定の領域だけ描画処理を行っている

ackyacky

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」が一致しない意味がわからない

ackyacky

バックバッファ

複数のフレームが重なっている部分は最も上位にあるものは一番最後に反映される。そのため、中間層のレイヤーが更新され、再描画するときにちらつきが発生する。

これを解決する方法として、バックバッファがある。これは2つの画面を用意する

  • フロントバッファ:画面に投影されているバッファ
  • バックバッファ:裏の画面で書き込みを行うバッファ

そして、バックバッファに書き込みが終わったときにバッファを切り替える

こうすることで画面のチラツキが抑えられる

https://nn-hokuson.hatenablog.com/entry/2014/01/15/164232

ackyacky

描画を

  • バックバッファに書き込み
  • バックバッファの内容をフロントバッファにコピー

これで課題を解決している

mutable

メンバ関数にconst修飾子を付けると、メンバ関数内でメンバ変数を変更していけないが、mutable演算子をつけることでその制約を回避できる

ackyacky

ウィンドウのドラッグ移動

ついにウィンドウが動く

ackyacky

ちょっと難しかったけど、動いた。そして別のバグを見つけた。多分重畳の優先順位の問題なんだろうな。

ackyacky

ウィンドウだけドラッグ移動

レイヤに移動可能かどうかの属性をもたせる

このスクラップは2021/08/25にクローズされました