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/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

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 =
456
MAX_SPRITES_PER_SCANLINE =
10

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils::BitOps

bit, clear_bit, set_bit

Constructor Details

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

Returns a new instance of Ppu.



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

#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

#registersObject (readonly)

Returns the value of attribute registers.



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

def registers
  @registers
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

#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

#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

#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

#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

#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

#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

#read_oam(address:) ⇒ Object

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



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)

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

#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

#tickObject

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

#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:) ⇒ Object

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



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:) ⇒ Object

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

Parameters:

  • 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