Class: Amaterasu::GameBoy::Ppu

Inherits:
Object
  • Object
show all
Includes:
Utils::BitOps
Defined in:
lib/amaterasu/game_boy/ppu.rb,
lib/amaterasu/game_boy/ppu/modes.rb,
lib/amaterasu/game_boy/ppu/registers.rb,
lib/amaterasu/game_boy/ppu/modes/h_blank.rb,
lib/amaterasu/game_boy/ppu/modes/v_blank.rb,
lib/amaterasu/game_boy/ppu/modes/disabled.rb,
lib/amaterasu/game_boy/ppu/modes/oam_scan.rb,
lib/amaterasu/game_boy/ppu/modes/rendering.rb,
lib/amaterasu/game_boy/ppu/registers/lcd_status.rb,
lib/amaterasu/game_boy/ppu/registers/lcd_control.rb,
lib/amaterasu/game_boy/ppu/modes/rendering/pixel_fifo.rb,
lib/amaterasu/game_boy/ppu/modes/rendering/pixel_emitter.rb,
lib/amaterasu/game_boy/ppu/modes/rendering/bg_win_fetcher.rb,
lib/amaterasu/game_boy/ppu/modes/rendering/sprite_fetcher.rb,
sig/akane/game_boy/ppu.rbs,
sig/akane/game_boy/ppu/modes.rbs,
sig/akane/game_boy/ppu/modes/h_blank.rbs,
sig/akane/game_boy/ppu/modes/v_blank.rbs,
sig/akane/game_boy/ppu/modes/disabled.rbs,
sig/akane/game_boy/ppu/modes/oam_scan.rbs,
sig/akane/game_boy/ppu/modes/rendering.rbs

Overview

This class models the Pixel Processing Unit from the Original Game Boy.

The Ppu outputs a 160x144 pixel framebuffer each frame. This framebuffer will be used by the chosen Renderer to display the graphics. The Ppu should not care about how the pixels are rendered, just output them.

Specifications:

  • The frame consists of 154 scanlines, 144 visible + 10 vblank (Cpu can access VRAM).
  • Scanlines are drawn from top to bottom, left to right.
  • Updating registers mid-frame can cause effects since the pixels are still being drawn.
  • Dots per scanline = 456 t-cycles (114 m-cycles) -> Hardware spec.
  • Dots per frame = 154 * 456 = 70_224 t-cycles (17_556 m-cycles).
  • OAM search takes 80 dots (20 m-cycles) -> 40 sprites, 2 dots each.

Defined Under Namespace

Modules: Modes Classes: Registers

Constant Summary collapse

PIXELS_PER_SCANLINE =
160
VISIBLE_SCANLINES =
144
TOTAL_SCANLINES =
154
DOTS_PER_SCANLINE =

Returns:

  • (Integer)
456
MAX_SPRITES_PER_SCANLINE =
10
MODES =

Returns:

  • (Hash[Symbol, Integer])
DOTS_PER_OAM_SCAN =

Returns:

  • (Integer)
WINDOW_TILE_MAPS =

Returns:

  • (Hash[Integer, Hash[Symbol, Integer]])
BG_TILE_MAPS =

Returns:

  • (Hash[Integer, Hash[Symbol, Integer]])
CONSOLE_CHARS =

Returns:

  • (Array[String])

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils::BitOps

bit, clear_bit, #self?.bit, #self?.clear_bit, #self?.set_bit, set_bit

Constructor Details

#initialize(vram, oam, display, interrupts, skip_boot_rom: true, trace_ppu: false) ⇒ Ppu

Returns a new instance of Ppu.

Parameters:

  • vram (Vram)
  • oam (Oam)
  • interrupts (Interrupts)
  • trace_ppu: (Boolean) (defaults to: false)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/amaterasu/game_boy/ppu.rb', line 34

def initialize(
  vram,
  oam,
  display,
  interrupts,
  skip_boot_rom: true,
  trace_ppu: false
)
  @vram = vram
  @oam = oam
  @display = display
  @interrupts = interrupts
  @trace_ppu = trace_ppu

  @registers     = Registers.new(interrupts, skip_boot_rom:)
  @framebuffer   = Array.new
  @sprite_buffer = Array.new(MAX_SPRITES_PER_SCANLINE).clear

  @modes = Modes.build_hash(self)
  @mode  = set_mode(:disabled)
  @wy_eq_ly = false
  @window_y_count = 0
  @dots = 0
end

Instance Attribute Details

#bgpInteger (readonly)

Returns the value of attribute bgp.

Returns:

  • (Integer)


20
21
22
# File 'sig/akane/game_boy/ppu.rbs', line 20

def bgp
  @bgp
end

#dmaInteger (readonly)

Returns the value of attribute dma.

Returns:

  • (Integer)


19
20
21
# File 'sig/akane/game_boy/ppu.rbs', line 19

def dma
  @dma
end

#dotsObject (readonly)

Returns the value of attribute dots.



29
30
31
# File 'lib/amaterasu/game_boy/ppu.rb', line 29

def dots
  @dots
end

#framebufferObject (readonly)

Returns the value of attribute framebuffer.



29
30
31
# File 'lib/amaterasu/game_boy/ppu.rb', line 29

def framebuffer
  @framebuffer
end

#lcdcInteger (readonly)

Returns the value of attribute lcdc.

Returns:

  • (Integer)


14
15
16
# File 'sig/akane/game_boy/ppu.rbs', line 14

def lcdc
  @lcdc
end

#lyInteger (readonly)

Returns the value of attribute ly.

Returns:

  • (Integer)


17
18
19
# File 'sig/akane/game_boy/ppu.rbs', line 17

def ly
  @ly
end

#lycInteger (readonly)

Returns the value of attribute lyc.

Returns:

  • (Integer)


18
19
20
# File 'sig/akane/game_boy/ppu.rbs', line 18

def lyc
  @lyc
end

#obp0Integer (readonly)

Returns the value of attribute obp0.

Returns:

  • (Integer)


21
22
23
# File 'sig/akane/game_boy/ppu.rbs', line 21

def obp0
  @obp0
end

#obp1Integer (readonly)

Returns the value of attribute obp1.

Returns:

  • (Integer)


22
23
24
# File 'sig/akane/game_boy/ppu.rbs', line 22

def obp1
  @obp1
end

#registersObject (readonly)

Returns the value of attribute registers.



29
30
31
# File 'lib/amaterasu/game_boy/ppu.rb', line 29

def registers
  @registers
end

#scxInteger (readonly)

Returns the value of attribute scx.

Returns:

  • (Integer)


16
17
18
# File 'sig/akane/game_boy/ppu.rbs', line 16

def scx
  @scx
end

#scyInteger (readonly)

Returns the value of attribute scy.

Returns:

  • (Integer)


15
16
17
# File 'sig/akane/game_boy/ppu.rbs', line 15

def scy
  @scy
end

#sprite_bufferObject (readonly)

Returns the value of attribute sprite_buffer.



29
30
31
# File 'lib/amaterasu/game_boy/ppu.rb', line 29

def sprite_buffer
  @sprite_buffer
end

#window_y_countObject

Returns the value of attribute window_y_count.



27
28
29
# File 'lib/amaterasu/game_boy/ppu.rb', line 27

def window_y_count
  @window_y_count
end

#wxInteger (readonly)

Returns the value of attribute wx.

Returns:

  • (Integer)


24
25
26
# File 'sig/akane/game_boy/ppu.rbs', line 24

def wx
  @wx
end

#wyInteger (readonly)

Returns the value of attribute wy.

Returns:

  • (Integer)


23
24
25
# File 'sig/akane/game_boy/ppu.rbs', line 23

def wy
  @wy
end

#wy_eq_lyObject

Returns the value of attribute wy_eq_ly.



27
28
29
# File 'lib/amaterasu/game_boy/ppu.rb', line 27

def wy_eq_ly
  @wy_eq_ly
end

Instance Method Details

#addressing_modeInteger

Returns:

  • (Integer)


55
# File 'sig/akane/game_boy/ppu.rbs', line 55

def addressing_mode: () -> Integer

#bg_tile_mapTileMap

Returns:

  • (TileMap)


164
165
166
167
168
# File 'lib/amaterasu/game_boy/ppu.rb', line 164

def bg_tile_map
  return @vram.tile_map_high if @registers.lcdc.bg_tile_map_high?

  @vram.tile_map_low
end

#bg_win_tile_dataTileData

Returns:

  • (TileData)


171
172
173
174
175
176
177
178
179
180
# File 'lib/amaterasu/game_boy/ppu.rb', line 171

def bg_win_tile_data
  @vram.tile_data.addressing_mode =
    if @registers.lcdc.tile_data_at_0x8000?
      :unsigned
    else
      :signed
    end

  @vram.tile_data
end

#bgp=void (readonly)

This method returns an undefined value.

Parameters:

  • value (Integer)


35
# File 'sig/akane/game_boy/ppu.rbs', line 35

def bgp=: (Integer value) -> void

#draw_frameObject

Delegates the draw to the chosen Renderer.



98
99
100
# File 'lib/amaterasu/game_boy/ppu.rb', line 98

def draw_frame
  @display&.draw(@framebuffer)
end

#draw_scanlinevoid

This method returns an undefined value.



49
# File 'sig/akane/game_boy/ppu.rbs', line 49

def draw_scanline: () -> void

#fetch_sprite_at(index) ⇒ Object



152
153
154
# File 'lib/amaterasu/game_boy/ppu.rb', line 152

def fetch_sprite_at(index)
  @oam.sprite(index)
end

#increment_lyObject



106
107
108
109
110
# File 'lib/amaterasu/game_boy/ppu.rb', line 106

def increment_ly
  @registers.ly += 1

  ly_compare
end

#lcd_off?Boolean

Returns:

  • (Boolean)


52
# File 'sig/akane/game_boy/ppu.rbs', line 52

def lcd_off?: () -> bool

#lcd_on?Boolean

Returns:

  • (Boolean)


51
# File 'sig/akane/game_boy/ppu.rbs', line 51

def lcd_on?: () -> bool

#lcdc=void (readonly)

This method returns an undefined value.

Parameters:

  • value (Integer)


30
# File 'sig/akane/game_boy/ppu.rbs', line 30

def lcdc=: (Integer value) -> void

#ly_compareObject



112
113
114
115
116
117
118
119
120
# File 'lib/amaterasu/game_boy/ppu.rb', line 112

def ly_compare
  if @registers.ly == @registers.lyc
    @registers.stat.set_lyc_bit
  else
    @registers.stat.clear_lyc_bit
  end

  # request_interrupt(:lcd_stat) if @registers.stat.rising_edge?
end

#lyc=void (readonly)

This method returns an undefined value.

Parameters:

  • value (Integer)


34
# File 'sig/akane/game_boy/ppu.rbs', line 34

def lyc=: (Integer value) -> void

#obj_tile_dataTileData

Returns:

  • (TileData)


183
184
185
186
# File 'lib/amaterasu/game_boy/ppu.rb', line 183

def obj_tile_data
  @vram.tile_data.addressing_mode = :unsigned
  @vram.tile_data
end

#obp0=void (readonly)

This method returns an undefined value.

Parameters:

  • value (Integer)


36
# File 'sig/akane/game_boy/ppu.rbs', line 36

def obp0=: (Integer value) -> void

#obp1=void (readonly)

This method returns an undefined value.

Parameters:

  • value (Integer)


37
# File 'sig/akane/game_boy/ppu.rbs', line 37

def obp1=: (Integer value) -> void

#read_oam(address:) ⇒ Integer

Returns a 8-bit value stored in OAM in a given address.

Parameters:

  • address: (Integer)

Returns:

  • (Integer)


141
142
143
144
145
# File 'lib/amaterasu/game_boy/ppu.rb', line 141

def read_oam(address:)
  return 0xFF if [@modes[:oam_scan], @modes[:rendering]].include?(@mode)

  @oam.read_byte(address:)
end

#read_vram(address:) ⇒ Integer

Returns a 8-bit value stored in VRAM in a given address.

Parameters:

  • address (Integer)
  • address: (Integer)

Returns:

  • (Integer)


126
127
128
129
130
# File 'lib/amaterasu/game_boy/ppu.rb', line 126

def read_vram(address:)
  return 0xFF if @mode == @modes[:rendering]

  @vram.read_byte(address:)
end

#request_interrupt(interrupt_type) ⇒ Object



102
103
104
# File 'lib/amaterasu/game_boy/ppu.rb', line 102

def request_interrupt(interrupt_type)
  @interrupts.request(interrupt_type)
end

#reset_for_scanlineObject



81
82
83
84
# File 'lib/amaterasu/game_boy/ppu.rb', line 81

def reset_for_scanline
  @dots = 0
  @sprite_buffer.clear unless @sprite_buffer.empty?
end

#reset_statesObject

Restarts the rendering pipeline state.



87
88
89
90
91
92
93
94
95
# File 'lib/amaterasu/game_boy/ppu.rb', line 87

def reset_states
  reset_for_scanline
  @registers.ly = 0x00
  @wy_eq_ly = false # here?
  @window_y_count = 0
  @framebuffer.clear

  ly_compare
end

#scx=void (readonly)

This method returns an undefined value.

Parameters:

  • value (Integer)


33
# File 'sig/akane/game_boy/ppu.rbs', line 33

def scx=: (Integer value) -> void

#scy=void (readonly)

This method returns an undefined value.

Parameters:

  • value (Integer)


32
# File 'sig/akane/game_boy/ppu.rbs', line 32

def scy=: (Integer value) -> void

#set_mode(mode) ⇒ Object

Sets the current PPU mode to be ticked. Updates STAT Bits 1-0 that reads the current PPU mode.

Parameters:

  • mode (Symbol)


73
74
75
76
77
78
79
# File 'lib/amaterasu/game_boy/ppu.rb', line 73

def set_mode(mode)
  @mode = @modes[mode]
  @registers.stat.set_mode_bits(@mode.number)
  # request_interrupt(:lcd_stat) if @registers.stat.rising_edge?

  @mode
end

#shadeInteger

Parameters:

  • color_index (Integer)

Returns:

  • (Integer)


50
# File 'sig/akane/game_boy/ppu.rbs', line 50

def shade: (Integer color_index) -> Integer

#statInteger

Returns:

  • (Integer)


28
# File 'sig/akane/game_boy/ppu.rbs', line 28

def stat: () -> Integer

#stat=void

This method returns an undefined value.

Parameters:

  • value (Integer)


31
# File 'sig/akane/game_boy/ppu.rbs', line 31

def stat=: (Integer value) -> void

#tickvoid

This method returns an undefined value.

Core PPU state machine.

Each mode is responsible for its own logic and also switching to the next mode.



63
64
65
66
67
# File 'lib/amaterasu/game_boy/ppu.rb', line 63

def tick
  @mode.tick
  log_state if @trace_ppu
  @dots += 1
end

#tracevoid

This method returns an undefined value.



56
# File 'sig/akane/game_boy/ppu.rbs', line 56

def trace: () -> void

#window_tile_mapTileMap

Returns:

  • (TileMap)


157
158
159
160
161
# File 'lib/amaterasu/game_boy/ppu.rb', line 157

def window_tile_map
  return @vram.tile_map_high if @registers.lcdc.window_tile_map_high?

  @vram.tile_map_low
end

#write_oam(address:, value:) ⇒ void

This method returns an undefined value.

Stores a 8-bit value in OAM in a given address.

Parameters:

  • address: (Integer)
  • value: (Integer)


148
149
150
# File 'lib/amaterasu/game_boy/ppu.rb', line 148

def write_oam(address:, value:)
  @oam.write_byte(address:, value:)
end

#write_vram(address:, value:) ⇒ void

This method returns an undefined value.

Stores a 8-bit value in VRAM in a given address.

Parameters:

  • address (Integer)
  • value (Integer)
  • address: (Integer)
  • value: (Integer)


136
137
138
# File 'lib/amaterasu/game_boy/ppu.rb', line 136

def write_vram(address:, value:)
  @vram.write_byte(address:, value:)
end

#wx=void (readonly)

This method returns an undefined value.

Parameters:

  • value (Integer)


39
# File 'sig/akane/game_boy/ppu.rbs', line 39

def wx=: (Integer value) -> void

#wy=void (readonly)

This method returns an undefined value.

Parameters:

  • value (Integer)


38
# File 'sig/akane/game_boy/ppu.rbs', line 38

def wy=: (Integer value) -> void