rubyboy 1.4.0 → 1.4.1
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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +2 -0
- data/docs/index.html +2 -2
- data/lib/executor.rb +1 -1
- data/lib/rubyboy/apu.rb +4 -4
- data/lib/rubyboy/emulator.rb +2 -1
- data/lib/rubyboy/emulator_wasm.rb +4 -4
- data/lib/rubyboy/lcd.rb +5 -5
- data/lib/rubyboy/ppu.rb +168 -53
- data/lib/rubyboy/raylib/lcd.rb +1 -1
- data/lib/rubyboy/sdl.rb +1 -0
- data/lib/rubyboy/version.rb +1 -1
- data/lib/rubyboy.rb +1 -0
- data/resource/screenshots/pokemon.png +0 -0
- data/resource/screenshots/puyopuyo.png +0 -0
- metadata +2 -4
- data/lib/rubyboy/apu_wasm.rb +0 -118
- data/lib/rubyboy/ppu_wasm.rb +0 -312
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f029df58812310dc308546c77d25e75496e0e7b0aff605a5d0826deba85d9014
|
4
|
+
data.tar.gz: c2fffa4e8f4535ae4fad4bdf3c5742f569443fffc925ee02fa06f6b268f5e2c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7f5d75b7ae8ec29488242943aca521b013875c896c8b368622f02408f52803b01e94b548b720fb4bad1ffca30d4223cfbf15ab2e8bd04b89799dc2a41bcf164
|
7
|
+
data.tar.gz: b21b63a6c19263c3a72aa9b592505358dc55f450b0fd9fd3c8fb8b907d2057c07b2dae41dc9c0fa1be2fcd4e77010d4dcaf8ca7eafab908f27f7b5d903596a69
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
data/docs/index.html
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html lang="en">
|
3
3
|
<head>
|
4
|
-
<title>
|
4
|
+
<title>Ruby Boy</title>
|
5
5
|
<meta charset="utf-8"/>
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7
7
|
<meta name="author" content="sacckey" />
|
8
8
|
<meta name="description" content="Game Boy emulator written in Ruby, running on ruby.wasm" />
|
9
9
|
<meta property="og:url" content="https://sacckey.github.io/rubyboy/" />
|
10
|
-
<meta property="og:title" content="
|
10
|
+
<meta property="og:title" content="Ruby Boy" />
|
11
11
|
<meta property="og:description" content="Game Boy emulator written in Ruby, running on ruby.wasm" />
|
12
12
|
<meta property="og:image" content="https://sacckey.github.io/rubyboy/ogp.png" />
|
13
13
|
<meta name="twitter:card" content="summary_large_image" />
|
data/lib/executor.rb
CHANGED
@@ -14,7 +14,7 @@ class Executor
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def exec(direction_key = 0b1111, action_key = 0b1111)
|
17
|
-
bin = @emulator.step(direction_key, action_key).pack('
|
17
|
+
bin = @emulator.step(direction_key, action_key).pack('V*')
|
18
18
|
File.binwrite(File.join('/RUBYBOY_TMP', 'video.data'), bin)
|
19
19
|
end
|
20
20
|
|
data/lib/rubyboy/apu.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'audio'
|
4
3
|
require_relative 'apu_channels/channel1'
|
5
4
|
require_relative 'apu_channels/channel2'
|
6
5
|
require_relative 'apu_channels/channel3'
|
@@ -8,8 +7,9 @@ require_relative 'apu_channels/channel4'
|
|
8
7
|
|
9
8
|
module Rubyboy
|
10
9
|
class Apu
|
10
|
+
attr_reader :samples
|
11
|
+
|
11
12
|
def initialize
|
12
|
-
@audio = Audio.new
|
13
13
|
@nr50 = 0
|
14
14
|
@nr51 = 0
|
15
15
|
@cycles = 0
|
@@ -67,10 +67,10 @@ module Rubyboy
|
|
67
67
|
@sample_idx += 1
|
68
68
|
end
|
69
69
|
|
70
|
-
return if @sample_idx < 512
|
70
|
+
return false if @sample_idx < 512
|
71
71
|
|
72
72
|
@sample_idx = 0
|
73
|
-
|
73
|
+
true
|
74
74
|
end
|
75
75
|
|
76
76
|
def read_byte(addr)
|
data/lib/rubyboy/emulator.rb
CHANGED
@@ -18,6 +18,7 @@ module Rubyboy
|
|
18
18
|
@bus = Bus.new(@ppu, rom, ram, mbc, @timer, interrupt, @joypad, @apu)
|
19
19
|
@cpu = Cpu.new(@bus, interrupt)
|
20
20
|
@lcd = Lcd.new
|
21
|
+
@audio = Audio.new
|
21
22
|
end
|
22
23
|
|
23
24
|
def start
|
@@ -31,7 +32,7 @@ module Rubyboy
|
|
31
32
|
while elapsed_real_time > elapsed_machine_time
|
32
33
|
cycles = @cpu.exec
|
33
34
|
@timer.step(cycles)
|
34
|
-
@apu.step(cycles)
|
35
|
+
@audio.queue(@apu.samples) if @apu.step(cycles)
|
35
36
|
if @ppu.step(cycles)
|
36
37
|
@lcd.draw(@ppu.buffer)
|
37
38
|
key_input_check
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'apu'
|
4
4
|
require_relative 'bus'
|
5
5
|
require_relative 'cpu'
|
6
6
|
require_relative 'emulator'
|
7
|
-
require_relative '
|
7
|
+
require_relative 'ppu'
|
8
8
|
require_relative 'rom'
|
9
9
|
require_relative 'ram'
|
10
10
|
require_relative 'timer'
|
@@ -22,10 +22,10 @@ module Rubyboy
|
|
22
22
|
ram = Ram.new
|
23
23
|
mbc = Cartridge::Factory.create(rom, ram)
|
24
24
|
interrupt = Interrupt.new
|
25
|
-
@ppu =
|
25
|
+
@ppu = Ppu.new(interrupt)
|
26
26
|
@timer = Timer.new(interrupt)
|
27
27
|
@joypad = Joypad.new(interrupt)
|
28
|
-
@apu =
|
28
|
+
@apu = Apu.new
|
29
29
|
@bus = Bus.new(@ppu, rom, ram, mbc, @timer, interrupt, @joypad, @apu)
|
30
30
|
@cpu = Cpu.new(@bus, interrupt)
|
31
31
|
end
|
data/lib/rubyboy/lcd.rb
CHANGED
@@ -11,21 +11,21 @@ module Rubyboy
|
|
11
11
|
def initialize
|
12
12
|
raise SDL.GetError() if SDL.InitSubSystem(SDL::INIT_VIDEO) != 0
|
13
13
|
|
14
|
-
@buffer = FFI::MemoryPointer.new(:
|
15
|
-
@window = SDL.CreateWindow('
|
14
|
+
@buffer = FFI::MemoryPointer.new(:uint32, SCREEN_WIDTH * SCREEN_HEIGHT)
|
15
|
+
@window = SDL.CreateWindow('Ruby Boy', 0, 0, SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE, SDL::SDL_WINDOW_RESIZABLE)
|
16
16
|
|
17
17
|
raise SDL.GetError() if @window.null?
|
18
18
|
|
19
19
|
@renderer = SDL.CreateRenderer(@window, -1, 0)
|
20
20
|
SDL.SetHint('SDL_HINT_RENDER_SCALE_QUALITY', '2')
|
21
21
|
SDL.RenderSetLogicalSize(@renderer, SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE)
|
22
|
-
@texture = SDL.CreateTexture(@renderer, SDL::
|
22
|
+
@texture = SDL.CreateTexture(@renderer, SDL::PIXELFORMAT_ABGR8888, 1, SCREEN_WIDTH, SCREEN_HEIGHT)
|
23
23
|
@event = FFI::MemoryPointer.new(:pointer)
|
24
24
|
end
|
25
25
|
|
26
26
|
def draw(framebuffer)
|
27
|
-
@buffer.
|
28
|
-
SDL.UpdateTexture(@texture, nil, @buffer, SCREEN_WIDTH *
|
27
|
+
@buffer.write_array_of_uint32(framebuffer)
|
28
|
+
SDL.UpdateTexture(@texture, nil, @buffer, SCREEN_WIDTH * 4)
|
29
29
|
SDL.RenderClear(@renderer)
|
30
30
|
SDL.RenderCopy(@renderer, @texture, nil, nil)
|
31
31
|
SDL.RenderPresent(@renderer)
|
data/lib/rubyboy/ppu.rb
CHANGED
@@ -64,8 +64,14 @@ module Rubyboy
|
|
64
64
|
@wly = 0x00
|
65
65
|
@cycles = 0
|
66
66
|
@interrupt = interrupt
|
67
|
-
@buffer = Array.new(144 * 160
|
67
|
+
@buffer = Array.new(144 * 160, 0xffffffff)
|
68
68
|
@bg_pixels = Array.new(LCD_WIDTH, 0x00)
|
69
|
+
@tile_cache = Array.new(384) { Array.new(64, 0) }
|
70
|
+
@tile_map_cache = Array.new(2048, 0)
|
71
|
+
@bgp_cache = Array.new(4, 0xffffffff)
|
72
|
+
@obp0_cache = Array.new(4, 0xffffffff)
|
73
|
+
@obp1_cache = Array.new(4, 0xffffffff)
|
74
|
+
@sprite_cache = Array.new(40) { { y: 0xff, x: 0xff, tile_index: 0, flags: 0 } }
|
69
75
|
end
|
70
76
|
|
71
77
|
def read_byte(addr)
|
@@ -102,11 +108,32 @@ module Rubyboy
|
|
102
108
|
def write_byte(addr, value)
|
103
109
|
case addr
|
104
110
|
when 0x8000..0x9fff
|
105
|
-
|
111
|
+
if @mode != MODE[:drawing]
|
112
|
+
@vram[addr - 0x8000] = value
|
113
|
+
if addr < 0x9800
|
114
|
+
update_tile_cache(addr - 0x8000)
|
115
|
+
else
|
116
|
+
update_tile_map_cache(addr - 0x8000)
|
117
|
+
end
|
118
|
+
end
|
106
119
|
when 0xfe00..0xfe9f
|
107
|
-
|
120
|
+
if @mode != MODE[:oam_scan] && @mode != MODE[:drawing]
|
121
|
+
@oam[addr - 0xfe00] = value
|
122
|
+
sprite_index = (addr - 0xfe00) >> 2
|
123
|
+
attribute = (addr - 0xfe00) & 3
|
124
|
+
|
125
|
+
case attribute
|
126
|
+
when 0 then @sprite_cache[sprite_index][:y] = (value - 16) & 0xff
|
127
|
+
when 1 then @sprite_cache[sprite_index][:x] = (value - 8) & 0xff
|
128
|
+
when 2 then @sprite_cache[sprite_index][:tile_index] = value
|
129
|
+
when 3 then @sprite_cache[sprite_index][:flags] = value
|
130
|
+
end
|
131
|
+
end
|
108
132
|
when 0xff40
|
133
|
+
old_lcdc = @lcdc
|
109
134
|
@lcdc = value
|
135
|
+
|
136
|
+
refresh_tile_map_cache if old_lcdc[LCDC[:bg_window_tile_data_area]] != value[LCDC[:bg_window_tile_data_area]]
|
110
137
|
when 0xff41
|
111
138
|
@stat = value & 0x78
|
112
139
|
when 0xff42
|
@@ -119,10 +146,13 @@ module Rubyboy
|
|
119
146
|
@lyc = value
|
120
147
|
when 0xff47
|
121
148
|
@bgp = value
|
149
|
+
refresh_palette_cache(@bgp_cache, value)
|
122
150
|
when 0xff48
|
123
151
|
@obp0 = value
|
152
|
+
refresh_palette_cache(@obp0_cache, value)
|
124
153
|
when 0xff49
|
125
154
|
@obp1 = value
|
155
|
+
refresh_palette_cache(@obp1_cache, value)
|
126
156
|
when 0xff4a
|
127
157
|
@wy = value
|
128
158
|
when 0xff4b
|
@@ -189,19 +219,85 @@ module Rubyboy
|
|
189
219
|
def render_bg
|
190
220
|
return if @lcdc[LCDC[:bg_window_enable]] == 0
|
191
221
|
|
192
|
-
y = (@ly + @scy)
|
193
|
-
tile_map_addr =
|
194
|
-
tile_map_addr +=
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
222
|
+
y = (@ly + @scy) & 0xff
|
223
|
+
tile_map_addr = (y >> 3) << 5
|
224
|
+
tile_map_addr += 1024 if @lcdc[LCDC[:bg_tile_map_area]] == 1
|
225
|
+
tile_y = (y & 7) << 3
|
226
|
+
buffer_start_index = @ly * LCD_WIDTH
|
227
|
+
|
228
|
+
scx = @scx
|
229
|
+
buffer = @buffer
|
230
|
+
bg_pixels = @bg_pixels
|
231
|
+
tile_cache = @tile_cache
|
232
|
+
tile_map_cache = @tile_map_cache
|
233
|
+
bgp_cache = @bgp_cache
|
234
|
+
|
235
|
+
i = 0
|
236
|
+
current_tile = scx >> 3
|
237
|
+
x_offset = scx & 7
|
238
|
+
|
239
|
+
if x_offset > 0
|
240
|
+
tile = tile_cache[tile_map_cache[tile_map_addr + current_tile]]
|
241
|
+
while (x_offset + i) < 8
|
242
|
+
pixel = tile[tile_y + x_offset + i]
|
243
|
+
buffer[buffer_start_index + i] = bgp_cache[pixel]
|
244
|
+
bg_pixels[i] = pixel
|
245
|
+
i += 1
|
246
|
+
end
|
247
|
+
current_tile += 1
|
248
|
+
end
|
249
|
+
|
250
|
+
while i < LCD_WIDTH - 7
|
251
|
+
tile = tile_cache[tile_map_cache[tile_map_addr + (current_tile & 0x1f)]]
|
252
|
+
idx = buffer_start_index + i
|
253
|
+
|
254
|
+
# Unroll the 8-pixel loop
|
255
|
+
pixel = tile[tile_y]
|
256
|
+
buffer[idx] = bgp_cache[pixel]
|
257
|
+
bg_pixels[i] = pixel
|
258
|
+
|
259
|
+
pixel = tile[tile_y + 1]
|
260
|
+
buffer[idx + 1] = bgp_cache[pixel]
|
261
|
+
bg_pixels[i + 1] = pixel
|
262
|
+
|
263
|
+
pixel = tile[tile_y + 2]
|
264
|
+
buffer[idx + 2] = bgp_cache[pixel]
|
265
|
+
bg_pixels[i + 2] = pixel
|
266
|
+
|
267
|
+
pixel = tile[tile_y + 3]
|
268
|
+
buffer[idx + 3] = bgp_cache[pixel]
|
269
|
+
bg_pixels[i + 3] = pixel
|
270
|
+
|
271
|
+
pixel = tile[tile_y + 4]
|
272
|
+
buffer[idx + 4] = bgp_cache[pixel]
|
273
|
+
bg_pixels[i + 4] = pixel
|
274
|
+
|
275
|
+
pixel = tile[tile_y + 5]
|
276
|
+
buffer[idx + 5] = bgp_cache[pixel]
|
277
|
+
bg_pixels[i + 5] = pixel
|
278
|
+
|
279
|
+
pixel = tile[tile_y + 6]
|
280
|
+
buffer[idx + 6] = bgp_cache[pixel]
|
281
|
+
bg_pixels[i + 6] = pixel
|
282
|
+
|
283
|
+
pixel = tile[tile_y + 7]
|
284
|
+
buffer[idx + 7] = bgp_cache[pixel]
|
285
|
+
bg_pixels[i + 7] = pixel
|
286
|
+
|
287
|
+
i += 8
|
288
|
+
current_tile += 1
|
289
|
+
end
|
290
|
+
|
291
|
+
return unless i < LCD_WIDTH
|
292
|
+
|
293
|
+
tile = tile_cache[tile_map_cache[tile_map_addr + (current_tile & 0x1f)]]
|
294
|
+
x = 0
|
295
|
+
while i < LCD_WIDTH
|
296
|
+
pixel = tile[tile_y + x]
|
297
|
+
buffer[buffer_start_index + i] = bgp_cache[pixel]
|
298
|
+
bg_pixels[i] = pixel
|
299
|
+
x += 1
|
300
|
+
i += 1
|
205
301
|
end
|
206
302
|
end
|
207
303
|
|
@@ -210,20 +306,18 @@ module Rubyboy
|
|
210
306
|
|
211
307
|
rendered = false
|
212
308
|
y = @wly
|
213
|
-
tile_map_addr =
|
214
|
-
tile_map_addr +=
|
309
|
+
tile_map_addr = (y >> 3) << 5
|
310
|
+
tile_map_addr += 1024 if @lcdc[LCDC[:window_tile_map_area]] == 1
|
311
|
+
tile_y = (y & 7) << 3
|
312
|
+
buffer_start_index = @ly * LCD_WIDTH
|
215
313
|
LCD_WIDTH.times do |i|
|
216
314
|
next if i < @wx - 7
|
217
315
|
|
218
316
|
rendered = true
|
219
317
|
x = i - (@wx - 7)
|
220
|
-
tile_index =
|
221
|
-
pixel =
|
222
|
-
|
223
|
-
base = @ly * LCD_WIDTH * 3 + i * 3
|
224
|
-
@buffer[base] = color
|
225
|
-
@buffer[base + 1] = color
|
226
|
-
@buffer[base + 2] = color
|
318
|
+
tile_index = @tile_map_cache[tile_map_addr + (x >> 3)]
|
319
|
+
pixel = @tile_cache[tile_index][tile_y + (x & 7)]
|
320
|
+
@buffer[buffer_start_index + i] = @bgp_cache[pixel]
|
227
321
|
@bg_pixels[i] = pixel
|
228
322
|
end
|
229
323
|
@wly += 1 if rendered
|
@@ -236,62 +330,83 @@ module Rubyboy
|
|
236
330
|
sprites = []
|
237
331
|
cnt = 0
|
238
332
|
|
239
|
-
@
|
240
|
-
y
|
241
|
-
x = (x - 8) % 256
|
242
|
-
next if y > @ly || y + sprite_height <= @ly
|
333
|
+
@sprite_cache.each do |sprite|
|
334
|
+
next if sprite[:y] > @ly || sprite[:y] + sprite_height <= @ly
|
243
335
|
|
244
|
-
sprites <<
|
336
|
+
sprites << sprite
|
245
337
|
cnt += 1
|
246
338
|
break if cnt == 10
|
247
339
|
end
|
248
|
-
sprites
|
340
|
+
sprites.reverse!
|
341
|
+
sprites.sort! { |a, b| b[:x] <=> a[:x] }
|
249
342
|
|
250
343
|
sprites.each do |sprite|
|
251
344
|
flags = sprite[:flags]
|
252
|
-
pallet = flags[SPRITE_FLAGS[:dmg_palette]] == 0 ? @
|
345
|
+
pallet = flags[SPRITE_FLAGS[:dmg_palette]] == 0 ? @obp0_cache : @obp1_cache
|
253
346
|
tile_index = sprite[:tile_index]
|
254
347
|
tile_index &= 0xfe if sprite_height == 16
|
255
|
-
y = (@ly - sprite[:y])
|
348
|
+
y = (@ly - sprite[:y]) & 0xff
|
256
349
|
y = sprite_height - y - 1 if flags[SPRITE_FLAGS[:y_flip]] == 1
|
257
|
-
tile_index = (tile_index + 1)
|
258
|
-
y
|
350
|
+
tile_index = (tile_index + 1) & 0xff if y >= 8
|
351
|
+
tile_y = (y & 7) << 3
|
352
|
+
buffer_start_index = @ly * LCD_WIDTH
|
259
353
|
|
260
354
|
8.times do |x|
|
261
355
|
x_flipped = flags[SPRITE_FLAGS[:x_flip]] == 1 ? 7 - x : x
|
262
356
|
|
263
|
-
pixel =
|
264
|
-
i = (sprite[:x] + x)
|
357
|
+
pixel = @tile_cache[tile_index][tile_y + x_flipped]
|
358
|
+
i = (sprite[:x] + x) & 0xff
|
265
359
|
|
266
360
|
next if pixel == 0 || i >= LCD_WIDTH
|
267
361
|
next if flags[SPRITE_FLAGS[:priority]] == 1 && @bg_pixels[i] != 0
|
268
362
|
|
269
|
-
|
270
|
-
base = @ly * LCD_WIDTH * 3 + i * 3
|
271
|
-
@buffer[base] = color
|
272
|
-
@buffer[base + 1] = color
|
273
|
-
@buffer[base + 2] = color
|
363
|
+
@buffer[buffer_start_index + i] = pallet[pixel]
|
274
364
|
end
|
275
365
|
end
|
276
366
|
end
|
277
367
|
|
278
368
|
private
|
279
369
|
|
280
|
-
def
|
281
|
-
tile_index =
|
282
|
-
|
370
|
+
def update_tile_cache(addr)
|
371
|
+
tile_index = addr >> 4
|
372
|
+
row = ((addr & 0xf) >> 1) << 3
|
373
|
+
|
374
|
+
byte1 = @vram[addr & ~1]
|
375
|
+
byte2 = @vram[addr | 1]
|
376
|
+
|
377
|
+
8.times do |col|
|
378
|
+
bit_index = 7 - col
|
379
|
+
pixel = ((byte1 >> bit_index) & 1) | (((byte2 >> bit_index) & 1) << 1)
|
380
|
+
@tile_cache[tile_index][row + col] = pixel
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def update_tile_map_cache(addr)
|
385
|
+
map_index = addr - 0x1800
|
386
|
+
tile_index = @vram[addr]
|
387
|
+
@tile_map_cache[map_index] = @lcdc[LCDC[:bg_window_tile_data_area]] == 0 ? to_signed_byte(tile_index) + 256 : tile_index
|
283
388
|
end
|
284
389
|
|
285
|
-
def
|
286
|
-
@
|
390
|
+
def refresh_tile_map_cache
|
391
|
+
if @lcdc[LCDC[:bg_window_tile_data_area]] == 0
|
392
|
+
(0x1800..0x1fff).each do |addr|
|
393
|
+
@tile_map_cache[addr - 0x1800] = to_signed_byte(@vram[addr]) + 256
|
394
|
+
end
|
395
|
+
else
|
396
|
+
(0x1800..0x1fff).each do |addr|
|
397
|
+
@tile_map_cache[addr - 0x1800] = @vram[addr]
|
398
|
+
end
|
399
|
+
end
|
287
400
|
end
|
288
401
|
|
289
|
-
def
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
402
|
+
def refresh_palette_cache(cache, palette_value)
|
403
|
+
4.times do |i|
|
404
|
+
case (palette_value >> (i << 1)) & 0b11
|
405
|
+
when 0 then cache[i] = 0xffffffff
|
406
|
+
when 1 then cache[i] = 0xffaaaaaa
|
407
|
+
when 2 then cache[i] = 0xff555555
|
408
|
+
when 3 then cache[i] = 0xff000000
|
409
|
+
end
|
295
410
|
end
|
296
411
|
end
|
297
412
|
|
data/lib/rubyboy/raylib/lcd.rb
CHANGED
@@ -12,7 +12,7 @@ module Rubyboy
|
|
12
12
|
SCALE = 4
|
13
13
|
|
14
14
|
def initialize
|
15
|
-
InitWindow(WIDTH * SCALE, HEIGHT * SCALE, '
|
15
|
+
InitWindow(WIDTH * SCALE, HEIGHT * SCALE, 'Ruby Boy')
|
16
16
|
image = GenImageColor(WIDTH, HEIGHT, BLACK)
|
17
17
|
image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8
|
18
18
|
@texture = LoadTextureFromImage(image)
|
data/lib/rubyboy/sdl.rb
CHANGED
data/lib/rubyboy/version.rb
CHANGED
data/lib/rubyboy.rb
CHANGED
Binary file
|
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyboy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.4.
|
4
|
+
version: 1.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sacckey
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -84,7 +84,6 @@ files:
|
|
84
84
|
- lib/rubyboy/apu_channels/channel2.rb
|
85
85
|
- lib/rubyboy/apu_channels/channel3.rb
|
86
86
|
- lib/rubyboy/apu_channels/channel4.rb
|
87
|
-
- lib/rubyboy/apu_wasm.rb
|
88
87
|
- lib/rubyboy/audio.rb
|
89
88
|
- lib/rubyboy/bus.rb
|
90
89
|
- lib/rubyboy/cartridge/factory.rb
|
@@ -97,7 +96,6 @@ files:
|
|
97
96
|
- lib/rubyboy/joypad.rb
|
98
97
|
- lib/rubyboy/lcd.rb
|
99
98
|
- lib/rubyboy/ppu.rb
|
100
|
-
- lib/rubyboy/ppu_wasm.rb
|
101
99
|
- lib/rubyboy/ram.rb
|
102
100
|
- lib/rubyboy/raylib/audio.rb
|
103
101
|
- lib/rubyboy/raylib/lcd.rb
|
data/lib/rubyboy/apu_wasm.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# require_relative 'audio'
|
4
|
-
require_relative 'apu_channels/channel1'
|
5
|
-
require_relative 'apu_channels/channel2'
|
6
|
-
require_relative 'apu_channels/channel3'
|
7
|
-
require_relative 'apu_channels/channel4'
|
8
|
-
|
9
|
-
module Rubyboy
|
10
|
-
class ApuWasm
|
11
|
-
def initialize
|
12
|
-
@audio = nil
|
13
|
-
@nr50 = 0
|
14
|
-
@nr51 = 0
|
15
|
-
@cycles = 0
|
16
|
-
@sampling_cycles = 0
|
17
|
-
@fs = 0
|
18
|
-
@samples = Array.new(1024, 0.0)
|
19
|
-
@sample_idx = 0
|
20
|
-
@channel1 = ApuChannels::Channel1.new
|
21
|
-
@channel2 = ApuChannels::Channel2.new
|
22
|
-
@channel3 = ApuChannels::Channel3.new
|
23
|
-
@channel4 = ApuChannels::Channel4.new
|
24
|
-
end
|
25
|
-
|
26
|
-
def step(cycles)
|
27
|
-
@cycles += cycles
|
28
|
-
@sampling_cycles += cycles
|
29
|
-
|
30
|
-
@channel1.step(cycles)
|
31
|
-
@channel2.step(cycles)
|
32
|
-
@channel3.step(cycles)
|
33
|
-
@channel4.step(cycles)
|
34
|
-
|
35
|
-
if @cycles >= 0x2000
|
36
|
-
@cycles -= 0x2000
|
37
|
-
|
38
|
-
@channel1.step_fs(@fs)
|
39
|
-
@channel2.step_fs(@fs)
|
40
|
-
@channel3.step_fs(@fs)
|
41
|
-
@channel4.step_fs(@fs)
|
42
|
-
|
43
|
-
@fs = (@fs + 1) % 8
|
44
|
-
end
|
45
|
-
|
46
|
-
if @sampling_cycles >= 87
|
47
|
-
@sampling_cycles -= 87
|
48
|
-
|
49
|
-
left_sample = (
|
50
|
-
@nr51[7] * @channel4.dac_output +
|
51
|
-
@nr51[6] * @channel3.dac_output +
|
52
|
-
@nr51[5] * @channel2.dac_output +
|
53
|
-
@nr51[4] * @channel1.dac_output
|
54
|
-
) / 4.0
|
55
|
-
|
56
|
-
right_sample = (
|
57
|
-
@nr51[3] * @channel4.dac_output +
|
58
|
-
@nr51[2] * @channel3.dac_output +
|
59
|
-
@nr51[1] * @channel2.dac_output +
|
60
|
-
@nr51[0] * @channel1.dac_output
|
61
|
-
) / 4.0
|
62
|
-
|
63
|
-
raise "#{@nr51} #{@channel4.dac_output}, #{@channel3.dac_output}, #{@channel2.dac_output},#{@channel1.dac_output}" if left_sample.abs > 1.0 || right_sample.abs > 1.0
|
64
|
-
|
65
|
-
@samples[@sample_idx * 2] = (@nr50[4..6] / 7.0) * left_sample / 8.0
|
66
|
-
@samples[@sample_idx * 2 + 1] = (@nr50[0..2] / 7.0) * right_sample / 8.0
|
67
|
-
@sample_idx += 1
|
68
|
-
end
|
69
|
-
|
70
|
-
return if @sample_idx < 512
|
71
|
-
|
72
|
-
@sample_idx = 0
|
73
|
-
@audio.queue(@samples)
|
74
|
-
end
|
75
|
-
|
76
|
-
def read_byte(addr)
|
77
|
-
case addr
|
78
|
-
when 0xff10..0xff14 then @channel1.read_nr1x(addr - 0xff10)
|
79
|
-
when 0xff15..0xff19 then @channel2.read_nr2x(addr - 0xff15)
|
80
|
-
when 0xff1a..0xff1e then @channel3.read_nr3x(addr - 0xff1a)
|
81
|
-
when 0xff1f..0xff23 then @channel4.read_nr4x(addr - 0xff1f)
|
82
|
-
when 0xff24 then @nr50
|
83
|
-
when 0xff25 then @nr51
|
84
|
-
when 0xff26 then (@channel1.enabled ? 0x01 : 0x00) | (@channel2.enabled ? 0x02 : 0x00) | (@channel3.enabled ? 0x04 : 0x00) | (@channel4.enabled ? 0x08 : 0x00) | 0x70 | (@enabled ? 0x80 : 0x00)
|
85
|
-
when 0xff30..0xff3f then @channel3.wave_ram[(addr - 0xff30)]
|
86
|
-
else raise "Invalid APU read at #{addr.to_s(16)}"
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def write_byte(addr, val)
|
91
|
-
return if !@enabled && ![0xff11, 0xff16, 0xff1b, 0xff20, 0xff26].include?(addr) && !(0xff30..0xff3f).include?(addr)
|
92
|
-
|
93
|
-
val &= 0x3f if !@enabled && [0xff11, 0xff16, 0xff1b, 0xff20].include?(addr)
|
94
|
-
|
95
|
-
case addr
|
96
|
-
when 0xff10..0xff14 then @channel1.write_nr1x(addr - 0xff10, val)
|
97
|
-
when 0xff15..0xff19 then @channel2.write_nr2x(addr - 0xff15, val)
|
98
|
-
when 0xff1a..0xff1e then @channel3.write_nr3x(addr - 0xff1a, val)
|
99
|
-
when 0xff1f..0xff23 then @channel4.write_nr4x(addr - 0xff1f, val)
|
100
|
-
when 0xff24 then @nr50 = val
|
101
|
-
when 0xff25 then @nr51 = val
|
102
|
-
when 0xff26
|
103
|
-
flg = val & 0x80 > 0
|
104
|
-
if !flg && @enabled
|
105
|
-
(0xff10..0xff25).each { |a| write_byte(a, 0) }
|
106
|
-
elsif flg && !@enabled
|
107
|
-
@fs = 0
|
108
|
-
@channel1.wave_duty_position = 0
|
109
|
-
@channel2.wave_duty_position = 0
|
110
|
-
@channel3.wave_duty_position = 0
|
111
|
-
end
|
112
|
-
@enabled = flg
|
113
|
-
when 0xff30..0xff3f then @channel3.wave_ram[(addr - 0xff30)] = val
|
114
|
-
else raise "Invalid APU write at #{addr.to_s(16)}"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
data/lib/rubyboy/ppu_wasm.rb
DELETED
@@ -1,312 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Rubyboy
|
4
|
-
class PpuWasm
|
5
|
-
attr_reader :buffer
|
6
|
-
|
7
|
-
MODE = {
|
8
|
-
hblank: 0,
|
9
|
-
vblank: 1,
|
10
|
-
oam_scan: 2,
|
11
|
-
drawing: 3
|
12
|
-
}.freeze
|
13
|
-
|
14
|
-
LCDC = {
|
15
|
-
bg_window_enable: 0,
|
16
|
-
sprite_enable: 1,
|
17
|
-
sprite_size: 2,
|
18
|
-
bg_tile_map_area: 3,
|
19
|
-
bg_window_tile_data_area: 4,
|
20
|
-
window_enable: 5,
|
21
|
-
window_tile_map_area: 6,
|
22
|
-
lcd_ppu_enable: 7
|
23
|
-
}.freeze
|
24
|
-
|
25
|
-
STAT = {
|
26
|
-
ly_eq_lyc: 2,
|
27
|
-
hblank: 3,
|
28
|
-
vblank: 4,
|
29
|
-
oam_scan: 5,
|
30
|
-
lyc: 6
|
31
|
-
}.freeze
|
32
|
-
|
33
|
-
SPRITE_FLAGS = {
|
34
|
-
bank: 3,
|
35
|
-
dmg_palette: 4,
|
36
|
-
x_flip: 5,
|
37
|
-
y_flip: 6,
|
38
|
-
priority: 7
|
39
|
-
}.freeze
|
40
|
-
|
41
|
-
LCD_WIDTH = 160
|
42
|
-
LCD_HEIGHT = 144
|
43
|
-
|
44
|
-
OAM_SCAN_CYCLES = 80
|
45
|
-
DRAWING_CYCLES = 172
|
46
|
-
HBLANK_CYCLES = 204
|
47
|
-
ONE_LINE_CYCLES = OAM_SCAN_CYCLES + DRAWING_CYCLES + HBLANK_CYCLES
|
48
|
-
|
49
|
-
def initialize(interrupt)
|
50
|
-
@mode = MODE[:oam_scan]
|
51
|
-
@lcdc = 0x91
|
52
|
-
@stat = 0x00
|
53
|
-
@scy = 0x00
|
54
|
-
@scx = 0x00
|
55
|
-
@ly = 0x00
|
56
|
-
@lyc = 0x00
|
57
|
-
@obp0 = 0x00
|
58
|
-
@obp1 = 0x00
|
59
|
-
@wy = 0x00
|
60
|
-
@wx = 0x00
|
61
|
-
@bgp = 0x00
|
62
|
-
@vram = Array.new(0x2000, 0x00)
|
63
|
-
@oam = Array.new(0xa0, 0x00)
|
64
|
-
@wly = 0x00
|
65
|
-
@cycles = 0
|
66
|
-
@interrupt = interrupt
|
67
|
-
@buffer = Array.new(144 * 160 * 4, 0xff)
|
68
|
-
@bg_pixels = Array.new(LCD_WIDTH, 0x00)
|
69
|
-
end
|
70
|
-
|
71
|
-
def read_byte(addr)
|
72
|
-
case addr
|
73
|
-
when 0x8000..0x9fff
|
74
|
-
@mode == MODE[:drawing] ? 0xff : @vram[addr - 0x8000]
|
75
|
-
when 0xfe00..0xfe9f
|
76
|
-
@mode == MODE[:oam_scan] || @mode == MODE[:drawing] ? 0xff : @oam[addr - 0xfe00]
|
77
|
-
when 0xff40
|
78
|
-
@lcdc
|
79
|
-
when 0xff41
|
80
|
-
@stat | 0x80 | @mode
|
81
|
-
when 0xff42
|
82
|
-
@scy
|
83
|
-
when 0xff43
|
84
|
-
@scx
|
85
|
-
when 0xff44
|
86
|
-
@ly
|
87
|
-
when 0xff45
|
88
|
-
@lyc
|
89
|
-
when 0xff47
|
90
|
-
@bgp
|
91
|
-
when 0xff48
|
92
|
-
@obp0
|
93
|
-
when 0xff49
|
94
|
-
@obp1
|
95
|
-
when 0xff4a
|
96
|
-
@wy
|
97
|
-
when 0xff4b
|
98
|
-
@wx
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def write_byte(addr, value)
|
103
|
-
case addr
|
104
|
-
when 0x8000..0x9fff
|
105
|
-
@vram[addr - 0x8000] = value if @mode != MODE[:drawing]
|
106
|
-
when 0xfe00..0xfe9f
|
107
|
-
@oam[addr - 0xfe00] = value if @mode != MODE[:oam_scan] && @mode != MODE[:drawing]
|
108
|
-
when 0xff40
|
109
|
-
@lcdc = value
|
110
|
-
when 0xff41
|
111
|
-
@stat = value & 0x78
|
112
|
-
when 0xff42
|
113
|
-
@scy = value
|
114
|
-
when 0xff43
|
115
|
-
@scx = value
|
116
|
-
when 0xff44
|
117
|
-
# ly is read only
|
118
|
-
when 0xff45
|
119
|
-
@lyc = value
|
120
|
-
when 0xff47
|
121
|
-
@bgp = value
|
122
|
-
when 0xff48
|
123
|
-
@obp0 = value
|
124
|
-
when 0xff49
|
125
|
-
@obp1 = value
|
126
|
-
when 0xff4a
|
127
|
-
@wy = value
|
128
|
-
when 0xff4b
|
129
|
-
@wx = value
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def step(cycles)
|
134
|
-
return false if @lcdc[LCDC[:lcd_ppu_enable]] == 0
|
135
|
-
|
136
|
-
res = false
|
137
|
-
@cycles += cycles
|
138
|
-
|
139
|
-
case @mode
|
140
|
-
when MODE[:oam_scan]
|
141
|
-
if @cycles >= OAM_SCAN_CYCLES
|
142
|
-
@cycles -= OAM_SCAN_CYCLES
|
143
|
-
@mode = MODE[:drawing]
|
144
|
-
end
|
145
|
-
when MODE[:drawing]
|
146
|
-
if @cycles >= DRAWING_CYCLES
|
147
|
-
render_bg
|
148
|
-
render_window
|
149
|
-
render_sprites
|
150
|
-
@cycles -= DRAWING_CYCLES
|
151
|
-
@mode = MODE[:hblank]
|
152
|
-
@interrupt.request(:lcd) if @stat[STAT[:hblank]] == 1
|
153
|
-
end
|
154
|
-
when MODE[:hblank]
|
155
|
-
if @cycles >= HBLANK_CYCLES
|
156
|
-
@cycles -= HBLANK_CYCLES
|
157
|
-
@ly += 1
|
158
|
-
handle_ly_eq_lyc
|
159
|
-
|
160
|
-
if @ly == LCD_HEIGHT
|
161
|
-
@mode = MODE[:vblank]
|
162
|
-
@interrupt.request(:vblank)
|
163
|
-
@interrupt.request(:lcd) if @stat[STAT[:vblank]] == 1
|
164
|
-
else
|
165
|
-
@mode = MODE[:oam_scan]
|
166
|
-
@interrupt.request(:lcd) if @stat[STAT[:oam_scan]] == 1
|
167
|
-
end
|
168
|
-
end
|
169
|
-
when MODE[:vblank]
|
170
|
-
if @cycles >= ONE_LINE_CYCLES
|
171
|
-
@cycles -= ONE_LINE_CYCLES
|
172
|
-
@ly += 1
|
173
|
-
handle_ly_eq_lyc
|
174
|
-
|
175
|
-
if @ly == 154
|
176
|
-
@ly = 0
|
177
|
-
@wly = 0
|
178
|
-
handle_ly_eq_lyc
|
179
|
-
@mode = MODE[:oam_scan]
|
180
|
-
@interrupt.request(:lcd) if @stat[STAT[:oam_scan]] == 1
|
181
|
-
res = true
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
res
|
187
|
-
end
|
188
|
-
|
189
|
-
def render_bg
|
190
|
-
return if @lcdc[LCDC[:bg_window_enable]] == 0
|
191
|
-
|
192
|
-
y = (@ly + @scy) % 256
|
193
|
-
tile_map_addr = @lcdc[LCDC[:bg_tile_map_area]] == 0 ? 0x1800 : 0x1c00
|
194
|
-
tile_map_addr += (y / 8) * 32
|
195
|
-
LCD_WIDTH.times do |i|
|
196
|
-
x = (i + @scx) % 256
|
197
|
-
tile_index = get_tile_index(tile_map_addr + (x / 8))
|
198
|
-
pixel = get_pixel(tile_index << 4, 7 - (x % 8), (y % 8) * 2)
|
199
|
-
color = get_color(@bgp, pixel)
|
200
|
-
base = @ly * LCD_WIDTH * 4 + i * 4
|
201
|
-
@buffer[base] = color
|
202
|
-
@buffer[base + 1] = color
|
203
|
-
@buffer[base + 2] = color
|
204
|
-
@bg_pixels[i] = pixel
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
def render_window
|
209
|
-
return if @lcdc[LCDC[:bg_window_enable]] == 0 || @lcdc[LCDC[:window_enable]] == 0 || @ly < @wy
|
210
|
-
|
211
|
-
rendered = false
|
212
|
-
y = @wly
|
213
|
-
tile_map_addr = @lcdc[LCDC[:window_tile_map_area]] == 0 ? 0x1800 : 0x1c00
|
214
|
-
tile_map_addr += (y / 8) * 32
|
215
|
-
LCD_WIDTH.times do |i|
|
216
|
-
next if i < @wx - 7
|
217
|
-
|
218
|
-
rendered = true
|
219
|
-
x = i - (@wx - 7)
|
220
|
-
tile_index = get_tile_index(tile_map_addr + (x / 8))
|
221
|
-
pixel = get_pixel(tile_index << 4, 7 - (x % 8), (y % 8) * 2)
|
222
|
-
color = get_color(@bgp, pixel)
|
223
|
-
base = @ly * LCD_WIDTH * 4 + i * 4
|
224
|
-
@buffer[base] = color
|
225
|
-
@buffer[base + 1] = color
|
226
|
-
@buffer[base + 2] = color
|
227
|
-
@bg_pixels[i] = pixel
|
228
|
-
end
|
229
|
-
@wly += 1 if rendered
|
230
|
-
end
|
231
|
-
|
232
|
-
def render_sprites
|
233
|
-
return if @lcdc[LCDC[:sprite_enable]] == 0
|
234
|
-
|
235
|
-
sprite_height = @lcdc[LCDC[:sprite_size]] == 0 ? 8 : 16
|
236
|
-
sprites = []
|
237
|
-
cnt = 0
|
238
|
-
|
239
|
-
@oam.each_slice(4) do |y, x, tile_index, flags|
|
240
|
-
y = (y - 16) % 256
|
241
|
-
x = (x - 8) % 256
|
242
|
-
next if y > @ly || y + sprite_height <= @ly
|
243
|
-
|
244
|
-
sprites << { y:, x:, tile_index:, flags: }
|
245
|
-
cnt += 1
|
246
|
-
break if cnt == 10
|
247
|
-
end
|
248
|
-
sprites = sprites.sort_by.with_index { |sprite, i| [-sprite[:x], -i] }
|
249
|
-
|
250
|
-
sprites.each do |sprite|
|
251
|
-
flags = sprite[:flags]
|
252
|
-
pallet = flags[SPRITE_FLAGS[:dmg_palette]] == 0 ? @obp0 : @obp1
|
253
|
-
tile_index = sprite[:tile_index]
|
254
|
-
tile_index &= 0xfe if sprite_height == 16
|
255
|
-
y = (@ly - sprite[:y]) % 256
|
256
|
-
y = sprite_height - y - 1 if flags[SPRITE_FLAGS[:y_flip]] == 1
|
257
|
-
tile_index = (tile_index + 1) % 256 if y >= 8
|
258
|
-
y %= 8
|
259
|
-
|
260
|
-
8.times do |x|
|
261
|
-
x_flipped = flags[SPRITE_FLAGS[:x_flip]] == 1 ? 7 - x : x
|
262
|
-
|
263
|
-
pixel = get_pixel(tile_index << 4, 7 - x_flipped, (y % 8) * 2)
|
264
|
-
i = (sprite[:x] + x) % 256
|
265
|
-
|
266
|
-
next if pixel == 0 || i >= LCD_WIDTH
|
267
|
-
next if flags[SPRITE_FLAGS[:priority]] == 1 && @bg_pixels[i] != 0
|
268
|
-
|
269
|
-
color = get_color(pallet, pixel)
|
270
|
-
base = @ly * LCD_WIDTH * 4 + i * 4
|
271
|
-
@buffer[base] = color
|
272
|
-
@buffer[base + 1] = color
|
273
|
-
@buffer[base + 2] = color
|
274
|
-
end
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
private
|
279
|
-
|
280
|
-
def get_tile_index(tile_map_addr)
|
281
|
-
tile_index = @vram[tile_map_addr]
|
282
|
-
@lcdc[LCDC[:bg_window_tile_data_area]] == 0 ? to_signed_byte(tile_index) + 256 : tile_index
|
283
|
-
end
|
284
|
-
|
285
|
-
def get_pixel(tile_index, c, r)
|
286
|
-
@vram[tile_index + r][c] + (@vram[tile_index + r + 1][c] << 1)
|
287
|
-
end
|
288
|
-
|
289
|
-
def get_color(pallet, pixel)
|
290
|
-
case (pallet >> (pixel * 2)) & 0b11
|
291
|
-
when 0 then 0xff
|
292
|
-
when 1 then 0xaa
|
293
|
-
when 2 then 0x55
|
294
|
-
when 3 then 0x00
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
def to_signed_byte(byte)
|
299
|
-
byte &= 0xff
|
300
|
-
byte > 127 ? byte - 256 : byte
|
301
|
-
end
|
302
|
-
|
303
|
-
def handle_ly_eq_lyc
|
304
|
-
if @ly == @lyc
|
305
|
-
@stat |= 0x04
|
306
|
-
@interrupt.request(:lcd) if @stat[STAT[:lyc]] == 1
|
307
|
-
else
|
308
|
-
@stat &= 0xfb
|
309
|
-
end
|
310
|
-
end
|
311
|
-
end
|
312
|
-
end
|