auto VDP::frame() -> void {
  latch.overscan = io.overscan;
}

auto VDP::scanline() -> void {
  state.hdot = 0;
  state.hcounter = 0;
  if(++state.vcounter >= frameHeight()) state.vcounter = 0;
  if(state.vcounter == 0) frame();

  latch.displayWidth = io.displayWidth;

  if(state.vcounter < screenHeight()) {
    planeA.scanline(state.vcounter);
    window.scanline(state.vcounter);
    planeB.scanline(state.vcounter);
    sprite.scanline(state.vcounter);
  }

  if(state.vcounter == 240) scheduler.exit(Scheduler::Event::Frame);

  state.output = output + (state.vcounter * 2 + 0) * 1280;
}

auto VDP::run() -> void {
  if(!io.displayEnable) return outputPixel(0);
  if(state.vcounter >= screenHeight()) return outputPixel(0);

  auto& planeA = window.isWindowed(state.hdot, state.vcounter) ? window : this->planeA;
  planeA.run(state.hdot, state.vcounter);
  planeB.run(state.hdot, state.vcounter);
  sprite.run(state.hdot, state.vcounter);

  auto output = io.backgroundColor;
  if(auto color = planeB.output.color) output = color;
  if(auto color = planeA.output.color) output = color;
  if(auto color = sprite.output.color) output = color;
  if(planeB.output.priority) if(auto color = planeB.output.color) output = color;
  if(planeA.output.priority) if(auto color = planeA.output.color) output = color;
  if(sprite.output.priority) if(auto color = sprite.output.color) output = color;

  outputPixel(cram.read(output));
  state.hdot++;
}

auto VDP::outputPixel(uint9 color) -> void {
  for(auto n : range(pixelWidth())) {
    state.output[   0 + n] = color;
    state.output[1280 + n] = color;
  }
  state.output += pixelWidth();
}
