Class: Amaterasu::GameBoy::Ppu
- Inherits:
-
Object
- Object
- Amaterasu::GameBoy::Ppu
- 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
-
#dots ⇒ Object
readonly
Returns the value of attribute dots.
-
#framebuffer ⇒ Object
readonly
Returns the value of attribute framebuffer.
-
#registers ⇒ Object
readonly
Returns the value of attribute registers.
-
#sprite_buffer ⇒ Object
readonly
Returns the value of attribute sprite_buffer.
-
#window_y_count ⇒ Object
Returns the value of attribute window_y_count.
-
#wy_eq_ly ⇒ Object
Returns the value of attribute wy_eq_ly.
Instance Method Summary collapse
- #bg_tile_map ⇒ TileMap
- #bg_win_tile_data ⇒ TileData
-
#draw_frame ⇒ Object
Delegates the draw to the chosen Renderer.
- #fetch_sprite_at(index) ⇒ Object
- #increment_ly ⇒ Object
-
#initialize(vram, oam, display, interrupts, skip_boot_rom: true, trace_ppu: false) ⇒ Ppu
constructor
A new instance of Ppu.
- #ly_compare ⇒ Object
- #obj_tile_data ⇒ TileData
-
#read_oam(address:) ⇒ Object
Returns a 8-bit value stored in OAM in a given address.
-
#read_vram(address:) ⇒ Integer
Returns a 8-bit value stored in VRAM in a given address.
- #request_interrupt(interrupt_type) ⇒ Object
- #reset_for_scanline ⇒ Object
-
#reset_states ⇒ Object
Restarts the rendering pipeline state.
-
#set_mode(mode) ⇒ Object
Sets the current PPU mode to be ticked.
-
#tick ⇒ Object
Core PPU state machine.
- #window_tile_map ⇒ TileMap
-
#write_oam(address:, value:) ⇒ Object
Stores a 8-bit value in OAM in a given address.
-
#write_vram(address:, value:) ⇒ Object
Stores a 8-bit value in VRAM in a given address.
Methods included from Utils::BitOps
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
#dots ⇒ Object (readonly)
Returns the value of attribute dots.
29 30 31 |
# File 'lib/amaterasu/game_boy/ppu.rb', line 29 def dots @dots end |
#framebuffer ⇒ Object (readonly)
Returns the value of attribute framebuffer.
29 30 31 |
# File 'lib/amaterasu/game_boy/ppu.rb', line 29 def framebuffer @framebuffer end |
#registers ⇒ Object (readonly)
Returns the value of attribute registers.
29 30 31 |
# File 'lib/amaterasu/game_boy/ppu.rb', line 29 def registers @registers end |
#sprite_buffer ⇒ Object (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_count ⇒ Object
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_ly ⇒ Object
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_map ⇒ 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_data ⇒ 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_frame ⇒ Object
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_ly ⇒ Object
106 107 108 109 110 |
# File 'lib/amaterasu/game_boy/ppu.rb', line 106 def increment_ly @registers.ly += 1 ly_compare end |
#ly_compare ⇒ Object
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_data ⇒ 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.
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_scanline ⇒ Object
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_states ⇒ Object
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.
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 |
#tick ⇒ Object
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_map ⇒ 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.
136 137 138 |
# File 'lib/amaterasu/game_boy/ppu.rb', line 136 def write_vram(address:, value:) @vram.write_byte(address:, value:) end |