struct PPU : Thread, IO {
  Node::Object node;
  Node::Video::Screen screen;
  Node::Setting::Boolean colorEmulation;
  Node::Setting::Boolean interframeBlending;
  Node::Setting::String rotation;
  Memory::Writable<n8 > vram;  //96KB
  Memory::Writable<n16> pram;
  Memory::Writable<n16> oam;

  bool accurate;

  struct Debugger {
    //debugger.cpp
    auto load(Node::Object) -> void;
    auto unload(Node::Object) -> void;

    struct Memory {
      Node::Debugger::Memory vram;
      Node::Debugger::Memory pram;
      Node::Debugger::Memory oam;
    } memory;

    struct Graphics {
      Node::Debugger::Graphics tiles4bpp;
      Node::Debugger::Graphics tiles8bpp;
      Node::Debugger::Graphics mode3;
      Node::Debugger::Graphics mode4;
      Node::Debugger::Graphics mode5;
    } graphics;
  } debugger;

  //ppu.cpp
  auto setAccurate(bool value) -> void;

  auto load(Node::Object) -> void;
  auto unload() -> void;

  auto blank() -> bool;

  auto step(u32 clocks) -> void;
  template<s32> auto cycleLinearMap(s32 x, u32 y) -> void;
  template<s32> auto cycleLinearRender(s32 x, u32 y) -> void;
  template<s32> auto cycleAffine(u32 x, u32 y) -> void;
  auto cycleBitmap(u32 x, u32 y) -> void;
  auto cycleWindow(u32 x, u32 y) -> void;
  auto cycleUpperLayer(u32 x, u32 y) -> void;
  template<s32> auto cycle(u32 y) -> void;
  auto main() -> void;

  auto frame() -> void;
  auto refresh() -> void;
  auto power() -> void;

  //io.cpp
  auto readIO(n32 address) -> n8;
  auto writeIO(n32 address, n8 byte) -> void;

  //memory.cpp
  auto bgReleaseBus() -> void;
  auto objReleaseBus() -> void;
  auto pramContention() -> bool;
  auto vramContention(n32 address) -> bool;
  auto oamContention() -> bool;

  auto readVRAM(u32 mode, n32 address) -> n16;
  auto readVRAM_BG(u32 mode, n32 address) -> n16;
  auto writeVRAM(u32 mode, n32 address, n16 half) -> void;

  auto readPRAM(u32 mode, n32 address) -> n16;
  auto writePRAM(u32 mode, n32 address, n16 half) -> void;

  auto readOAM(u32 mode, n32 address) -> n32;
  auto writeOAM(u32 mode, n32 address, n32 word) -> void;

  auto readObjectVRAM(u32 address) const -> n8;

  //color.cpp
  auto color(n32) -> n64;

  //serialization.cpp
  auto serialize(serializer&) -> void;

private:
  //note: I/O register order is {BG0-BG3, OBJ, SFX}
  //however; layer ordering is {OBJ, BG0-BG3, SFX}
  enum : u32 { OBJ = 0, BG0 = 1, BG1 = 2, BG2 = 3, BG3 = 4, SFX = 5 };
  enum : u32 { IN0 = 0, IN1 = 1, IN2 = 2, OUT = 3 };

  struct IO {
    n1  gameBoyColorMode;
    n1  forceBlank[4];
    n1  greenSwap;
  } io;

  struct Pixel {
    n1  enable;
    n2  priority;
    n15 color;

    //BG2 only
    n1 directColor;

    //OBJ only
    n1  translucent;
    n1  mosaic;
    n1  window;  //IN2
  };

  struct Background {
    //background.cpp
    auto setEnable(n1 status) -> void;
    auto scanline(u32 y) -> void;
    auto outputPixel(u32 x, u32 y) -> void;
    auto run(s32 x, u32 y) -> void;
    auto linearFetchTileMap(s32 x, u32 y) -> void;
    auto linearFetchTileData() -> void;
    auto linearRender(s32 x, u32 y) -> void;
    auto affineFetchTileMap(u32 x, u32 y) -> void;
    auto affineFetchTileData(u32 x, u32 y) -> void;
    auto bitmap(u32 x, u32 y) -> void;
    auto power(u32 id) -> void;

    //serialization.cpp
    auto serialize(serializer&) -> void;

    u32 id;  //BG0, BG1, BG2, BG3

    struct IO {
      static n3 mode;
      static n1 frame;
      static n5 mosaicWidth;
      static n5 mosaicHeight;

      n1 enable[4];

      n2 priority;
      n2 characterBase;
      n2 unused;
      n1 mosaic;
      n1 colorMode;
      n5 screenBase;
      n1 affineWrap;  //BG2, BG3 only
      n2 screenSize;

      n9 hoffset;
      n9 voffset;

      //BG2, BG3 only
      i16 pa;
      i16 pb;
      i16 pc;
      i16 pd;
      i28 x;
      i28 y;

      //internal
      i28 lx;
      i28 ly;
    } io;

    struct Latch {
      n10 character;
      n1 hflip;
      n1 vflip;
      n4 palette;

      n16 data;

      n1 active;
      n3 px;
    } latch;

    struct Affine {
      u32 screenSize;
      u32 screenWrap;
      u32 hmosaic;
      u32 vmosaic;
      u32 cx;
      u32 cy;
      u32 tx;
      u32 ty;
      n8  character;
    } affine;

    Pixel output[240];
    Pixel mosaicLatch;
    u32 mosaicOffset;
    u32 vmosaic;

    i28 fx;
    i28 fy;
  } bg0, bg1, bg2, bg3;

  struct Objects {
    //object.cpp
    auto setEnable(n1 status) -> void;
    auto goToNext() -> void;
    auto readA01(u32 y) -> void;
    auto readA2() -> void;
    auto drawObject(u32 y) -> void;
    auto step() -> void;
    auto scanline(u32 y) -> void;
    auto renderScanline(u32 y) -> void;
    auto outputPixel(u32 x, u32 y) -> void;
    auto power() -> void;

    //object.cpp
    auto serialize(serializer&) -> void;

    struct IO {
      n1 enable[4];

      n1 hblank;   //1 = allow access to OAM during Hblank
      n1 mapping;  //0 = two-dimensional, 1 = one-dimensional
      n4 mosaicWidth;
      n4 mosaicHeight;
    } io;

    struct Latch {
      n1 affine;
      n1 affineSize;
      n2 mode;
      n1 mosaic;
      n1 colors;

      n9 x;
      n5 affineParam;
      n1 hflip;
      n1 vflip;

      n10 character;
      n2  priority;
      n4  palette;

      n32 width;
      n32 height;
      n8  py;

      i16 pa;
      i16 pb;
      i16 pc;
      i16 pd;
    } latch;

    Pixel lineBuffers[2][240];
    Pixel output;
    Pixel mosaicLatch;
    u32 renderY;
    s32 mosaicY;
    n4  hmosaicOffset;
    n4  vmosaicOffset;
    n7  objIndex;
    bool active;
    bool activeCycle;

    enum class State : u32 {
      ReadA01, ReadA2, ReadPA, ReadPB, ReadPC, ReadPD
    } state;
  } objects;

  struct Window {
    //window.cpp
    auto scanline(u32 y) -> void;
    auto run(u32 x, u32 y) -> void;
    auto power(u32 id) -> void;

    //serialization.cpp
    auto serialize(serializer&) -> void;

    u32 id;  //IN0, IN1, IN2, OUT

    struct IO {
      n1 enable;
      n1 active[6];

      //IN0, IN1 only
      n8 x1;
      n8 x2;
      n8 y1;
      n8 y2;
    } io;

    n1 output[256];  //IN0, IN1, IN2 only
    n1 h;  //IN0, IN1 only
    n1 v;  //IN0, IN1 only
  } window0, window1, window2, window3;

  struct DAC {
    //dac.cpp
    auto scanline(u32 y) -> void;
    auto upperLayer(u32 x, u32 y) -> void;
    auto lowerLayer(u32 x, u32 y) -> void;
    auto pramLookup(Pixel& layer) -> n15;
    auto blend(n15 above, u32 eva, n15 below, u32 evb) -> n15;
    auto power() -> void;

    //serialization.cpp
    auto serialize(serializer&) -> void;

    struct IO {
      n2 blendMode;
      n1 blendAbove[6];
      n1 blendBelow[6];

      n5 blendEVA;
      n5 blendEVB;
      n5 blendEVY;
    } io;

    u32 aboveLayer;
    u32 belowLayer;
    n15 color;
    n1  blending;

    Pixel layers[6];

    u32* line = nullptr;
  } dac;

  bool pramAccessed;
  bool vramAccessedBG;
  bool oamAccessed;
  n32  renderingCycle;
};

extern PPU ppu;
