amaterasu 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +1 -0
- data/.rubocop.yml +54 -0
- data/Gemfile +32 -0
- data/Gemfile.lock +267 -0
- data/LICENSE +21 -0
- data/README.md +115 -0
- data/Steepfile +7 -0
- data/exe/amaterasu +23 -0
- data/lib/amaterasu/cartridge/mbc1.rb +56 -0
- data/lib/amaterasu/cartridge/rom.rb +118 -0
- data/lib/amaterasu/cartridge.rb +68 -0
- data/lib/amaterasu/cli.rb +60 -0
- data/lib/amaterasu/emulator.rb +121 -0
- data/lib/amaterasu/game_boy/apu.rb +12 -0
- data/lib/amaterasu/game_boy/bus.rb +161 -0
- data/lib/amaterasu/game_boy/cpu/instructions/adc.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/add16.rb +73 -0
- data/lib/amaterasu/game_boy/cpu/instructions/add8.rb +63 -0
- data/lib/amaterasu/game_boy/cpu/instructions/and.rb +62 -0
- data/lib/amaterasu/game_boy/cpu/instructions/base.rb +38 -0
- data/lib/amaterasu/game_boy/cpu/instructions/call.rb +48 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_bit.rb +52 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_res.rb +49 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rl.rb +70 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rlc.rb +68 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rr.rb +70 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_rrc.rb +68 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_set.rb +51 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_sla.rb +69 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_sra.rb +71 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_srl.rb +69 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cb_swap.rb +67 -0
- data/lib/amaterasu/game_boy/cpu/instructions/cp.rb +61 -0
- data/lib/amaterasu/game_boy/cpu/instructions/daa.rb +59 -0
- data/lib/amaterasu/game_boy/cpu/instructions/dec.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/di.rb +21 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ei.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/halt.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/inc.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/jp.rb +54 -0
- data/lib/amaterasu/game_boy/cpu/instructions/jr.rb +45 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ld16.rb +79 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ld8.rb +210 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ldh.rb +61 -0
- data/lib/amaterasu/game_boy/cpu/instructions/misc.rb +53 -0
- data/lib/amaterasu/game_boy/cpu/instructions/nop.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/or.rb +56 -0
- data/lib/amaterasu/game_boy/cpu/instructions/pop.rb +39 -0
- data/lib/amaterasu/game_boy/cpu/instructions/push.rb +43 -0
- data/lib/amaterasu/game_boy/cpu/instructions/ret.rb +70 -0
- data/lib/amaterasu/game_boy/cpu/instructions/rotate.rb +120 -0
- data/lib/amaterasu/game_boy/cpu/instructions/rst.rb +33 -0
- data/lib/amaterasu/game_boy/cpu/instructions/sbc.rb +64 -0
- data/lib/amaterasu/game_boy/cpu/instructions/stop.rb +19 -0
- data/lib/amaterasu/game_boy/cpu/instructions/sub.rb +63 -0
- data/lib/amaterasu/game_boy/cpu/instructions/xor.rb +60 -0
- data/lib/amaterasu/game_boy/cpu/instructions.rb +600 -0
- data/lib/amaterasu/game_boy/cpu/registers.rb +264 -0
- data/lib/amaterasu/game_boy/cpu.rb +232 -0
- data/lib/amaterasu/game_boy/dma.rb +114 -0
- data/lib/amaterasu/game_boy/interrupts.rb +108 -0
- data/lib/amaterasu/game_boy/joypad.rb +127 -0
- data/lib/amaterasu/game_boy/oam/sprite.rb +106 -0
- data/lib/amaterasu/game_boy/oam.rb +29 -0
- data/lib/amaterasu/game_boy/ppu/modes/disabled.rb +29 -0
- data/lib/amaterasu/game_boy/ppu/modes/h_blank.rb +45 -0
- data/lib/amaterasu/game_boy/ppu/modes/oam_scan.rb +93 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/bg_win_fetcher.rb +204 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/pixel_emitter.rb +83 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/pixel_fifo.rb +70 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering/sprite_fetcher.rb +140 -0
- data/lib/amaterasu/game_boy/ppu/modes/rendering.rb +108 -0
- data/lib/amaterasu/game_boy/ppu/modes/v_blank.rb +43 -0
- data/lib/amaterasu/game_boy/ppu/modes.rb +22 -0
- data/lib/amaterasu/game_boy/ppu/registers/lcd_control.rb +57 -0
- data/lib/amaterasu/game_boy/ppu/registers/lcd_status.rb +88 -0
- data/lib/amaterasu/game_boy/ppu/registers.rb +131 -0
- data/lib/amaterasu/game_boy/ppu.rb +207 -0
- data/lib/amaterasu/game_boy/ram.rb +70 -0
- data/lib/amaterasu/game_boy/serial.rb +91 -0
- data/lib/amaterasu/game_boy/timer.rb +230 -0
- data/lib/amaterasu/game_boy/vram/tile.rb +68 -0
- data/lib/amaterasu/game_boy/vram/tile_data.rb +52 -0
- data/lib/amaterasu/game_boy/vram/tile_map.rb +71 -0
- data/lib/amaterasu/game_boy/vram.rb +51 -0
- data/lib/amaterasu/hal/console.rb +23 -0
- data/lib/amaterasu/hal/sdl2/bindings.rb +59 -0
- data/lib/amaterasu/hal/sdl2.rb +127 -0
- data/lib/amaterasu/utils/bit_ops.rb +22 -0
- data/lib/amaterasu.rb +13 -0
- data/sig/akane/cartridge/rom.rbs +29 -0
- data/sig/akane/cartridge.rbs +12 -0
- data/sig/akane/cli.rbs +16 -0
- data/sig/akane/emulator.rbs +19 -0
- data/sig/akane/game_boy/apu.rbs +7 -0
- data/sig/akane/game_boy/bus.rbs +25 -0
- data/sig/akane/game_boy/cpu/instructions/adc.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/add16.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/add8.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/and.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/base.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/call.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/cb_bit.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/cb_res.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rl.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rlc.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rr.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_rrc.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_set.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/cb_sla.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_sra.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_srl.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cb_swap.rbs +21 -0
- data/sig/akane/game_boy/cpu/instructions/cp.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/daa.rbs +17 -0
- data/sig/akane/game_boy/cpu/instructions/dec.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/di.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/ei.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/halt.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/inc.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/jp.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/jr.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/ld16.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/ld8.rbs +31 -0
- data/sig/akane/game_boy/cpu/instructions/ldh.rbs +23 -0
- data/sig/akane/game_boy/cpu/instructions/misc.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/nop.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/or.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/pop.rbs +17 -0
- data/sig/akane/game_boy/cpu/instructions/push.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/ret.rbs +20 -0
- data/sig/akane/game_boy/cpu/instructions/rotate.rbs +23 -0
- data/sig/akane/game_boy/cpu/instructions/rst.rbs +17 -0
- data/sig/akane/game_boy/cpu/instructions/sbc.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions/stop.rbs +13 -0
- data/sig/akane/game_boy/cpu/instructions/sub.rbs +18 -0
- data/sig/akane/game_boy/cpu/instructions/xor.rbs +19 -0
- data/sig/akane/game_boy/cpu/instructions.rbs +12 -0
- data/sig/akane/game_boy/cpu/registers.rbs +56 -0
- data/sig/akane/game_boy/cpu.rbs +39 -0
- data/sig/akane/game_boy/interrupts.rbs +28 -0
- data/sig/akane/game_boy/joypad.rbs +25 -0
- data/sig/akane/game_boy/oam/sprite.rbs +30 -0
- data/sig/akane/game_boy/ppu/modes/disabled.rbs +17 -0
- data/sig/akane/game_boy/ppu/modes/h_blank.rbs +20 -0
- data/sig/akane/game_boy/ppu/modes/oam_scan.rbs +28 -0
- data/sig/akane/game_boy/ppu/modes/rendering.rbs +26 -0
- data/sig/akane/game_boy/ppu/modes/v_blank.rbs +20 -0
- data/sig/akane/game_boy/ppu/modes.rbs +13 -0
- data/sig/akane/game_boy/ppu.rbs +59 -0
- data/sig/akane/game_boy/ram.rbs +16 -0
- data/sig/akane/game_boy/serial.rbs +21 -0
- data/sig/akane/game_boy/timer.rbs +30 -0
- data/sig/akane/utils/bit_ops.rbs +11 -0
- data/sig/akane.rbs +3 -0
- metadata +226 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Ppu
|
|
6
|
+
module Modes
|
|
7
|
+
class Rendering
|
|
8
|
+
# Responsible for emitting pixels from the Pixel FIFO to the Display.
|
|
9
|
+
class PixelEmitter
|
|
10
|
+
PIXELS_PER_SCANLINE = 160
|
|
11
|
+
|
|
12
|
+
def initialize(ppu, bg_win_fifo, sprite_fifo)
|
|
13
|
+
@ppu = ppu
|
|
14
|
+
@bg_win_fifo = bg_win_fifo
|
|
15
|
+
@sprite_fifo = sprite_fifo
|
|
16
|
+
|
|
17
|
+
@pixels_discarded = 0
|
|
18
|
+
@pixels_emitted = 0
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Called each T-cycle.
|
|
22
|
+
def tick?
|
|
23
|
+
return false if @bg_win_fifo.empty?
|
|
24
|
+
return false unless @pixels_emitted < PIXELS_PER_SCANLINE
|
|
25
|
+
|
|
26
|
+
@popped_sprite_pixel = @sprite_fifo.pop_pixel
|
|
27
|
+
@popped_bg_win_pixel = @bg_win_fifo.pop_pixel
|
|
28
|
+
|
|
29
|
+
shaded_priority_pixel = define_pixel_priority
|
|
30
|
+
|
|
31
|
+
# TODO: Implement framebuffer fixed size [lcd_y * width + lcd_x]
|
|
32
|
+
@ppu.framebuffer << shaded_priority_pixel
|
|
33
|
+
@pixels_emitted += 1
|
|
34
|
+
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def reset_for_scanline
|
|
39
|
+
@pixels_emitted = 0
|
|
40
|
+
@pixels_discarded = 0
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_s
|
|
44
|
+
"BG WIN FIFO: #{@bg_win_fifo.pixels} | " \
|
|
45
|
+
"Popped: #{@popped_pixel} (##{format('%d', @pixels_discarded)})"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def define_pixel_priority
|
|
51
|
+
return shaded_sprite_pixel if show_sprite?
|
|
52
|
+
|
|
53
|
+
shaded_bg_pixel
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def show_sprite?
|
|
57
|
+
return false unless @ppu.registers.lcdc.obj_enabled?
|
|
58
|
+
return false if @popped_sprite_pixel.nil?
|
|
59
|
+
return false if (@popped_sprite_pixel & 0b11) == 0b00
|
|
60
|
+
return false if ((@popped_sprite_pixel & 0b1000) != 0) && (@popped_bg_win_pixel != 0b00)
|
|
61
|
+
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def shaded_sprite_pixel
|
|
66
|
+
if (@popped_sprite_pixel & 0b100) == 0
|
|
67
|
+
@ppu.registers.sprite_palettes0[@popped_sprite_pixel & 0b11]
|
|
68
|
+
else
|
|
69
|
+
@ppu.registers.sprite_palettes1[@popped_sprite_pixel & 0b11]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def shaded_bg_pixel
|
|
74
|
+
return 0b00 unless @ppu.registers.lcdc.bg_win_enabled?
|
|
75
|
+
|
|
76
|
+
@ppu.registers.bg_palettes[@popped_bg_win_pixel]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Ppu
|
|
6
|
+
module Modes
|
|
7
|
+
class Rendering
|
|
8
|
+
# Models the PPU Pixel FIFO from the original Game Boy (DMG).
|
|
9
|
+
class PixelFifo
|
|
10
|
+
MAX_PIXELS = 16 # or 8?
|
|
11
|
+
|
|
12
|
+
# Array of raw color indices (0 - 3).
|
|
13
|
+
attr_reader :pixels
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@pixel_buffer = Array.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The Pixel Fetcher is the pixel producer,
|
|
20
|
+
# it produces 8 color indices (1 for each pixel) and attempts
|
|
21
|
+
# to push all 8 at the same time until it succeeds.
|
|
22
|
+
#
|
|
23
|
+
# It will only succeed if the FIFO is empty.
|
|
24
|
+
#
|
|
25
|
+
# @return [Boolean] If the push was successful or not.
|
|
26
|
+
def push?(pushed_pixels)
|
|
27
|
+
return false unless @pixel_buffer.empty?
|
|
28
|
+
|
|
29
|
+
pushed_pixels.each { |pp| @pixel_buffer << pp }
|
|
30
|
+
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# This is specific for Sprites.
|
|
35
|
+
#
|
|
36
|
+
def merge(sprite_pixels)
|
|
37
|
+
idx = 0
|
|
38
|
+
|
|
39
|
+
while idx <= 7
|
|
40
|
+
transparent = (@pixel_buffer[idx] & 0b11) == 0b00
|
|
41
|
+
@pixel_buffer[idx] = sprite_pixels[idx] if transparent || @pixel_buffer[idx].nil?
|
|
42
|
+
idx += 1
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# To follow the FIFO rules, elements need to be popped
|
|
47
|
+
# from left to right, that is why we need to use Array#shift.
|
|
48
|
+
#
|
|
49
|
+
# @return [Integer] 2 bit number representing the color id of the pixel.
|
|
50
|
+
def pop_pixel
|
|
51
|
+
return if @pixel_buffer.empty?
|
|
52
|
+
|
|
53
|
+
@pixel_buffer.shift
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Removes all current elements of the FIFO.
|
|
57
|
+
def clear
|
|
58
|
+
@pixel_buffer.clear
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Removes all current elements of the FIFO.
|
|
62
|
+
def empty?
|
|
63
|
+
@pixel_buffer.empty?
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Ppu
|
|
6
|
+
module Modes
|
|
7
|
+
class Rendering
|
|
8
|
+
# Responsible for fetching Sprites tiles, decoding Sprite pixels
|
|
9
|
+
# and outputting them into the Sprite FIFO.
|
|
10
|
+
class SpriteFetcher
|
|
11
|
+
SpritePixel = Struct.new(:bg_win_priority_set, :use_obp1_palette, :color_id)
|
|
12
|
+
|
|
13
|
+
BIT_MASK_BG_WIN_PRIORITY_SET = 1 << 3
|
|
14
|
+
BIT_MASK_USE_OBP1_PALETTE = 1 << 2
|
|
15
|
+
|
|
16
|
+
def initialize(ppu, sprite_fifo)
|
|
17
|
+
@ppu = ppu
|
|
18
|
+
@sprite_fifo = sprite_fifo
|
|
19
|
+
|
|
20
|
+
@step = :fetch_tile_index
|
|
21
|
+
@completed = false
|
|
22
|
+
@fetch_duration = 6
|
|
23
|
+
|
|
24
|
+
@current_sprite = nil
|
|
25
|
+
|
|
26
|
+
@tile_index = nil
|
|
27
|
+
@tile_data_low = nil
|
|
28
|
+
@tile_data_high = nil
|
|
29
|
+
@tile_pixels = nil
|
|
30
|
+
@encoded_pixels = Array.new(8)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def start_for(sprite)
|
|
34
|
+
@current_sprite = sprite
|
|
35
|
+
@step = :fetch_tile_index
|
|
36
|
+
@completed = false
|
|
37
|
+
@fetch_duration = 6
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Core fetch state machine (Takes 2 dots per step).
|
|
41
|
+
#
|
|
42
|
+
def tick
|
|
43
|
+
case @step
|
|
44
|
+
when :fetch_tile_index then fetch_tile_index
|
|
45
|
+
when :fetch_tile_data_low then fetch_tile_data_low
|
|
46
|
+
when :fetch_tile_data_high then fetch_tile_data_high
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def done?
|
|
51
|
+
@completed
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Fetches the Tile index of the current Sprite to be rendered.
|
|
57
|
+
def fetch_tile_index
|
|
58
|
+
@fetch_duration -= 1
|
|
59
|
+
return unless @fetch_duration == 4
|
|
60
|
+
|
|
61
|
+
obj_size_8x16 = @ppu.registers.lcdc.obj_size_8x16?
|
|
62
|
+
y_flipped = @current_sprite.y_flipped?
|
|
63
|
+
|
|
64
|
+
@tile_index = @current_sprite.tile_index(obj_size_8x16, y_flipped, current_obj_y)
|
|
65
|
+
@step = :fetch_tile_data_low
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Fetches the low byte from the Tile Row overlapping the current LY.
|
|
69
|
+
def fetch_tile_data_low
|
|
70
|
+
@fetch_duration -= 1
|
|
71
|
+
return unless @fetch_duration == 2
|
|
72
|
+
|
|
73
|
+
obj_y = @current_sprite.y_flipped? ? (7 - current_obj_y) : current_obj_y
|
|
74
|
+
@tile_data_low = obj_tile_data.tile_at(@tile_index).data_low(obj_y)
|
|
75
|
+
@step = :fetch_tile_data_high
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Fetches the high byte from the Tile Row overlapping the current LY.
|
|
79
|
+
def fetch_tile_data_high
|
|
80
|
+
@fetch_duration -= 1
|
|
81
|
+
return unless @fetch_duration == 0
|
|
82
|
+
|
|
83
|
+
obj_y = @current_sprite.y_flipped? ? (7 - current_obj_y) : current_obj_y
|
|
84
|
+
@tile_data_high = obj_tile_data.tile_at(@tile_index).data_high(obj_y)
|
|
85
|
+
fetch_tile_pixels
|
|
86
|
+
add_pixel_metadata
|
|
87
|
+
merge_into_fifo
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [Array] Decodes 8 pixel color ids from the high | low bytes.
|
|
91
|
+
def fetch_tile_pixels
|
|
92
|
+
@tile_pixels = Vram::Tile::PIXELS_LOOKUP[(@tile_data_high << 8) | @tile_data_low]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Encodes the current OBP color palette and BG/WIN priority into the pixel.
|
|
96
|
+
# This data would be lost downstream (when rendering the pixels to the LCD)
|
|
97
|
+
# if not encoded in this step.
|
|
98
|
+
#
|
|
99
|
+
# @return [void]
|
|
100
|
+
def add_pixel_metadata
|
|
101
|
+
idx = 0
|
|
102
|
+
|
|
103
|
+
while idx < 8
|
|
104
|
+
pixel = @tile_pixels[idx]
|
|
105
|
+
pixel = BIT_MASK_BG_WIN_PRIORITY_SET | pixel if @current_sprite.bg_win_priority_set?
|
|
106
|
+
pixel = BIT_MASK_USE_OBP1_PALETTE | pixel if @current_sprite.use_obp1_palette?
|
|
107
|
+
@encoded_pixels[idx] = pixel
|
|
108
|
+
|
|
109
|
+
idx += 1
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Merges the 8 pixels into the Sprite FIFO.
|
|
114
|
+
def merge_into_fifo
|
|
115
|
+
@encoded_pixels.reverse! if @current_sprite.x_flipped?
|
|
116
|
+
@sprite_fifo.merge(@encoded_pixels)
|
|
117
|
+
@completed = true
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def current_obj_y
|
|
121
|
+
@ppu.registers.ly - @current_sprite.y_screen_pos
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def obj_tile_data
|
|
125
|
+
@ppu.obj_tile_data
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def to_s
|
|
129
|
+
"Step: #{format('%-10s', @action)} | " \
|
|
130
|
+
"IDX: #{@tile_index.nil? ? 'Nil' : format('%02X', @tile_index)} | " \
|
|
131
|
+
"DL: #{@tile_data_low.nil? ? 'Nil' : format('%02X', @tile_data_low)} | " \
|
|
132
|
+
"DH: #{@tile_data_high.nil? ? 'Nil' : format('%02X', @tile_data_high)} | " \
|
|
133
|
+
"PIX: #{@tile_pixels}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Ppu
|
|
6
|
+
module Modes
|
|
7
|
+
# Defines the behavior of the PPU during Rendering mode.
|
|
8
|
+
#
|
|
9
|
+
# The Rendering mode does not have a fixed duration,
|
|
10
|
+
# it can vary between 172 and 289 dots.
|
|
11
|
+
#
|
|
12
|
+
# This mode can be considered completed when 160 pixels
|
|
13
|
+
# are outputted to the LCD. Normally, the PPU can output
|
|
14
|
+
# a pixel per dot, but this is not always the case, there are
|
|
15
|
+
# several scenarios in which "penalties" occur and this
|
|
16
|
+
# causes the mode to take longer.
|
|
17
|
+
class Rendering
|
|
18
|
+
attr_reader :name, :number
|
|
19
|
+
|
|
20
|
+
def initialize(ppu)
|
|
21
|
+
@ppu = ppu
|
|
22
|
+
|
|
23
|
+
@name = 'RENDERING'
|
|
24
|
+
@number = 3
|
|
25
|
+
|
|
26
|
+
@bg_win_fifo = PixelFifo.new
|
|
27
|
+
@sprite_fifo = PixelFifo.new
|
|
28
|
+
@bg_win_fetcher = BgWinFetcher.new(ppu, @bg_win_fifo)
|
|
29
|
+
@sprite_fetcher = SpriteFetcher.new(ppu, @sprite_fifo)
|
|
30
|
+
@pixel_emitter = PixelEmitter.new(ppu, @bg_win_fifo, @sprite_fifo)
|
|
31
|
+
|
|
32
|
+
@sprite_found = nil
|
|
33
|
+
@mode = :fetch_bg
|
|
34
|
+
@lcd_x = 0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def tick
|
|
38
|
+
if any_sprites? && @mode != :fetch_sprite
|
|
39
|
+
@mode = :fetch_sprite
|
|
40
|
+
@sprite_found = @ppu.sprite_buffer.shift
|
|
41
|
+
@sprite_fetcher.start_for(@sprite_found) unless sprite_offscreen?(@sprite_found)
|
|
42
|
+
elsif @mode == :fetch_sprite
|
|
43
|
+
@sprite_fetcher.tick
|
|
44
|
+
@mode = :fetch_bg if @sprite_fetcher.done?
|
|
45
|
+
else
|
|
46
|
+
@bg_win_fetcher.activate_window! if window_reached?
|
|
47
|
+
|
|
48
|
+
@bg_win_fetcher.tick
|
|
49
|
+
pixel_drawn = @pixel_emitter.tick?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@lcd_x += 1 if pixel_drawn
|
|
53
|
+
return unless @lcd_x == PIXELS_PER_SCANLINE && @bg_win_fetcher.step == :sleep
|
|
54
|
+
|
|
55
|
+
@bg_win_fetcher.reset_for_scanline
|
|
56
|
+
@pixel_emitter.reset_for_scanline
|
|
57
|
+
@lcd_x = 0
|
|
58
|
+
@sprite_fifo.clear
|
|
59
|
+
@bg_win_fifo.clear
|
|
60
|
+
@ppu.set_mode(:h_blank)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def inspect
|
|
64
|
+
'#<Amaterasu::GameBoy::Ppu::Modes::Rendering ' \
|
|
65
|
+
"@name='#{@name}' " \
|
|
66
|
+
"@number=#{@number} "
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_s
|
|
70
|
+
if @mode == :fetch_bg
|
|
71
|
+
"#{@name} (##{@number}) | " \
|
|
72
|
+
"LCD X: #{@lcd_x} | " \
|
|
73
|
+
"#{@bg_win_fetcher} | " \
|
|
74
|
+
"#{@pixel_emitter}"
|
|
75
|
+
else
|
|
76
|
+
"#{@name} (##{@number}) | " \
|
|
77
|
+
"LCD X: #{@lcd_x} | " \
|
|
78
|
+
"Sprite: #{@sprite_fetcher}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def any_sprites?
|
|
85
|
+
return false if @ppu.sprite_buffer.empty?
|
|
86
|
+
return false unless @ppu.registers.lcdc.obj_enabled?
|
|
87
|
+
return false unless @lcd_x >= @ppu.sprite_buffer.first.x_screen_pos
|
|
88
|
+
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def sprite_offscreen?(sprite)
|
|
93
|
+
sprite.x < 0 || sprite.x >= 168 # @lcd_x stops at 160
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def window_reached?
|
|
97
|
+
return false if @bg_win_fetcher.fetch_mode == :window
|
|
98
|
+
return false unless @ppu.wy_eq_ly
|
|
99
|
+
return false unless @ppu.registers.lcdc.window_enabled?
|
|
100
|
+
return false unless @lcd_x == @ppu.registers.wx - 7
|
|
101
|
+
|
|
102
|
+
true
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Ppu
|
|
6
|
+
module Modes
|
|
7
|
+
# Defines the behavior of the Ppu during VBlank mode.
|
|
8
|
+
class VBlank
|
|
9
|
+
attr_reader :name, :number
|
|
10
|
+
|
|
11
|
+
def initialize(ppu)
|
|
12
|
+
@ppu = ppu
|
|
13
|
+
|
|
14
|
+
@name = 'VBLANK'
|
|
15
|
+
@number = 1
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tick
|
|
19
|
+
return unless @ppu.dots == DOTS_PER_SCANLINE
|
|
20
|
+
|
|
21
|
+
@ppu.reset_for_scanline
|
|
22
|
+
@ppu.increment_ly
|
|
23
|
+
return unless @ppu.registers.ly == TOTAL_SCANLINES
|
|
24
|
+
|
|
25
|
+
@ppu.draw_frame
|
|
26
|
+
@ppu.reset_states
|
|
27
|
+
@ppu.set_mode(:oam_scan)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def inspect
|
|
31
|
+
'#<Amaterasu::GameBoy::Ppu::Modes::VBlank ' \
|
|
32
|
+
"@name='#{@name}' " \
|
|
33
|
+
"@number=#{@number} "
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_s
|
|
37
|
+
"#{@name} (##{@number}) | WAITING UNTIL THE FRAME IS COMPLETED"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Ppu
|
|
6
|
+
# Instantiates the PPU mode objects that are going to be used.
|
|
7
|
+
module Modes
|
|
8
|
+
def self.build_hash(ppu)
|
|
9
|
+
@modes = Hash.new
|
|
10
|
+
|
|
11
|
+
@modes[:disabled] = Disabled.new(ppu)
|
|
12
|
+
@modes[:h_blank] = HBlank.new(ppu)
|
|
13
|
+
@modes[:v_blank] = VBlank.new(ppu)
|
|
14
|
+
@modes[:oam_scan] = OamScan.new(ppu)
|
|
15
|
+
@modes[:rendering] = Rendering.new(ppu)
|
|
16
|
+
|
|
17
|
+
@modes.freeze
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Ppu
|
|
6
|
+
class Registers
|
|
7
|
+
# Models the behavior of the LCD Control PPU Register.
|
|
8
|
+
class LcdControl
|
|
9
|
+
BIT_MASK_LCD_ENABLE = 1 << 7
|
|
10
|
+
BIT_MASK_WINDOW_TILE_MAP = 1 << 6
|
|
11
|
+
BIT_MASK_WINDOW_ENABLE = 1 << 5
|
|
12
|
+
BIT_MASK_BG_WINDOW_TILES = 1 << 4
|
|
13
|
+
BIT_MASK_BG_TILE_MAP = 1 << 3
|
|
14
|
+
BIT_MASK_OBJ_SIZE = 1 << 2
|
|
15
|
+
BIT_MASK_OBJ_ENABLE = 1 << 1
|
|
16
|
+
BIT_MASK_BG_WIN_ENABLED = 1 << 0
|
|
17
|
+
|
|
18
|
+
attr_reader :value
|
|
19
|
+
|
|
20
|
+
def initialize(skip_boot_rom:)
|
|
21
|
+
@value = skip_boot_rom ? 0x91 : 0x00
|
|
22
|
+
|
|
23
|
+
update_derived_states
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param value [Integer] 8-bit value written into 0xFF40.
|
|
27
|
+
def value=(value)
|
|
28
|
+
@value = value & 0xFF
|
|
29
|
+
|
|
30
|
+
update_derived_states
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Computes new state and caches the value on the Register write.
|
|
34
|
+
def update_derived_states
|
|
35
|
+
@lcd_enabled = (@value & BIT_MASK_LCD_ENABLE) != 0
|
|
36
|
+
@window_tile_map_high = (@value & BIT_MASK_WINDOW_TILE_MAP) != 0
|
|
37
|
+
@window_enabled = (@value & BIT_MASK_WINDOW_ENABLE) != 0
|
|
38
|
+
@tile_data_at_0x8000 = (@value & BIT_MASK_BG_WINDOW_TILES) != 0
|
|
39
|
+
@bg_tile_map_high = (@value & BIT_MASK_BG_TILE_MAP) != 0
|
|
40
|
+
@obj_size_8x16 = (@value & BIT_MASK_OBJ_SIZE) != 0
|
|
41
|
+
@obj_enabled = (@value & BIT_MASK_OBJ_ENABLE) != 0
|
|
42
|
+
@bg_win_enabled = (@value & BIT_MASK_BG_WIN_ENABLED) != 0
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def lcd_enabled? = @lcd_enabled
|
|
46
|
+
def window_tile_map_high? = @window_tile_map_high
|
|
47
|
+
def window_enabled? = @window_enabled
|
|
48
|
+
def tile_data_at_0x8000? = @tile_data_at_0x8000
|
|
49
|
+
def bg_tile_map_high? = @bg_tile_map_high
|
|
50
|
+
def obj_size_8x16? = @obj_size_8x16
|
|
51
|
+
def obj_enabled? = @obj_enabled
|
|
52
|
+
def bg_win_enabled? = @bg_win_enabled
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Ppu
|
|
6
|
+
class Registers
|
|
7
|
+
# Models the behavior of the LCD Status PPU Register.
|
|
8
|
+
class LcdStatus
|
|
9
|
+
BIT_MASK_WRITABLE_BITS = 0b01111000
|
|
10
|
+
|
|
11
|
+
BIT_MASK_LYC_INTERRUPT_SELECT = 1 << 6
|
|
12
|
+
BIT_MASK_MODE_2_INTERRUPT_SELECT = 1 << 5
|
|
13
|
+
BIT_MASK_MODE_1_INTERRUPT_SELECT = 1 << 4
|
|
14
|
+
BIT_MASK_MODE_0_INTERRUPT_SELECT = 1 << 3
|
|
15
|
+
|
|
16
|
+
attr_reader :value, :interrupt_line_cache
|
|
17
|
+
|
|
18
|
+
def initialize(interrupts, skip_boot_rom:)
|
|
19
|
+
@interrupts = interrupts
|
|
20
|
+
@value = skip_boot_rom ? 0x85 : 0x00
|
|
21
|
+
|
|
22
|
+
@interrupt_line_cache = interrupt_line_signal
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def value=(value)
|
|
26
|
+
@interrupt_line_cache = interrupt_line_signal
|
|
27
|
+
@value |= value & BIT_MASK_WRITABLE_BITS
|
|
28
|
+
|
|
29
|
+
fire_stat_interrupt if rising_edge?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @param ppu_mode [Integer] 2-bit value.
|
|
33
|
+
def set_mode_bits(ppu_mode)
|
|
34
|
+
@interrupt_line_cache = interrupt_line_signal
|
|
35
|
+
@value = (@value & 0b11111100) | ppu_mode
|
|
36
|
+
|
|
37
|
+
fire_stat_interrupt if rising_edge?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set_lyc_bit
|
|
41
|
+
@interrupt_line_cache = interrupt_line_signal
|
|
42
|
+
@value |= 0b100
|
|
43
|
+
|
|
44
|
+
fire_stat_interrupt if rising_edge?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def clear_lyc_bit
|
|
48
|
+
@interrupt_line_cache = interrupt_line_signal
|
|
49
|
+
@value &= 0xFB
|
|
50
|
+
|
|
51
|
+
fire_stat_interrupt if rising_edge?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def lyc_interrupt_selected?
|
|
55
|
+
(@value & BIT_MASK_LYC_INTERRUPT_SELECT) != 0
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def mode_2_interrupt_selected?
|
|
59
|
+
(@value & BIT_MASK_MODE_2_INTERRUPT_SELECT) != 0
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def mode_1_interrupt_selected?
|
|
63
|
+
(@value & BIT_MASK_MODE_1_INTERRUPT_SELECT) != 0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def mode_0_interrupt_selected?
|
|
67
|
+
(@value & BIT_MASK_MODE_0_INTERRUPT_SELECT) != 0
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def interrupt_line_signal
|
|
71
|
+
(@value[6] == 1 && @value[2] == 1) ||
|
|
72
|
+
(@value[5] == 1 && (@value & 0b11) == 0b10) ||
|
|
73
|
+
(@value[4] == 1 && (@value & 0b11) == 0b01) ||
|
|
74
|
+
(@value[3] == 1 && (@value & 0b11) == 0b00)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def rising_edge?
|
|
78
|
+
@interrupt_line_cache == false && interrupt_line_signal == true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def fire_stat_interrupt
|
|
82
|
+
@interrupts.request(:lcd_stat)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|