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.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +54 -0
  5. data/Gemfile +32 -0
  6. data/Gemfile.lock +267 -0
  7. data/LICENSE +21 -0
  8. data/README.md +115 -0
  9. data/Steepfile +7 -0
  10. data/exe/amaterasu +23 -0
  11. data/lib/amaterasu/cartridge/mbc1.rb +56 -0
  12. data/lib/amaterasu/cartridge/rom.rb +118 -0
  13. data/lib/amaterasu/cartridge.rb +68 -0
  14. data/lib/amaterasu/cli.rb +60 -0
  15. data/lib/amaterasu/emulator.rb +121 -0
  16. data/lib/amaterasu/game_boy/apu.rb +12 -0
  17. data/lib/amaterasu/game_boy/bus.rb +161 -0
  18. data/lib/amaterasu/game_boy/cpu/instructions/adc.rb +64 -0
  19. data/lib/amaterasu/game_boy/cpu/instructions/add16.rb +73 -0
  20. data/lib/amaterasu/game_boy/cpu/instructions/add8.rb +63 -0
  21. data/lib/amaterasu/game_boy/cpu/instructions/and.rb +62 -0
  22. data/lib/amaterasu/game_boy/cpu/instructions/base.rb +38 -0
  23. data/lib/amaterasu/game_boy/cpu/instructions/call.rb +48 -0
  24. data/lib/amaterasu/game_boy/cpu/instructions/cb_bit.rb +52 -0
  25. data/lib/amaterasu/game_boy/cpu/instructions/cb_res.rb +49 -0
  26. data/lib/amaterasu/game_boy/cpu/instructions/cb_rl.rb +70 -0
  27. data/lib/amaterasu/game_boy/cpu/instructions/cb_rlc.rb +68 -0
  28. data/lib/amaterasu/game_boy/cpu/instructions/cb_rr.rb +70 -0
  29. data/lib/amaterasu/game_boy/cpu/instructions/cb_rrc.rb +68 -0
  30. data/lib/amaterasu/game_boy/cpu/instructions/cb_set.rb +51 -0
  31. data/lib/amaterasu/game_boy/cpu/instructions/cb_sla.rb +69 -0
  32. data/lib/amaterasu/game_boy/cpu/instructions/cb_sra.rb +71 -0
  33. data/lib/amaterasu/game_boy/cpu/instructions/cb_srl.rb +69 -0
  34. data/lib/amaterasu/game_boy/cpu/instructions/cb_swap.rb +67 -0
  35. data/lib/amaterasu/game_boy/cpu/instructions/cp.rb +61 -0
  36. data/lib/amaterasu/game_boy/cpu/instructions/daa.rb +59 -0
  37. data/lib/amaterasu/game_boy/cpu/instructions/dec.rb +64 -0
  38. data/lib/amaterasu/game_boy/cpu/instructions/di.rb +21 -0
  39. data/lib/amaterasu/game_boy/cpu/instructions/ei.rb +19 -0
  40. data/lib/amaterasu/game_boy/cpu/instructions/halt.rb +19 -0
  41. data/lib/amaterasu/game_boy/cpu/instructions/inc.rb +64 -0
  42. data/lib/amaterasu/game_boy/cpu/instructions/jp.rb +54 -0
  43. data/lib/amaterasu/game_boy/cpu/instructions/jr.rb +45 -0
  44. data/lib/amaterasu/game_boy/cpu/instructions/ld16.rb +79 -0
  45. data/lib/amaterasu/game_boy/cpu/instructions/ld8.rb +210 -0
  46. data/lib/amaterasu/game_boy/cpu/instructions/ldh.rb +61 -0
  47. data/lib/amaterasu/game_boy/cpu/instructions/misc.rb +53 -0
  48. data/lib/amaterasu/game_boy/cpu/instructions/nop.rb +19 -0
  49. data/lib/amaterasu/game_boy/cpu/instructions/or.rb +56 -0
  50. data/lib/amaterasu/game_boy/cpu/instructions/pop.rb +39 -0
  51. data/lib/amaterasu/game_boy/cpu/instructions/push.rb +43 -0
  52. data/lib/amaterasu/game_boy/cpu/instructions/ret.rb +70 -0
  53. data/lib/amaterasu/game_boy/cpu/instructions/rotate.rb +120 -0
  54. data/lib/amaterasu/game_boy/cpu/instructions/rst.rb +33 -0
  55. data/lib/amaterasu/game_boy/cpu/instructions/sbc.rb +64 -0
  56. data/lib/amaterasu/game_boy/cpu/instructions/stop.rb +19 -0
  57. data/lib/amaterasu/game_boy/cpu/instructions/sub.rb +63 -0
  58. data/lib/amaterasu/game_boy/cpu/instructions/xor.rb +60 -0
  59. data/lib/amaterasu/game_boy/cpu/instructions.rb +600 -0
  60. data/lib/amaterasu/game_boy/cpu/registers.rb +264 -0
  61. data/lib/amaterasu/game_boy/cpu.rb +232 -0
  62. data/lib/amaterasu/game_boy/dma.rb +114 -0
  63. data/lib/amaterasu/game_boy/interrupts.rb +108 -0
  64. data/lib/amaterasu/game_boy/joypad.rb +127 -0
  65. data/lib/amaterasu/game_boy/oam/sprite.rb +106 -0
  66. data/lib/amaterasu/game_boy/oam.rb +29 -0
  67. data/lib/amaterasu/game_boy/ppu/modes/disabled.rb +29 -0
  68. data/lib/amaterasu/game_boy/ppu/modes/h_blank.rb +45 -0
  69. data/lib/amaterasu/game_boy/ppu/modes/oam_scan.rb +93 -0
  70. data/lib/amaterasu/game_boy/ppu/modes/rendering/bg_win_fetcher.rb +204 -0
  71. data/lib/amaterasu/game_boy/ppu/modes/rendering/pixel_emitter.rb +83 -0
  72. data/lib/amaterasu/game_boy/ppu/modes/rendering/pixel_fifo.rb +70 -0
  73. data/lib/amaterasu/game_boy/ppu/modes/rendering/sprite_fetcher.rb +140 -0
  74. data/lib/amaterasu/game_boy/ppu/modes/rendering.rb +108 -0
  75. data/lib/amaterasu/game_boy/ppu/modes/v_blank.rb +43 -0
  76. data/lib/amaterasu/game_boy/ppu/modes.rb +22 -0
  77. data/lib/amaterasu/game_boy/ppu/registers/lcd_control.rb +57 -0
  78. data/lib/amaterasu/game_boy/ppu/registers/lcd_status.rb +88 -0
  79. data/lib/amaterasu/game_boy/ppu/registers.rb +131 -0
  80. data/lib/amaterasu/game_boy/ppu.rb +207 -0
  81. data/lib/amaterasu/game_boy/ram.rb +70 -0
  82. data/lib/amaterasu/game_boy/serial.rb +91 -0
  83. data/lib/amaterasu/game_boy/timer.rb +230 -0
  84. data/lib/amaterasu/game_boy/vram/tile.rb +68 -0
  85. data/lib/amaterasu/game_boy/vram/tile_data.rb +52 -0
  86. data/lib/amaterasu/game_boy/vram/tile_map.rb +71 -0
  87. data/lib/amaterasu/game_boy/vram.rb +51 -0
  88. data/lib/amaterasu/hal/console.rb +23 -0
  89. data/lib/amaterasu/hal/sdl2/bindings.rb +59 -0
  90. data/lib/amaterasu/hal/sdl2.rb +127 -0
  91. data/lib/amaterasu/utils/bit_ops.rb +22 -0
  92. data/lib/amaterasu.rb +13 -0
  93. data/sig/akane/cartridge/rom.rbs +29 -0
  94. data/sig/akane/cartridge.rbs +12 -0
  95. data/sig/akane/cli.rbs +16 -0
  96. data/sig/akane/emulator.rbs +19 -0
  97. data/sig/akane/game_boy/apu.rbs +7 -0
  98. data/sig/akane/game_boy/bus.rbs +25 -0
  99. data/sig/akane/game_boy/cpu/instructions/adc.rbs +18 -0
  100. data/sig/akane/game_boy/cpu/instructions/add16.rbs +19 -0
  101. data/sig/akane/game_boy/cpu/instructions/add8.rbs +18 -0
  102. data/sig/akane/game_boy/cpu/instructions/and.rbs +18 -0
  103. data/sig/akane/game_boy/cpu/instructions/base.rbs +20 -0
  104. data/sig/akane/game_boy/cpu/instructions/call.rbs +18 -0
  105. data/sig/akane/game_boy/cpu/instructions/cb_bit.rbs +20 -0
  106. data/sig/akane/game_boy/cpu/instructions/cb_res.rbs +19 -0
  107. data/sig/akane/game_boy/cpu/instructions/cb_rl.rbs +21 -0
  108. data/sig/akane/game_boy/cpu/instructions/cb_rlc.rbs +21 -0
  109. data/sig/akane/game_boy/cpu/instructions/cb_rr.rbs +21 -0
  110. data/sig/akane/game_boy/cpu/instructions/cb_rrc.rbs +21 -0
  111. data/sig/akane/game_boy/cpu/instructions/cb_set.rbs +20 -0
  112. data/sig/akane/game_boy/cpu/instructions/cb_sla.rbs +21 -0
  113. data/sig/akane/game_boy/cpu/instructions/cb_sra.rbs +21 -0
  114. data/sig/akane/game_boy/cpu/instructions/cb_srl.rbs +21 -0
  115. data/sig/akane/game_boy/cpu/instructions/cb_swap.rbs +21 -0
  116. data/sig/akane/game_boy/cpu/instructions/cp.rbs +18 -0
  117. data/sig/akane/game_boy/cpu/instructions/daa.rbs +17 -0
  118. data/sig/akane/game_boy/cpu/instructions/dec.rbs +19 -0
  119. data/sig/akane/game_boy/cpu/instructions/di.rbs +13 -0
  120. data/sig/akane/game_boy/cpu/instructions/ei.rbs +13 -0
  121. data/sig/akane/game_boy/cpu/instructions/halt.rbs +13 -0
  122. data/sig/akane/game_boy/cpu/instructions/inc.rbs +19 -0
  123. data/sig/akane/game_boy/cpu/instructions/jp.rbs +18 -0
  124. data/sig/akane/game_boy/cpu/instructions/jr.rbs +18 -0
  125. data/sig/akane/game_boy/cpu/instructions/ld16.rbs +18 -0
  126. data/sig/akane/game_boy/cpu/instructions/ld8.rbs +31 -0
  127. data/sig/akane/game_boy/cpu/instructions/ldh.rbs +23 -0
  128. data/sig/akane/game_boy/cpu/instructions/misc.rbs +20 -0
  129. data/sig/akane/game_boy/cpu/instructions/nop.rbs +13 -0
  130. data/sig/akane/game_boy/cpu/instructions/or.rbs +18 -0
  131. data/sig/akane/game_boy/cpu/instructions/pop.rbs +17 -0
  132. data/sig/akane/game_boy/cpu/instructions/push.rbs +18 -0
  133. data/sig/akane/game_boy/cpu/instructions/ret.rbs +20 -0
  134. data/sig/akane/game_boy/cpu/instructions/rotate.rbs +23 -0
  135. data/sig/akane/game_boy/cpu/instructions/rst.rbs +17 -0
  136. data/sig/akane/game_boy/cpu/instructions/sbc.rbs +19 -0
  137. data/sig/akane/game_boy/cpu/instructions/stop.rbs +13 -0
  138. data/sig/akane/game_boy/cpu/instructions/sub.rbs +18 -0
  139. data/sig/akane/game_boy/cpu/instructions/xor.rbs +19 -0
  140. data/sig/akane/game_boy/cpu/instructions.rbs +12 -0
  141. data/sig/akane/game_boy/cpu/registers.rbs +56 -0
  142. data/sig/akane/game_boy/cpu.rbs +39 -0
  143. data/sig/akane/game_boy/interrupts.rbs +28 -0
  144. data/sig/akane/game_boy/joypad.rbs +25 -0
  145. data/sig/akane/game_boy/oam/sprite.rbs +30 -0
  146. data/sig/akane/game_boy/ppu/modes/disabled.rbs +17 -0
  147. data/sig/akane/game_boy/ppu/modes/h_blank.rbs +20 -0
  148. data/sig/akane/game_boy/ppu/modes/oam_scan.rbs +28 -0
  149. data/sig/akane/game_boy/ppu/modes/rendering.rbs +26 -0
  150. data/sig/akane/game_boy/ppu/modes/v_blank.rbs +20 -0
  151. data/sig/akane/game_boy/ppu/modes.rbs +13 -0
  152. data/sig/akane/game_boy/ppu.rbs +59 -0
  153. data/sig/akane/game_boy/ram.rbs +16 -0
  154. data/sig/akane/game_boy/serial.rbs +21 -0
  155. data/sig/akane/game_boy/timer.rbs +30 -0
  156. data/sig/akane/utils/bit_ops.rbs +11 -0
  157. data/sig/akane.rbs +3 -0
  158. 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