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

cycle[RW]

@note For debug use. @param [Integer] @return [Integer]

image[R]

@note For debug use. @return [Array<Rnes::Image>]

line[RW]

@note For debug use. @param [Integer] @return [Integer]

registers[R]

@note For debug use. @return [Rnes::PpuRegisters]

Public Class Methods

new(bus:, interrupt_line:, renderer:) click to toggle source

@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

read(address) click to toggle source

@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
step() click to toggle source
# 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
transfer_sprite_data(index:, value:) click to toggle source

@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
write(address, value) click to toggle source

@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

assert_nmi() click to toggle source
# File lib/rnes/ppu.rb, line 168
def assert_nmi
  @interrupt_line.assert_nmi
end
background_pattern_index() click to toggle source

-----------———–+ | 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
background_pattern_index_in_window() click to toggle source

@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
background_pattern_index_page() click to toggle source

@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
background_pattern_index_paging_offset() click to toggle source

@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
base_background_pattern_table_address() click to toggle source

@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
base_name_table_address() click to toggle source

@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
base_sprite_pattern_table_address() click to toggle source

@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
block_id_in_encoded_attributes() click to toggle source

---—+ | 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
check_sprite_hit() click to toggle source
# 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
clear_sprite_hit() click to toggle source
# File lib/rnes/ppu.rb, line 211
def clear_sprite_hit
  registers.sprite_hit = false
end
clear_v_blank() click to toggle source
# File lib/rnes/ppu.rb, line 215
def clear_v_blank
  registers.in_v_blank = false
end
deassert_nmi() click to toggle source
# File lib/rnes/ppu.rb, line 219
def deassert_nmi
  @interrupt_line.deassert_nmi
end
draw_background_8pixels() click to toggle source
# 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
draw_sprites() click to toggle source

@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
object_attribute_index() click to toggle source

@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
on_bottom_end_line?() click to toggle source

@return [Boolean]

# File lib/rnes/ppu.rb, line 298
def on_bottom_end_line?
  line == WINDOW_HEIGHT + V_BLANK_HEIGHT
end
on_line_to_start_v_blank?() click to toggle source

@return [Boolean]

# File lib/rnes/ppu.rb, line 303
def on_line_to_start_v_blank?
  line == WINDOW_HEIGHT
end
on_right_end_cycle?() click to toggle source

@return [Boolean]

# File lib/rnes/ppu.rb, line 308
def on_right_end_cycle?
  cycle == CYCLES_PER_LINE - 1
end
on_visible_cycle?() click to toggle source

@return [Boolean]

# File lib/rnes/ppu.rb, line 313
def on_visible_cycle?
  (0...WINDOW_WIDTH).cover?(x) && (0...WINDOW_HEIGHT).cover?(y)
end
palette_data_requested?() click to toggle source

@return [Boolean]

# File lib/rnes/ppu.rb, line 318
def palette_data_requested?
  PALETTE_ADDRESS_RANGE.cover?(@registers.video_ram_address % 0x4000)
end
read_background_pattern_line(index) click to toggle source

@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
read_color_id(index) click to toggle source

@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
read_from_sprite_ram(address) click to toggle source

@param [Integer] address @return [Integer]

# File lib/rnes/ppu.rb, line 336
def read_from_sprite_ram(address)
  @sprite_ram.read(address)
end
read_from_video_ram_for_cpu() click to toggle source

@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
read_object_attribute(index) click to toggle source

@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
read_pattern_index(index) click to toggle source

@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
read_pattern_line(index) click to toggle source

@param [Integer] index @return [Integer]

# File lib/rnes/ppu.rb, line 361
def read_pattern_line(index)
  @bus.read(index)
end
read_sprite_pattern_line(index) click to toggle source

@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
render_image() click to toggle source
# File lib/rnes/ppu.rb, line 377
def render_image
  @renderer.render(@image)
end
set_v_blank() click to toggle source
# File lib/rnes/ppu.rb, line 381
def set_v_blank
  registers.in_v_blank = true
end
v_blank_interrupt_enabled?() click to toggle source

@return [Boolean]

# File lib/rnes/ppu.rb, line 411
def v_blank_interrupt_enabled?
  @registers.has_v_blank_irq_enabled_bit?
end
video_ram_address_offset() click to toggle source

@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
write_to_sprite_ram_for_cpu(value) click to toggle source

@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
write_to_video_ram_for_cpu(value) click to toggle source

@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
x() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 437
def x
  cycle - 1
end
x_in_tile() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 442
def x_in_tile
  x_with_scroll % TILE_WIDTH
end
x_of_block() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 447
def x_of_block
  x_with_scroll / BLOCK_WIDTH
end
x_of_encoded_attributes() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 452
def x_of_encoded_attributes
  x_with_scroll / ENCODED_ATTRIBUTES_WIDTH
end
x_of_tile() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 457
def x_of_tile
  x_with_scroll / TILE_WIDTH
end
x_with_scroll() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 462
def x_with_scroll
  x + @registers.scroll_x
end
y() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 467
def y
  line
end
y_in_tile() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 472
def y_in_tile
  y_with_scroll % TILE_HEIGHT
end
y_of_block() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 477
def y_of_block
  y_with_scroll / BLOCK_HEIGHT
end
y_of_encoded_attributes() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 482
def y_of_encoded_attributes
  y_with_scroll / ENCODED_ATTRIBUTES_HEIGHT
end
y_of_tile() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 487
def y_of_tile
  y_with_scroll / TILE_HEIGHT
end
y_with_scroll() click to toggle source

@return [Integer]

# File lib/rnes/ppu.rb, line 492
def y_with_scroll
  y + @registers.scroll_y
end