class Rnes::Ppu
Constants
- ADDRESS_TO_FINISH_SPRITE_PALETTE_TABLE
- ADDRESS_TO_START_ATTRIBUTE_TABLE
- ADDRESS_TO_START_BACKGROUND_PALETTE_TABLE
- ADDRESS_TO_START_NAME_TABLE
- ADDRESS_TO_START_SPRITE_PALETTE_TABLE
- BLOCK_HEIGHT
- BLOCK_WIDTH
- COLORS
- CYCLES_PER_LINE
- ENCODED_ATTRIBUTES_COUNT_IN_HORIZONTAL_LINE
- ENCODED_ATTRIBUTES_COUNT_IN_VERTICAL_LINE
- ENCODED_ATTRIBUTES_HEIGHT
- ENCODED_ATTRIBUTES_WIDTH
- PALETTE_ADDRESS_RANGE
- SPRITES_COUNT
- SPRITE_RAM_BYTESIZE
- TILES_COUNT_IN_HORIZONTAL_LINE
- TILES_COUNT_IN_VERTICAL_LINE
- TILES_COUNT_IN_WINDOW
- TILE_HEIGHT
- TILE_WIDTH
- V_BLANK_HEIGHT
- WINDOW_HEIGHT
- WINDOW_WIDTH
Attributes
@note For debug use. @param [Integer] @return [Integer]
@note For debug use. @return [Array<Rnes::Image>]
@note For debug use. @param [Integer] @return [Integer]
@note For debug use. @return [Rnes::PpuRegisters]
Public Class Methods
@param [Rnes::PpuBus] bus @param [Rnes::InterruptLine] interrupt_line @param [Rnes::TerminalRenderer] renderer
# File lib/rnes/ppu.rb, line 76 def initialize(bus:, interrupt_line:, renderer:) @bus = bus @cycle = 0 @image = ::Rnes::Image.new(height: WINDOW_HEIGHT, width: WINDOW_WIDTH) @interrupt_line = interrupt_line @line = 0 @registers = ::Rnes::PpuRegisters.new @renderer = renderer @sprite_ram = ::Rnes::Ram.new(bytesize: SPRITE_RAM_BYTESIZE) @video_ram_reading_buffer = 0x00 end
Public Instance Methods
@param [Integer] address @return [Integer]
# File lib/rnes/ppu.rb, line 90 def read(address) case address when 0x0000 @registers.control when 0x0001 @registers.mask when 0x0002 @registers.status when 0x0004 read_from_sprite_ram(@registers.sprite_ram_address) when 0x0007 read_from_video_ram_for_cpu else raise ::Rnes::Errors::InvalidPpuAddressError, address end end
# File lib/rnes/ppu.rb, line 107 def step if on_visible_cycle? && x_in_tile.zero? draw_background_8pixels end if on_right_end_cycle? self.cycle = 0 if on_bottom_end_line? self.line = 0 deassert_nmi clear_sprite_hit clear_v_blank draw_sprites render_image else self.line += 1 check_sprite_hit if on_line_to_start_v_blank? set_v_blank if v_blank_interrupt_enabled? assert_nmi end end end else self.cycle += 1 end end
@param [Integer] index @param [Integer] value
# File lib/rnes/ppu.rb, line 137 def transfer_sprite_data(index:, value:) address = (@registers.sprite_ram_address + index) % SPRITE_RAM_BYTESIZE @sprite_ram.write(address, value) end
@param [Integer] address @param [Integer] value @return [Integer]
# File lib/rnes/ppu.rb, line 145 def write(address, value) case address when 0x0000 @registers.control = value when 0x0001 @registers.mask = value when 0x0003 @registers.sprite_ram_address = value when 0x0004 write_to_sprite_ram_for_cpu(value) when 0x0005 @registers.scroll = value when 0x0006 @registers.video_ram_address = value when 0x0007 write_to_video_ram_for_cpu(value) else raise ::Rnes::Errors::InvalidPpuAddressError, address end end
Private Instance Methods
# File lib/rnes/ppu.rb, line 168 def assert_nmi @interrupt_line.assert_nmi end
-----------
———–+ | 0(0x0000) | 1(0x0400) | -----------
———–+ | 2(0x0800) | 3(0x0C00) | -----------
———–+ @return [Integer] Integer from 0x0000 to 0x0FC0.
# File lib/rnes/ppu.rb, line 391 def background_pattern_index background_pattern_index_in_window + background_pattern_index_paging_offset end
@return [Integer] Integer from 0x0000 to 0x03C0.
# File lib/rnes/ppu.rb, line 396 def background_pattern_index_in_window (y_of_tile % TILES_COUNT_IN_VERTICAL_LINE) * TILES_COUNT_IN_HORIZONTAL_LINE + x_of_tile % TILES_COUNT_IN_HORIZONTAL_LINE end
@return [Integer] Integer from 0 to 3.
# File lib/rnes/ppu.rb, line 401 def background_pattern_index_page x_of_tile / TILES_COUNT_IN_HORIZONTAL_LINE + y_of_tile / TILES_COUNT_IN_VERTICAL_LINE * 2 end
@return [Integer] 0x0000, 0x0400, 0x0800, or 0x0C00.
# File lib/rnes/ppu.rb, line 406 def background_pattern_index_paging_offset background_pattern_index_page * 0x0400 end
@return [Integer]
# File lib/rnes/ppu.rb, line 178 def base_background_pattern_table_address if registers.background_pattern_table_address_banked? 0x1000 else 0x0000 end end
@return [Integer]
# File lib/rnes/ppu.rb, line 173 def base_name_table_address ADDRESS_TO_START_NAME_TABLE + @registers.base_name_table_id * 0x400 end
@return [Integer]
# File lib/rnes/ppu.rb, line 187 def base_sprite_pattern_table_address if registers.sprite_pattern_table_address_banked? 0x1000 else 0x0000 end end
---
—+ | 0 | 1 | ---
—+ | 2 | 3 | ---
—+ @return [Integer] Integer from 0 to 3.
# File lib/rnes/ppu.rb, line 201 def block_id_in_encoded_attributes (x_of_block.even? ? 0 : 1) + (y_of_block.even? ? 0 : 2) end
# File lib/rnes/ppu.rb, line 205 def check_sprite_hit if read_from_sprite_ram(0) == y && @registers.background_enabled? && @registers.sprite_enabled? registers.sprite_hit = true end end
# File lib/rnes/ppu.rb, line 211 def clear_sprite_hit registers.sprite_hit = false end
# File lib/rnes/ppu.rb, line 215 def clear_v_blank registers.in_v_blank = false end
# File lib/rnes/ppu.rb, line 219 def deassert_nmi @interrupt_line.deassert_nmi end
# File lib/rnes/ppu.rb, line 223 def draw_background_8pixels pattern_index = read_pattern_index(background_pattern_index) pattern_line_low_byte_address = TILE_HEIGHT * 2 * pattern_index + y_in_tile pattern_line_low_byte = read_background_pattern_line(pattern_line_low_byte_address) pattern_line_high_byte = read_background_pattern_line(pattern_line_low_byte_address + TILE_HEIGHT) palette_ids_byte = read_object_attribute(object_attribute_index) palette_id = (palette_ids_byte >> (block_id_in_encoded_attributes * 2)) & 0b11 TILE_WIDTH.times do |x_in_pattern| index_in_pattern_line_byte = TILE_WIDTH - 1 - x_in_pattern background_palette_index = pattern_line_low_byte[index_in_pattern_line_byte] | (pattern_line_high_byte[index_in_pattern_line_byte] << 1) | (palette_id << 2) color_id = read_color_id(background_palette_index) @image.write( value: ::Rnes::Ppu::COLORS[color_id], x: x + x_in_pattern, y: y, ) end end
@note
struct Sprite { U8 y; U8 tile; U8 attr; U8 x; }
attr 76543210
||| `+- palette ||`------ priority (0: front, 1: back) |`------- horizontal flip `-------- vertical flip
# File lib/rnes/ppu.rb, line 257 def draw_sprites 0.step(SPRITES_COUNT - 1, 4) do |base_sprite_ram_address| y_for_sprite = read_from_sprite_ram(base_sprite_ram_address) pattern_index = read_from_sprite_ram(base_sprite_ram_address + 1) sprite_attribute_byte = read_from_sprite_ram(base_sprite_ram_address + 2) x_for_sprite = read_from_sprite_ram(base_sprite_ram_address + 3) palette_id = sprite_attribute_byte & 0b11 reversed_horizontally = sprite_attribute_byte[6] == 1 reversed_vertically = sprite_attribute_byte[7] == 1 TILE_HEIGHT.times do |y_in_pattern| pattern_line_low_byte_address = TILE_HEIGHT * 2 * pattern_index + y_in_pattern pattern_line_low_byte = read_sprite_pattern_line(pattern_line_low_byte_address) pattern_line_high_byte = read_sprite_pattern_line(pattern_line_low_byte_address + TILE_HEIGHT) TILE_WIDTH.times do |x_in_pattern| index_in_pattern_line_byte = TILE_WIDTH - 1 - x_in_pattern sprite_palette_index = pattern_line_low_byte[index_in_pattern_line_byte] | (pattern_line_high_byte[index_in_pattern_line_byte] << 1) | (palette_id << 2) if sprite_palette_index % 4 != 0 color_id = read_color_id(sprite_palette_index) y_in_pattern = TILE_HEIGHT - 1 - y_in_pattern if reversed_vertically x_in_pattern = TILE_WIDTH - 1 - x_in_pattern if reversed_horizontally @image.write( value: ::Rnes::Ppu::COLORS[color_id], x: x_for_sprite + x_in_pattern, y: y_for_sprite + y_in_pattern, ) end end end end end
@return [Integer] Integer from 0 to 63.
# File lib/rnes/ppu.rb, line 291 def object_attribute_index (y_of_encoded_attributes % ENCODED_ATTRIBUTES_COUNT_IN_VERTICAL_LINE) * ENCODED_ATTRIBUTES_COUNT_IN_HORIZONTAL_LINE + x_of_encoded_attributes % ENCODED_ATTRIBUTES_COUNT_IN_HORIZONTAL_LINE + background_pattern_index_paging_offset end
@return [Boolean]
# File lib/rnes/ppu.rb, line 298 def on_bottom_end_line? line == WINDOW_HEIGHT + V_BLANK_HEIGHT end
@return [Boolean]
# File lib/rnes/ppu.rb, line 303 def on_line_to_start_v_blank? line == WINDOW_HEIGHT end
@return [Boolean]
# File lib/rnes/ppu.rb, line 308 def on_right_end_cycle? cycle == CYCLES_PER_LINE - 1 end
@return [Boolean]
# File lib/rnes/ppu.rb, line 313 def on_visible_cycle? (0...WINDOW_WIDTH).cover?(x) && (0...WINDOW_HEIGHT).cover?(y) end
@return [Boolean]
# File lib/rnes/ppu.rb, line 318 def palette_data_requested? PALETTE_ADDRESS_RANGE.cover?(@registers.video_ram_address % 0x4000) end
@param [Integer] index. @return [Integer]
# File lib/rnes/ppu.rb, line 324 def read_background_pattern_line(index) read_pattern_line(base_background_pattern_table_address + index) end
@param [Integer] index @return [Integer]
# File lib/rnes/ppu.rb, line 330 def read_color_id(index) @bus.read(ADDRESS_TO_START_BACKGROUND_PALETTE_TABLE + index) end
@param [Integer] address @return [Integer]
# File lib/rnes/ppu.rb, line 336 def read_from_sprite_ram(address) @sprite_ram.read(address) end
@return [Integer]
# File lib/rnes/ppu.rb, line 341 def read_from_video_ram_for_cpu if palette_data_requested? value = @bus.read(@registers.video_ram_address) @video_ram_reading_buffer = @bus.read(@registers.video_ram_address - 0x1000) else value = @video_ram_reading_buffer @video_ram_reading_buffer = @bus.read(@registers.video_ram_address) end @registers.increment_video_ram_address(video_ram_address_offset) value end
@param [Integer] index @return [Integer] 4-color-palette IDs of 4 blocks, as 8 bit data.
# File lib/rnes/ppu.rb, line 355 def read_object_attribute(index) @bus.read(ADDRESS_TO_START_ATTRIBUTE_TABLE + index) end
@param [Integer] index @return [Integer]
# File lib/rnes/ppu.rb, line 373 def read_pattern_index(index) @bus.read(base_name_table_address + index) end
@param [Integer] index @return [Integer]
# File lib/rnes/ppu.rb, line 361 def read_pattern_line(index) @bus.read(index) end
@param [Integer] index. @return [Integer]
# File lib/rnes/ppu.rb, line 367 def read_sprite_pattern_line(index) read_pattern_line(base_sprite_pattern_table_address + index) end
# File lib/rnes/ppu.rb, line 377 def render_image @renderer.render(@image) end
# File lib/rnes/ppu.rb, line 381 def set_v_blank registers.in_v_blank = true end
@return [Boolean]
# File lib/rnes/ppu.rb, line 411 def v_blank_interrupt_enabled? @registers.has_v_blank_irq_enabled_bit? end
@return [Integer]
# File lib/rnes/ppu.rb, line 416 def video_ram_address_offset if registers.horizontal_increment? TILES_COUNT_IN_HORIZONTAL_LINE else 1 end end
@param [Integer] value
# File lib/rnes/ppu.rb, line 425 def write_to_sprite_ram_for_cpu(value) @sprite_ram.write(@registers.sprite_ram_address, value) @registers.sprite_ram_address = (@registers.sprite_ram_address + 1) & 0xFF end
@param [Integer] value
# File lib/rnes/ppu.rb, line 431 def write_to_video_ram_for_cpu(value) @bus.write(@registers.video_ram_address, value) @registers.increment_video_ram_address(video_ram_address_offset) end
@return [Integer]
# File lib/rnes/ppu.rb, line 437 def x cycle - 1 end
@return [Integer]
# File lib/rnes/ppu.rb, line 442 def x_in_tile x_with_scroll % TILE_WIDTH end
@return [Integer]
# File lib/rnes/ppu.rb, line 447 def x_of_block x_with_scroll / BLOCK_WIDTH end
@return [Integer]
# File lib/rnes/ppu.rb, line 452 def x_of_encoded_attributes x_with_scroll / ENCODED_ATTRIBUTES_WIDTH end
@return [Integer]
# File lib/rnes/ppu.rb, line 457 def x_of_tile x_with_scroll / TILE_WIDTH end
@return [Integer]
# File lib/rnes/ppu.rb, line 462 def x_with_scroll x + @registers.scroll_x end
@return [Integer]
# File lib/rnes/ppu.rb, line 467 def y line end
@return [Integer]
# File lib/rnes/ppu.rb, line 472 def y_in_tile y_with_scroll % TILE_HEIGHT end
@return [Integer]
# File lib/rnes/ppu.rb, line 477 def y_of_block y_with_scroll / BLOCK_HEIGHT end
@return [Integer]
# File lib/rnes/ppu.rb, line 482 def y_of_encoded_attributes y_with_scroll / ENCODED_ATTRIBUTES_HEIGHT end
@return [Integer]
# File lib/rnes/ppu.rb, line 487 def y_of_tile y_with_scroll / TILE_HEIGHT end
@return [Integer]
# File lib/rnes/ppu.rb, line 492 def y_with_scroll y + @registers.scroll_y end