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,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
# Models the joypad inputs and logic.
|
|
6
|
+
class Joypad
|
|
7
|
+
BIT_MASK_UNUSED_BITS = 0b11000000
|
|
8
|
+
|
|
9
|
+
BIT_MASK_BUTTONS_SELECT_BIT = 0b00100000
|
|
10
|
+
BIT_MASK_DPAD_SELECT_BIT = 0b00010000
|
|
11
|
+
|
|
12
|
+
# Maps all face buttons to its relevant bit.
|
|
13
|
+
FACE_BUTTONS = {
|
|
14
|
+
a: 0,
|
|
15
|
+
b: 1,
|
|
16
|
+
select: 2,
|
|
17
|
+
start: 3
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
# Maps all D-pad buttons to its relevant bit.
|
|
21
|
+
DPAD_BUTTONS = {
|
|
22
|
+
right: 0,
|
|
23
|
+
left: 1,
|
|
24
|
+
up: 2,
|
|
25
|
+
down: 3
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
# Creates a joypad object.
|
|
29
|
+
#
|
|
30
|
+
# - Holds interrupts instance to request a :joypad interrupt.
|
|
31
|
+
# - @p1 register only holds the relevant selection bits (Bits 5 and 4).
|
|
32
|
+
# - Dpad and button state are tracked as separate nibbles.
|
|
33
|
+
def initialize(interrupts, skip_boot_rom: true)
|
|
34
|
+
@interrupts = interrupts
|
|
35
|
+
@p1 = skip_boot_rom ? 0xCF : 0x00
|
|
36
|
+
|
|
37
|
+
@dpad = 0xF
|
|
38
|
+
@buttons = 0xF
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns the 8-bit value stored in the P1 register.
|
|
42
|
+
#
|
|
43
|
+
# - Bits 7 and 6 are unused, they always read 1.
|
|
44
|
+
# - The actual @p1 register only holds the 2 selection bits (Bit 5 and 4).
|
|
45
|
+
# - Calculate the buttons state based on the pressed buttons and use it as the lower nibble.
|
|
46
|
+
def p1
|
|
47
|
+
BIT_MASK_UNUSED_BITS | @p1 | buttons_state
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Sets a 8-bit value into the P1 register.
|
|
51
|
+
#
|
|
52
|
+
# - The lower nibble is read-only.
|
|
53
|
+
# - Bit 5 is used to select the face buttons.
|
|
54
|
+
# - Bit 4 is used to select the d-pad.
|
|
55
|
+
def p1=(value)
|
|
56
|
+
old_state = buttons_state
|
|
57
|
+
@p1 = value & 0b00110000
|
|
58
|
+
new_state = buttons_state
|
|
59
|
+
|
|
60
|
+
falling_edges = old_state & ~new_state # checks for bits that went 1 -> 0
|
|
61
|
+
request_interrupt if falling_edges.anybits?(0x0F)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param button [Symbol] Dpad button pressed (:up, :down, :left, :right).
|
|
65
|
+
def press_dpad(button)
|
|
66
|
+
relevant_bit = DPAD_BUTTONS[button]
|
|
67
|
+
return if (@dpad >> relevant_bit).nobits?(1) # already pressed
|
|
68
|
+
|
|
69
|
+
clear_mask = ~(1 << relevant_bit)
|
|
70
|
+
@dpad &= clear_mask
|
|
71
|
+
|
|
72
|
+
request_interrupt if dpad_selected?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @param button [Symbol] Dpad button released (:up, :down, :left, :right).
|
|
76
|
+
def release_dpad(button)
|
|
77
|
+
relevant_bit = DPAD_BUTTONS[button]
|
|
78
|
+
return if (@dpad >> relevant_bit).anybits?(1) # already released
|
|
79
|
+
|
|
80
|
+
set_mask = (1 << relevant_bit)
|
|
81
|
+
@dpad |= set_mask
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @param button [Symbol] Face button pressed (:a, :b, :start, :select).
|
|
85
|
+
def press_face(button)
|
|
86
|
+
relevant_bit = FACE_BUTTONS[button]
|
|
87
|
+
return if (@buttons >> relevant_bit).nobits?(1) # already pressed
|
|
88
|
+
|
|
89
|
+
clear_mask = ~(1 << relevant_bit)
|
|
90
|
+
@buttons &= clear_mask
|
|
91
|
+
|
|
92
|
+
request_interrupt if buttons_selected?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @param button [Symbol] Face button released (:a, :b, :start, :select).
|
|
96
|
+
def release_face(button)
|
|
97
|
+
relevant_bit = FACE_BUTTONS[button]
|
|
98
|
+
return if (@buttons >> relevant_bit).anybits?(1) # already released
|
|
99
|
+
|
|
100
|
+
set_mask = (1 << relevant_bit)
|
|
101
|
+
@buttons |= set_mask
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def request_interrupt
|
|
107
|
+
@interrupts.request(:joypad)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def dpad_selected?
|
|
111
|
+
(@p1 >> 4).nobits?(1)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def buttons_selected?
|
|
115
|
+
(@p1 >> 5).nobits?(1)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def buttons_state
|
|
119
|
+
return @dpad & @buttons if dpad_selected? && buttons_selected?
|
|
120
|
+
return @dpad if dpad_selected?
|
|
121
|
+
return @buttons if buttons_selected?
|
|
122
|
+
|
|
123
|
+
0xF
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
class Oam
|
|
6
|
+
# Models each Sprite that is stored inside the Object Attribute Memory (OAM).
|
|
7
|
+
class Sprite
|
|
8
|
+
SIZE_IN_BYTES = 4
|
|
9
|
+
|
|
10
|
+
BIT_MASK_BG_WIN_PRIORITY_SET = 1 << 7
|
|
11
|
+
BIT_MASK_OBJ_Y_FLIPPED = 1 << 6
|
|
12
|
+
BIT_MASK_OBJ_X_FLIPPED = 1 << 5
|
|
13
|
+
BIT_MASK_USE_OBP1_PALETTE = 1 << 4
|
|
14
|
+
|
|
15
|
+
# @param oam_data [Array] Reference to the original OAM @data.
|
|
16
|
+
# @param index [Integer] Sprite index within OAM (0 - 39).
|
|
17
|
+
def initialize(oam_data:, index:)
|
|
18
|
+
@oam_data = oam_data
|
|
19
|
+
@base_offset = index * SIZE_IN_BYTES
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Screen Y position + 16, this means if Y = 0
|
|
23
|
+
# the object top pixel is offscreen at (- 16).
|
|
24
|
+
#
|
|
25
|
+
# @return [Integer] First byte of the Sprite in OAM.
|
|
26
|
+
def y
|
|
27
|
+
@oam_data[@base_offset]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Where the top-most pixel starts at (top row).
|
|
31
|
+
#
|
|
32
|
+
def y_screen_pos
|
|
33
|
+
y - 16
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Integer] Byte 1 of the Sprite.
|
|
37
|
+
def x
|
|
38
|
+
@oam_data[@base_offset + 1]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def x_screen_pos
|
|
42
|
+
x - 8
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Integer] Byte 2 of the Sprite.
|
|
46
|
+
def tile_index(obj_size_8x16, y_flipped, current_obj_y)
|
|
47
|
+
return @oam_data[@base_offset + 2] unless obj_size_8x16
|
|
48
|
+
|
|
49
|
+
if current_obj_y >= 0 && current_obj_y < 8
|
|
50
|
+
y_flipped ? bottom_half : top_half
|
|
51
|
+
else
|
|
52
|
+
y_flipped ? top_half : bottom_half
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def top_half
|
|
57
|
+
@oam_data[@base_offset + 2] & 0xFE
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def bottom_half
|
|
61
|
+
@oam_data[@base_offset + 2] | 0x01
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Integer] Byte 3 of the Sprite.
|
|
65
|
+
def attributes
|
|
66
|
+
@oam_data[@base_offset + 3]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# If this bit is set in the Sprite attributes
|
|
70
|
+
# it means that the Background or Window should
|
|
71
|
+
# be displayed on top of the Sprite *UNLESS* the
|
|
72
|
+
# Background/Window pixel has color_id = 0b00,
|
|
73
|
+
# in which case the Sprite pixel is shown.
|
|
74
|
+
#
|
|
75
|
+
# @return [Boolean]
|
|
76
|
+
def bg_win_priority_set?
|
|
77
|
+
(attributes & BIT_MASK_BG_WIN_PRIORITY_SET) != 0
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [Boolean] Whether or not the Sprite is flipped horizontally.
|
|
81
|
+
def y_flipped?
|
|
82
|
+
(attributes & BIT_MASK_OBJ_Y_FLIPPED) != 0
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [Boolean] Whether or not the Sprite is flipped vertically.
|
|
86
|
+
def x_flipped?
|
|
87
|
+
(attributes & BIT_MASK_OBJ_X_FLIPPED) != 0
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [Boolean] Selects which Color Palette to use for the Sprite pixels.
|
|
91
|
+
def use_obp1_palette?
|
|
92
|
+
(attributes & BIT_MASK_USE_OBP1_PALETTE) != 0
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [String] Custom inspect for easier debugging.
|
|
96
|
+
def inspect
|
|
97
|
+
'#<Sprite ' \
|
|
98
|
+
"y_pos=$#{format('%02X', y)} " \
|
|
99
|
+
"x_pos=$#{format('%02X', x)} " \
|
|
100
|
+
"tile_index=$#{format('%02X', top_half)} " \
|
|
101
|
+
"attributes=#{format('%08b', attributes)}>"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Amaterasu
|
|
4
|
+
module GameBoy
|
|
5
|
+
# Models the OAM (Object Attribute Memory) from the DMG Game Boy.
|
|
6
|
+
class Oam < Ram
|
|
7
|
+
START_ADDRESS = 0xFE00
|
|
8
|
+
END_ADDRESS = 0xFE9F
|
|
9
|
+
SPRITE_ENTRIES = 40
|
|
10
|
+
SIZE = (END_ADDRESS - START_ADDRESS) + 1 #=> 160 bytes
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
super(size: SIZE, offset: START_ADDRESS)
|
|
14
|
+
|
|
15
|
+
@sprites = Array.new(SPRITE_ENTRIES) do |sprite_index|
|
|
16
|
+
Sprite.new(oam_data: @data, index: sprite_index)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def sprite(index)
|
|
21
|
+
@sprites[index]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def each_sprite(&)
|
|
25
|
+
@sprites.each(&)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
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 when LCD Control Bit 7 is 0.
|
|
8
|
+
class Disabled
|
|
9
|
+
attr_reader :name, :number
|
|
10
|
+
|
|
11
|
+
def initialize(ppu)
|
|
12
|
+
@ppu = ppu
|
|
13
|
+
@lcd_control = @ppu.registers.lcdc
|
|
14
|
+
|
|
15
|
+
@name = 'DISABLED'
|
|
16
|
+
@number = 0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def tick
|
|
20
|
+
return unless @lcd_control.lcd_enabled?
|
|
21
|
+
|
|
22
|
+
@ppu.reset_states
|
|
23
|
+
@ppu.set_mode(:oam_scan)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
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 HBlank mode.
|
|
8
|
+
class HBlank
|
|
9
|
+
attr_reader :name, :number
|
|
10
|
+
|
|
11
|
+
def initialize(ppu)
|
|
12
|
+
@ppu = ppu
|
|
13
|
+
|
|
14
|
+
@name = 'HBLANK'
|
|
15
|
+
@number = 0
|
|
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
|
+
|
|
24
|
+
if @ppu.registers.ly < VISIBLE_SCANLINES
|
|
25
|
+
@ppu.set_mode(:oam_scan)
|
|
26
|
+
elsif @ppu.registers.ly == VISIBLE_SCANLINES
|
|
27
|
+
@ppu.set_mode(:v_blank)
|
|
28
|
+
@ppu.request_interrupt(:v_blank)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def inspect
|
|
33
|
+
'#<Amaterasu::GameBoy::Ppu::Modes::HBlank ' \
|
|
34
|
+
"@name='#{@name}' " \
|
|
35
|
+
"@number=#{@number} "
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_s
|
|
39
|
+
"#{@name} (##{@number}) | WAITING FOR SCANLINE TO FINISH"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
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 OAM Scan mode.
|
|
8
|
+
class OamScan
|
|
9
|
+
# Each Sprite takes 2 dots to scan.
|
|
10
|
+
SCAN_DURATION_IN_DOTS = 80
|
|
11
|
+
|
|
12
|
+
attr_reader :name, :number
|
|
13
|
+
|
|
14
|
+
# @param ppu [Amaterasu::GameBoy::Ppu] Reference to the PPU instance.
|
|
15
|
+
def initialize(ppu)
|
|
16
|
+
@ppu = ppu
|
|
17
|
+
|
|
18
|
+
@name = 'OAM SCAN'
|
|
19
|
+
@number = 2
|
|
20
|
+
|
|
21
|
+
@sprite_count = 0
|
|
22
|
+
@sprite_index = 0
|
|
23
|
+
@current_sprite = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# This method is called by the PPU each T-cycle
|
|
27
|
+
# for all visible scanlines (0 - 143) and when
|
|
28
|
+
# dots are between 0 and 79.
|
|
29
|
+
#
|
|
30
|
+
# Takes exactly 2 dots to fetch a sprite so every
|
|
31
|
+
# even dot (0, 2, 4, ..., 78) is a NO-OP.
|
|
32
|
+
def tick
|
|
33
|
+
if @ppu.dots.odd?
|
|
34
|
+
@current_sprite = @ppu.fetch_sprite_at(@sprite_index) #=> 0 - 39
|
|
35
|
+
@sprite_index += 1
|
|
36
|
+
|
|
37
|
+
if sprite_within_current_scanline? && @sprite_count < Ppu::MAX_SPRITES_PER_SCANLINE
|
|
38
|
+
@ppu.sprite_buffer << @current_sprite
|
|
39
|
+
@sprite_count += 1
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
return unless @ppu.dots == SCAN_DURATION_IN_DOTS
|
|
44
|
+
|
|
45
|
+
sort_sprites_by_x_positions unless @ppu.sprite_buffer.empty?
|
|
46
|
+
@sprite_count = 0
|
|
47
|
+
@sprite_index = 0
|
|
48
|
+
@ppu.wy_eq_ly = true if window_triggered?
|
|
49
|
+
@ppu.set_mode(:rendering)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Custom inspect to prevent circular dependencies.
|
|
53
|
+
def inspect
|
|
54
|
+
'#<Amaterasu::GameBoy::Ppu::Modes::OamScan ' \
|
|
55
|
+
"@name='#{@name}' " \
|
|
56
|
+
"@number=#{@number} " \
|
|
57
|
+
"@sprite_buffer=#{@sprite_buffer}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Custom to_s method to use in the Ppu#log_state method.
|
|
61
|
+
def to_s
|
|
62
|
+
"#{@name} (##{@number}) | " \
|
|
63
|
+
"SCANNED: Sprite ##{format('%02d', @sprite_index)} | " \
|
|
64
|
+
"BUFFER: #{@ppu.sprite_buffer} (#{@ppu.sprite_buffer.size})"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def window_triggered?
|
|
70
|
+
return false unless @ppu.registers.lcdc.window_enabled?
|
|
71
|
+
return false unless @ppu.registers.ly == @ppu.registers.wy
|
|
72
|
+
return false if @ppu.wy_eq_ly
|
|
73
|
+
|
|
74
|
+
true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def sprite_within_current_scanline?
|
|
78
|
+
sprite_height = @ppu.registers.lcdc.obj_size_8x16? ? 16 : 8
|
|
79
|
+
|
|
80
|
+
@ppu.registers.ly >= @current_sprite.y_screen_pos
|
|
81
|
+
&& @ppu.registers.ly < @current_sprite.y_screen_pos + sprite_height
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# TODO: benchmark the performance of both
|
|
85
|
+
def sort_sprites_by_x_positions
|
|
86
|
+
@ppu.sprite_buffer.sort_by!(&:x)
|
|
87
|
+
# @ppu.sprite_buffer.sort! { |a, b| a.x <=> b.x }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
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 Background and Window tiles,
|
|
9
|
+
# decoding the pixels and outputting them into the BG/WIN FIFO.
|
|
10
|
+
#
|
|
11
|
+
# Despite the similarity it behaves differently than the SpriteFetcher.
|
|
12
|
+
class BgWinFetcher
|
|
13
|
+
attr_reader :fetch_mode, :step
|
|
14
|
+
|
|
15
|
+
def initialize(ppu, bg_win_fifo)
|
|
16
|
+
@ppu = ppu
|
|
17
|
+
@bg_win_fifo = bg_win_fifo
|
|
18
|
+
|
|
19
|
+
@step = :fetch_tile_index
|
|
20
|
+
@bg_fetcher_x = 0
|
|
21
|
+
@window_fetcher_x = 0
|
|
22
|
+
@fetch_mode = :bg
|
|
23
|
+
|
|
24
|
+
@fetch_duration = 6
|
|
25
|
+
@sleep_duration = 2
|
|
26
|
+
@warming_up = true
|
|
27
|
+
|
|
28
|
+
@tile_index = nil
|
|
29
|
+
@tile_data_low = nil
|
|
30
|
+
@tile_data_high = nil
|
|
31
|
+
@tile_pixels = nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Core fetch state machine.
|
|
35
|
+
#
|
|
36
|
+
def tick
|
|
37
|
+
case @step
|
|
38
|
+
when :fetch_tile_index then fetch_tile_index
|
|
39
|
+
when :fetch_tile_data_low then fetch_tile_data_low
|
|
40
|
+
when :fetch_tile_data_high then fetch_tile_data_high
|
|
41
|
+
when :push then push
|
|
42
|
+
when :sleep then sleep
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def activate_window!
|
|
47
|
+
@bg_win_fifo.clear
|
|
48
|
+
@fetch_mode = :window
|
|
49
|
+
reset_cycle
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def increment_window_y
|
|
53
|
+
return unless @window_drawed
|
|
54
|
+
|
|
55
|
+
@ppu.window_y_count += 1
|
|
56
|
+
@window_drawed = false
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def reset_for_scanline
|
|
60
|
+
reset_cycle
|
|
61
|
+
@fetch_mode = :bg
|
|
62
|
+
@warming_up = true
|
|
63
|
+
@bg_fetcher_x = 0
|
|
64
|
+
@window_fetcher_x = 0
|
|
65
|
+
increment_window_y
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Fetches the BG Tile index at the current Tile map.
|
|
71
|
+
def fetch_tile_index
|
|
72
|
+
@fetch_duration -= 1
|
|
73
|
+
return unless @fetch_duration == 4
|
|
74
|
+
|
|
75
|
+
tile_x = @fetch_mode == :bg ? current_bg_tile_x : current_window_tile_x
|
|
76
|
+
tile_y = @fetch_mode == :bg ? current_bg_tile_y : current_window_tile_y
|
|
77
|
+
|
|
78
|
+
@tile_index = current_tile_map.tile_index_at(
|
|
79
|
+
tile_x: tile_x,
|
|
80
|
+
tile_y: tile_y
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@step = :fetch_tile_data_low
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Fetches the low byte of the BG or Window Tile Row
|
|
87
|
+
# that overlaps with the current row/line being drawn.
|
|
88
|
+
def fetch_tile_data_low
|
|
89
|
+
@fetch_duration -= 1
|
|
90
|
+
return unless @fetch_duration == 2
|
|
91
|
+
|
|
92
|
+
current_y = @fetch_mode == :bg ? current_bg_y : current_window_y
|
|
93
|
+
@tile_data_low = current_tile_data.tile_at(@tile_index).data_low(current_y)
|
|
94
|
+
|
|
95
|
+
@step = :fetch_tile_data_high
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Fetches the high byte of the BG or Window Tile Row
|
|
99
|
+
# that overlaps with the current row/line being drawn.
|
|
100
|
+
def fetch_tile_data_high
|
|
101
|
+
@fetch_duration -= 1
|
|
102
|
+
return unless @fetch_duration == 0
|
|
103
|
+
|
|
104
|
+
current_y = @fetch_mode == :bg ? current_bg_y : current_window_y
|
|
105
|
+
@tile_data_high = current_tile_data.tile_at(@tile_index).data_high(current_y)
|
|
106
|
+
fetch_tile_pixels
|
|
107
|
+
@warming_up ? discard_pixels : attempt_push_into_fifo
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Fetches the BG or Window Tile pixels.
|
|
111
|
+
def fetch_tile_pixels
|
|
112
|
+
@tile_pixels = Vram::Tile::PIXELS_LOOKUP[(@tile_data_high << 8) | @tile_data_low]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def discard_pixels
|
|
116
|
+
@tile_pixels = nil
|
|
117
|
+
@warming_up = false
|
|
118
|
+
reset_cycle
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def attempt_push_into_fifo
|
|
122
|
+
push_successful = @bg_win_fifo.push?(@tile_pixels)
|
|
123
|
+
|
|
124
|
+
if push_successful
|
|
125
|
+
if @fetch_mode == :bg
|
|
126
|
+
@bg_fetcher_x += 1
|
|
127
|
+
else
|
|
128
|
+
@window_fetcher_x += 1
|
|
129
|
+
@window_drawed = true
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
@step = :sleep
|
|
133
|
+
else
|
|
134
|
+
@step = :push
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def push
|
|
139
|
+
push_successful = @bg_win_fifo.push?(@tile_pixels)
|
|
140
|
+
return unless push_successful
|
|
141
|
+
|
|
142
|
+
reset_cycle
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def sleep
|
|
146
|
+
@sleep_duration -= 1
|
|
147
|
+
reset_cycle if @sleep_duration == 0
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def reset_cycle
|
|
151
|
+
@step = :fetch_tile_index
|
|
152
|
+
@fetch_duration = 6
|
|
153
|
+
@sleep_duration = 2
|
|
154
|
+
@tile_index = nil
|
|
155
|
+
@tile_data_low = nil
|
|
156
|
+
@tile_data_high = nil
|
|
157
|
+
@tile_pixels = nil
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def current_bg_y
|
|
161
|
+
@ppu.registers.ly + @ppu.registers.scy
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def current_bg_tile_x
|
|
165
|
+
(@ppu.registers.scx / Vram::Tile::PIXEL_WIDTH) + @bg_fetcher_x
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def current_bg_tile_y
|
|
169
|
+
current_bg_y / Vram::Tile::PIXEL_HEIGHT
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def current_window_y
|
|
173
|
+
@ppu.window_y_count
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def current_window_tile_x
|
|
177
|
+
@window_fetcher_x
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def current_window_tile_y
|
|
181
|
+
@ppu.window_y_count / Vram::Tile::PIXEL_HEIGHT
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# @return [Vram::TileMap]
|
|
185
|
+
def current_tile_map
|
|
186
|
+
@fetch_mode == :bg ? @ppu.bg_tile_map : @ppu.window_tile_map
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @return [Vram::TileData]
|
|
190
|
+
def current_tile_data
|
|
191
|
+
@ppu.bg_win_tile_data
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def to_s
|
|
195
|
+
"Mode: #{@fetch_mode.upcase} | " \
|
|
196
|
+
"Step: #{@step.upcase} | " \
|
|
197
|
+
"PIX: #{@tile_pixels}"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|