From 7a7d8007e841794a8c0aa1b2921ce92cfd54ae60 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 19 Oct 2025 10:46:54 +0200 Subject: [PATCH 1/2] Make benchmarks/optcarrot/benchmark.rb compatible with the Ractor harness * Without needing a copy of the benchmark files. --- benchmarks.yml | 1 + benchmarks/optcarrot/benchmark.rb | 46 +++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/benchmarks.yml b/benchmarks.yml index dc58626e..2932094c 100644 --- a/benchmarks.yml +++ b/benchmarks.yml @@ -85,6 +85,7 @@ nqueens: ractor: true optcarrot: desc: optcarrot is a functional headless NES emulator, run on a specific game cartridge for a specific number of frames. + ractor: true protoboeuf: desc: protoboeuf (pure-Ruby protobuf) message decoding ractor: true diff --git a/benchmarks/optcarrot/benchmark.rb b/benchmarks/optcarrot/benchmark.rb index a5b2e4d3..0c4c53ab 100755 --- a/benchmarks/optcarrot/benchmark.rb +++ b/benchmarks/optcarrot/benchmark.rb @@ -1,10 +1,46 @@ require_relative '../../harness/loader' require_relative "lib/optcarrot" -rom_path = File.join(__dir__, "examples/Lan_Master.nes") -nes = Optcarrot::NES.new(["--headless", rom_path]) -nes.reset +if ENV["YJIT_BENCH_RACTOR_HARNESS"] + # Based on bin/optcarrot-bench-parallel-on-ractor + [ + Optcarrot::Config::DEFAULT_OPTIONS, + Optcarrot::Config::OPTIONS, + Optcarrot::Driver::DRIVER_DB, + Optcarrot::Audio::PACK_FORMAT, + Optcarrot::APU::Pulse::WAVE_FORM, + Optcarrot::APU::Triangle::WAVE_FORM, + Optcarrot::APU::FRAME_CLOCKS, + Optcarrot::APU::OSCILLATOR_CLOCKS, + Optcarrot::APU::LengthCounter::LUT, + Optcarrot::APU::Noise::LUT, + Optcarrot::APU::Noise::NEXT_BITS_1, + Optcarrot::APU::Noise::NEXT_BITS_6, + Optcarrot::APU::DMC::LUT, + Optcarrot::PPU::DUMMY_FRAME, + Optcarrot::PPU::BOOT_FRAME, + Optcarrot::PPU::SP_PIXEL_POSITIONS, + Optcarrot::PPU::TILE_LUT, + Optcarrot::PPU::NMT_TABLE, + Optcarrot::CPU::DISPATCH, + Optcarrot::ROM::MAPPER_DB, + ].each { |const| Ractor.make_shareable(const) } -run_benchmark(10) do - 200.times { nes.step } + ROM_PATH = File.join(__dir__, "examples/Lan_Master.nes").freeze + ENV["WARMUP_ITRS"] = "1" + + run_benchmark(10) do + nes = Optcarrot::NES.new(["-b", "--no-print-video-checksum", ROM_PATH]) + nes.reset + + 200.times { nes.step } + end +else + rom_path = File.join(__dir__, "examples/Lan_Master.nes") + nes = Optcarrot::NES.new(["--headless", rom_path]) + nes.reset + + run_benchmark(10) do + 200.times { nes.step } + end end From 1fc0ebc71cc320db6641e25bd0b6d59316976293 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Sun, 19 Oct 2025 10:47:44 +0200 Subject: [PATCH 2/2] Remove Ractor copy of Optcarrot --- benchmarks-ractor/optcarrot/.dockerignore | 23 - benchmarks-ractor/optcarrot/.gitignore | 21 - benchmarks-ractor/optcarrot/.rubocop.yml | 236 --- benchmarks-ractor/optcarrot/.travis.yml | 3 - benchmarks-ractor/optcarrot/Gemfile | 6 - benchmarks-ractor/optcarrot/LICENSE | 21 - benchmarks-ractor/optcarrot/benchmark.rb | 12 - .../optcarrot/examples/Lan_Master.nes | Bin 40976 -> 0 bytes benchmarks-ractor/optcarrot/lib/optcarrot.rb | 14 - .../optcarrot/lib/optcarrot/apu.rb | 858 ---------- .../optcarrot/lib/optcarrot/config.rb | 259 --- .../optcarrot/lib/optcarrot/cpu.rb | 1183 ------------- .../optcarrot/lib/optcarrot/driver.rb | 165 -- .../lib/optcarrot/driver/ao_audio.rb | 63 - .../lib/optcarrot/driver/gif_video.rb | 71 - .../lib/optcarrot/driver/log_input.rb | 36 - .../optcarrot/lib/optcarrot/driver/misc.rb | 123 -- .../lib/optcarrot/driver/mplayer_video.rb | 47 - .../lib/optcarrot/driver/png_video.rb | 74 - .../optcarrot/lib/optcarrot/driver/sdl2.rb | 214 --- .../lib/optcarrot/driver/sdl2_audio.rb | 61 - .../lib/optcarrot/driver/sdl2_input.rb | 126 -- .../lib/optcarrot/driver/sdl2_video.rb | 88 - .../optcarrot/lib/optcarrot/driver/sfml.rb | 134 -- .../lib/optcarrot/driver/sfml_audio.rb | 46 - .../lib/optcarrot/driver/sfml_input.rb | 75 - .../lib/optcarrot/driver/sfml_video.rb | 84 - .../lib/optcarrot/driver/sixel_video.rb | 63 - .../lib/optcarrot/driver/term_input.rb | 52 - .../lib/optcarrot/driver/wav_audio.rb | 21 - .../optcarrot/lib/optcarrot/mapper/cnrom.rb | 14 - .../optcarrot/lib/optcarrot/mapper/mmc1.rb | 105 -- .../optcarrot/lib/optcarrot/mapper/mmc3.rb | 153 -- .../optcarrot/lib/optcarrot/mapper/uxrom.rb | 14 - .../optcarrot/lib/optcarrot/nes.rb | 105 -- .../optcarrot/lib/optcarrot/opt.rb | 168 -- .../optcarrot/lib/optcarrot/pad.rb | 92 -- .../optcarrot/lib/optcarrot/palette.rb | 65 - .../optcarrot/lib/optcarrot/ppu.rb | 1470 ----------------- .../optcarrot/lib/optcarrot/rom.rb | 144 -- benchmarks-ractor/optcarrot/optcarrot.gemspec | 26 - benchmarks.yml | 2 - 42 files changed, 6537 deletions(-) delete mode 100644 benchmarks-ractor/optcarrot/.dockerignore delete mode 100644 benchmarks-ractor/optcarrot/.gitignore delete mode 100644 benchmarks-ractor/optcarrot/.rubocop.yml delete mode 100644 benchmarks-ractor/optcarrot/.travis.yml delete mode 100644 benchmarks-ractor/optcarrot/Gemfile delete mode 100644 benchmarks-ractor/optcarrot/LICENSE delete mode 100755 benchmarks-ractor/optcarrot/benchmark.rb delete mode 100644 benchmarks-ractor/optcarrot/examples/Lan_Master.nes delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/apu.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/config.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/cpu.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/ao_audio.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/gif_video.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/log_input.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/misc.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/mplayer_video.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/png_video.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_audio.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_input.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_video.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_audio.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_input.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_video.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/sixel_video.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/term_input.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/driver/wav_audio.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/mapper/cnrom.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/mapper/mmc1.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/mapper/mmc3.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/mapper/uxrom.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/nes.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/opt.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/pad.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/palette.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/ppu.rb delete mode 100644 benchmarks-ractor/optcarrot/lib/optcarrot/rom.rb delete mode 100644 benchmarks-ractor/optcarrot/optcarrot.gemspec diff --git a/benchmarks-ractor/optcarrot/.dockerignore b/benchmarks-ractor/optcarrot/.dockerignore deleted file mode 100644 index f56b539c..00000000 --- a/benchmarks-ractor/optcarrot/.dockerignore +++ /dev/null @@ -1,23 +0,0 @@ -.bundle/ -Gemfile.lock -coverage/ -doc/ -pkg/ -vendor/ -optcarrot-*.gem - -.git -.dockerignore -.*.sw* -**/.*.sw* -tools/nes-test-roms -SDL2.dll -video.png -video.gif -audio.wav -stackprof-*.dump -perf.data -perf.data.old -benchmark/bm-*.csv -benchmark/Dockerfile.* -tmp diff --git a/benchmarks-ractor/optcarrot/.gitignore b/benchmarks-ractor/optcarrot/.gitignore deleted file mode 100644 index f267ae8a..00000000 --- a/benchmarks-ractor/optcarrot/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -/.bundle/ -/Gemfile.lock -/benchmark -/coverage/ -/pkg/ -/vendor/ -optcarrot-*.gem - -.*.sw* -/tools/nes-test-roms -video.png -video.gif -audio.wav -stackprof-*.dump -perf.data -perf.data.old -benchmark/bm-*.csv -ppu-core.rb -cpu-core.rb -benchmark/Dockerfile.* -benchmark/*-core-opt-*.rb diff --git a/benchmarks-ractor/optcarrot/.rubocop.yml b/benchmarks-ractor/optcarrot/.rubocop.yml deleted file mode 100644 index 5d849266..00000000 --- a/benchmarks-ractor/optcarrot/.rubocop.yml +++ /dev/null @@ -1,236 +0,0 @@ -AllCops: - NewCops: enable - Exclude: - - "*-core.rb" - - "tools/plot.rb" - - "tmp/**" - - "test/**" - - "benchmark/*-core-opt-*.rb" - - "benchmark/Dockerfile.*" - -# "eval" is the swiss army knife -Security/Eval: - Enabled: false - -# Marshal.load is needed when needed -Security/MarshalLoad: - Enabled: false - -# "while true" loop is easy and fast -Lint/Loop: - Enabled: false -Style/InfiniteLoop: - Enabled: false - -# `String#%` is a great invention of Ruby -Style/FormatString: - EnforcedStyle: percent - -# I hate frozen string literal -Style/FrozenStringLiteralComment: - Enabled: false - -# 10_000 looks dirty to me -Style/NumericLiterals: - MinDigits: 6 - -# explicit return is sometimes good for consistency -Style/RedundantReturn: - Enabled: false - -# `x == :error ? error-case : normal-case` does not look beautiful to me -Style/NegatedIfElseCondition: - Enabled: false - -# I like `foo {|x| bar(x) }` and `foo { bar }` -Layout/SpaceInsideBlockBraces: - EnforcedStyleForEmptyBraces: space - SpaceBeforeBlockParameters: false - -# `"foo#{bar}baz"` looks too dense to me -Layout/SpaceInsideStringInterpolation: - EnforcedStyle: space - -# I consistently use double quotes -Style/StringLiterals: - EnforcedStyle: double_quotes -Style/StringLiteralsInInterpolation: - EnforcedStyle: double_quotes - -# A trailing comma in array/hash literal is super cool -Style/TrailingCommaInArrayLiteral: - Enabled: false -Style/TrailingCommaInHashLiteral: - Enabled: false - -# I don't like this so much but virtually needed for ffi struct layout -Style/TrailingCommaInArguments: - Enabled: false - -# I don't want to fill my code with `.freeze` -Style/MutableConstant: - Enabled: false - -# I have no idea why this is prohibited... -Style/ParallelAssignment: - Enabled: false - -# Backrefs are indeed dirty, but `Regexp.last_match` is too verbose -Style/PerlBackrefs: - Enabled: false - -# I think `{|ary| ary.size }` is not so bad since its type is explicit -Style/SymbolProc: - Enabled: false - -# Wrapping a code is so bad? Case-by-case. -Style/GuardClause: - Enabled: false - -# I use hyphen-separated case for script program. -Naming/FileName: - Exclude: - - 'tools/*.rb' - -# I don't care class/module size -Metrics/ClassLength: - Max: 1000 -Metrics/ModuleLength: - Max: 1000 - -# I accept two-screen size (about 100 lines?) -Metrics/MethodLength: - Max: 100 -Metrics/BlockLength: - Max: 100 - -# Don't worry, my terminal is big enough -Layout/LineLength: - Max: 120 - -# Code metrics is good, but I think the default is too strict... -Metrics/CyclomaticComplexity: - Max: 40 -Metrics/PerceivedComplexity: - Max: 40 -Metrics/AbcSize: - Max: 100 -Metrics/BlockNesting: - Max: 5 - -# I like `x == 0` -Style/NumericPredicate: - EnforcedStyle: comparison - -# I like `foo1` and `foo_bar_1` -Naming/VariableNumber: - Enabled: false - -# empty is empty -Style/EmptyMethod: - Enabled: false -Lint/EmptyWhen: - Enabled: false - -# if-elsif-elsif... looks awkward to me -Style/EmptyCaseCondition: - Enabled: false - -# `rescue StandardError` looks redundant to me -Style/RescueStandardError: - Enabled: false - -# `END` is always my heredoc delimiter -Naming/HeredocDelimiterNaming: - Enabled: false - -# I like `%w()` -Style/PercentLiteralDelimiters: - PreferredDelimiters: - '%w': '()' - -# I cannot use `%i()` since I want to make this code run in 1.8 -Style/SymbolArray: - EnforcedStyle: brackets - -# `0 <= n && n <= 0x7f` is completely innocent -Style/YodaCondition: - Enabled: false - -# I understand but `while true` is faster than `loop do` -Lint/LiteralAsCondition: - Enabled: false - -# What I want to do is to puts a message to stderr, not to warn users -Style/StderrPuts: - Exclude: - - 'tools/shim.rb' - -# Leave me alone -Style/CommentedKeyword: - Enabled: false - -# I want to casually use %s for simple format -Style/FormatStringToken: - Enabled: false - -# Indeed, if having a single-line body is not so smart, but just not smart -Style/IfUnlessModifier: - Enabled: false - -# Let me choose "" + "" -Style/StringConcatenation: - Enabled: false - -# Keyword arguments cannot be used in Ruby 1.8 -Style/OptionalBooleanParameter: - Enabled: false - -# I don't use `Kernel#Array` -Style/ArrayCoercion: - Enabled: false - -# `(1 + 1)**-1` looks awkward -Layout/SpaceAroundOperators: - Enabled: false - -# One-letter variable is cute -Naming/MethodParameterName: - Enabled: false - -# It highly depends on the context -Layout/EmptyLineAfterGuardClause: - Enabled: false - -# Hash table literal is a kind of an art, difficult for machine -Layout/HashAlignment: - Enabled: false - -# The program needs to work on old rubies -Style/SafeNavigation: - Enabled: false - -# I want to use %w() only when the length is long -Style/WordArray: - Enabled: false - -# I want to align lines -Layout/SpaceAroundMethodCallOperator: - Enabled: false - -# shim is shim -Layout/EmptyLinesAroundAttributeAccessor: - Exclude: - - 'tools/shim.rb' - -# This is sometimes a good habit -Style/RedundantAssignment: - Enabled: false - -# We support Ruby 1.8 -Gemspec/RequiredRubyVersion: - Enabled: false - -# Ruby 1.8 does not allow rescue clause in a block -Style/RedundantBegin: - Enabled: false diff --git a/benchmarks-ractor/optcarrot/.travis.yml b/benchmarks-ractor/optcarrot/.travis.yml deleted file mode 100644 index 187b5784..00000000 --- a/benchmarks-ractor/optcarrot/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: ruby -rvm: - - 2.7.1 diff --git a/benchmarks-ractor/optcarrot/Gemfile b/benchmarks-ractor/optcarrot/Gemfile deleted file mode 100644 index d61f237c..00000000 --- a/benchmarks-ractor/optcarrot/Gemfile +++ /dev/null @@ -1,6 +0,0 @@ -source "https://rubygems.org" - -gem "benchmark_driver", ">= 0.11.0", group: :development -gem "ffi" -gem "rake", group: [:development, :test] -gem "rubocop", group: :development diff --git a/benchmarks-ractor/optcarrot/LICENSE b/benchmarks-ractor/optcarrot/LICENSE deleted file mode 100644 index 9a415ca7..00000000 --- a/benchmarks-ractor/optcarrot/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Yusuke Endoh - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/benchmarks-ractor/optcarrot/benchmark.rb b/benchmarks-ractor/optcarrot/benchmark.rb deleted file mode 100755 index b0771ac6..00000000 --- a/benchmarks-ractor/optcarrot/benchmark.rb +++ /dev/null @@ -1,12 +0,0 @@ -require_relative '../../harness/loader' -require_relative "lib/optcarrot" - -ROM_PATH = File.join(__dir__, "examples/Lan_Master.nes").freeze -ENV["WARMUP_ITRS"] = "1" - -run_benchmark(10) do - nes = Optcarrot::NES.new(["-b", "--no-print-video-checksum", ROM_PATH]) - nes.cpu.load! - nes.reset - 200.times { nes.step } -end diff --git a/benchmarks-ractor/optcarrot/examples/Lan_Master.nes b/benchmarks-ractor/optcarrot/examples/Lan_Master.nes deleted file mode 100644 index 15dd5ef1b432e0f480a454144517239504d49034..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40976 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 diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot.rb b/benchmarks-ractor/optcarrot/lib/optcarrot.rb deleted file mode 100644 index f5daa76f..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot.rb +++ /dev/null @@ -1,14 +0,0 @@ -# Optcarrot namespace -module Optcarrot - VERSION = "0.9.0" -end - -require_relative "optcarrot/nes" -require_relative "optcarrot/rom" -require_relative "optcarrot/pad" -require_relative "optcarrot/cpu" -require_relative "optcarrot/apu" -require_relative "optcarrot/ppu" -require_relative "optcarrot/palette" -require_relative "optcarrot/driver" -require_relative "optcarrot/config" diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/apu.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/apu.rb deleted file mode 100644 index 3441ab48..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/apu.rb +++ /dev/null @@ -1,858 +0,0 @@ -module Optcarrot - # APU implementation (audio output) - class APU - CLK_M2_MUL = 6 - CLK_NTSC = 39_375_000 * CLK_M2_MUL - CLK_NTSC_DIV = 11 - - CHANNEL_OUTPUT_MUL = 256 - CHANNEL_OUTPUT_DECAY = CHANNEL_OUTPUT_MUL / 4 - 1 - - FRAME_CLOCKS = Ractor.make_shareable([29830, 1, 1, 29828].map {|n| RP2A03_CC * n }) - OSCILLATOR_CLOCKS = Ractor.make_shareable([ - [7458, 7456, 7458, 7458], - [7458, 7456, 7458, 7458 + 7452] - ].map {|a| a.map {|n| RP2A03_CC * n } }) - - def inspect - "#<#{ self.class }>" - end - - ########################################################################### - # initialization - - def initialize(conf, cpu, rate, bits) - @conf = conf - @cpu = cpu - - @pulse_0, @pulse_1 = Pulse.new(self), Pulse.new(self) - @triangle = Triangle.new(self) - @noise = Noise.new(self) - @dmc = DMC.new(@cpu, self) - @mixer = Mixer.new(@pulse_0, @pulse_1, @triangle, @noise, @dmc) - - @conf.fatal("audio sample rate must be >= 11050") if rate < 11050 - @conf.fatal("audio bit depth must be 8 or 16") if bits != 8 && bits != 16 - - @settings_rate = rate - - @output = [] - @buffer = [] - - @fixed_clock = 1 - @rate_clock = 1 - @rate_counter = 0 - @frame_counter = 0 - @frame_divider = 0 - @frame_irq_clock = 0 - @frame_irq_repeat = 0 - @dmc_clock = 0 - - reset(false) - end - - def reset_mapping - @frame_counter /= @fixed_clock - @rate_counter /= @fixed_clock - multiplier = 0 - while true - multiplier += 1 - break if multiplier >= 512 - break if CLK_NTSC * multiplier % @settings_rate == 0 - end - @rate_clock = CLK_NTSC * multiplier / @settings_rate - @fixed_clock = CLK_NTSC_DIV * multiplier - @frame_counter *= @fixed_clock - @rate_counter *= @fixed_clock - - @mixer.reset - @buffer.clear - - multiplier = 0 - while true - multiplier += 1 - break if multiplier >= 0x1000 - break if CLK_NTSC * (multiplier + 1) / @settings_rate > 0x7ffff - break if CLK_NTSC * multiplier % @settings_rate == 0 - end - rate = CLK_NTSC * multiplier / @settings_rate - fixed = CLK_NTSC_DIV * CPU::CLK_1 * multiplier - - @pulse_0 .update_settings(rate, fixed) - @pulse_1 .update_settings(rate, fixed) - @triangle.update_settings(rate, fixed) - @noise .update_settings(rate, fixed) - - @cpu.add_mappings(0x4000, method(:peek_40xx), @pulse_0 .method(:poke_0)) - @cpu.add_mappings(0x4001, method(:peek_40xx), @pulse_0 .method(:poke_1)) - @cpu.add_mappings(0x4002, method(:peek_40xx), @pulse_0 .method(:poke_2)) - @cpu.add_mappings(0x4003, method(:peek_40xx), @pulse_0 .method(:poke_3)) - @cpu.add_mappings(0x4004, method(:peek_40xx), @pulse_1 .method(:poke_0)) - @cpu.add_mappings(0x4005, method(:peek_40xx), @pulse_1 .method(:poke_1)) - @cpu.add_mappings(0x4006, method(:peek_40xx), @pulse_1 .method(:poke_2)) - @cpu.add_mappings(0x4007, method(:peek_40xx), @pulse_1 .method(:poke_3)) - @cpu.add_mappings(0x4008, method(:peek_40xx), @triangle.method(:poke_0)) - @cpu.add_mappings(0x400a, method(:peek_40xx), @triangle.method(:poke_2)) - @cpu.add_mappings(0x400b, method(:peek_40xx), @triangle.method(:poke_3)) - @cpu.add_mappings(0x400c, method(:peek_40xx), @noise .method(:poke_0)) - @cpu.add_mappings(0x400e, method(:peek_40xx), @noise .method(:poke_2)) - @cpu.add_mappings(0x400f, method(:peek_40xx), @noise .method(:poke_3)) - @cpu.add_mappings(0x4010, method(:peek_40xx), @dmc .method(:poke_0)) - @cpu.add_mappings(0x4011, method(:peek_40xx), @dmc .method(:poke_1)) - @cpu.add_mappings(0x4012, method(:peek_40xx), @dmc .method(:poke_2)) - @cpu.add_mappings(0x4013, method(:peek_40xx), @dmc .method(:poke_3)) - @cpu.add_mappings(0x4015, method(:peek_4015), method(:poke_4015)) - @frame_irq_clock = (@frame_counter / @fixed_clock) - CPU::CLK_1 - end - - def reset(mapping = true) - @cycles_ratecounter = 0 - @frame_divider = 0 - @frame_irq_clock = FOREVER_CLOCK - @frame_irq_repeat = 0 - @dmc_clock = DMC::LUT[0] - @frame_counter = FRAME_CLOCKS[0] * @fixed_clock - - reset_mapping if mapping - - @pulse_0.reset - @pulse_1.reset - @triangle.reset - @noise.reset - @dmc.reset - @mixer.reset - @buffer.clear - @oscillator_clocks = OSCILLATOR_CLOCKS[0] - end - - ########################################################################### - # other APIs - - attr_reader :output - - def do_clock - clock_dma(@cpu.current_clock) - clock_frame_irq(@cpu.current_clock) if @frame_irq_clock <= @cpu.current_clock - @dmc_clock < @frame_irq_clock ? @dmc_clock : @frame_irq_clock - end - - def clock_dma(clk) - clock_dmc(clk) if @dmc_clock <= clk - end - - def update(target = @cpu.update) - target *= @fixed_clock - proceed(target) - clock_frame_counter if @frame_counter < target - end - - def update_latency - update(@cpu.update + 1) - end - - def update_delta - elapsed = @cpu.update - delta = @frame_counter != elapsed * @fixed_clock - update(elapsed + 1) - delta - end - - def vsync - flush_sound - update(@cpu.current_clock) - frame = @cpu.next_frame_clock - @dmc_clock -= frame - @frame_irq_clock -= frame if @frame_irq_clock != FOREVER_CLOCK - frame *= @fixed_clock - @rate_counter -= frame - @frame_counter -= frame - end - - ########################################################################### - # helpers - - def clock_oscillators(two_clocks) - @pulse_0.clock_envelope - @pulse_1.clock_envelope - @triangle.clock_linear_counter - @noise.clock_envelope - return unless two_clocks - @pulse_0.clock_sweep(-1) - @pulse_1.clock_sweep(0) - @triangle.clock_length_counter - @noise.clock_length_counter - end - - def clock_dmc(target) - begin - if @dmc.clock_dac - update(@dmc_clock) - @dmc.update - end - @dmc_clock += @dmc.freq - @dmc.clock_dma - end while @dmc_clock <= target - end - - def clock_frame_counter - clock_oscillators(@frame_divider[0] == 1) - @frame_divider = (@frame_divider + 1) & 3 - @frame_counter += @oscillator_clocks[@frame_divider] * @fixed_clock - end - - def clock_frame_irq(target) - @cpu.do_irq(CPU::IRQ_FRAME, @frame_irq_clock) - begin - @frame_irq_clock += FRAME_CLOCKS[1 + @frame_irq_repeat % 3] - @frame_irq_repeat += 1 - end while @frame_irq_clock <= target - end - - def flush_sound - if @buffer.size < @settings_rate / 60 - target = @cpu.current_clock * @fixed_clock - proceed(target) - if @buffer.size < @settings_rate / 60 - clock_frame_counter if @frame_counter < target - @buffer << @mixer.sample while @buffer.size < @settings_rate / 60 - end - end - @output.clear - @output.concat(@buffer) # Array#replace creates an object internally - @buffer.clear - end - - def proceed(target) - while @rate_counter < target && @buffer.size < @settings_rate / 60 - @buffer << @mixer.sample - clock_frame_counter if @frame_counter <= @rate_counter - @rate_counter += @rate_clock - end - end - - ########################################################################### - # mapped memory handlers - - # Control - def poke_4015(_addr, data) - update - @pulse_0 .enable(data[0] == 1) - @pulse_1 .enable(data[1] == 1) - @triangle.enable(data[2] == 1) - @noise .enable(data[3] == 1) - @dmc .enable(data[4] == 1) - end - - # Status - def peek_4015(_addr) - elapsed = @cpu.update - clock_frame_irq(elapsed) if @frame_irq_clock <= elapsed - update(elapsed) if @frame_counter < elapsed * @fixed_clock - @cpu.clear_irq(CPU::IRQ_FRAME) | - (@pulse_0 .status ? 0x01 : 0) | - (@pulse_1 .status ? 0x02 : 0) | - (@triangle.status ? 0x04 : 0) | - (@noise .status ? 0x08 : 0) | - (@dmc .status ? 0x10 : 0) - end - - # Frame counter (NOTE: this handler is called via Pads) - def poke_4017(_addr, data) - n = @cpu.update - n += CPU::CLK_1 if @cpu.odd_clock? - update(n) - clock_frame_irq(n) if @frame_irq_clock <= n - n += CPU::CLK_1 - @oscillator_clocks = OSCILLATOR_CLOCKS[data[7]] - @frame_counter = (n + @oscillator_clocks[0]) * @fixed_clock - @frame_divider = 0 - @frame_irq_clock = data & 0xc0 != 0 ? FOREVER_CLOCK : n + FRAME_CLOCKS[0] - @frame_irq_repeat = 0 - @cpu.clear_irq(CPU::IRQ_FRAME) if data[6] != 0 - clock_oscillators(true) if data[7] != 0 - end - - def peek_40xx(_addr) - 0x40 - end - - ########################################################################### - # helper classes - - # A counter for note length - class LengthCounter - LUT = Ractor.make_shareable([ - 0x0a, 0xfe, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xa0, 0x08, 0x3c, 0x0a, 0x0e, 0x0c, 0x1a, 0x0e, - 0x0c, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xc0, 0x18, 0x48, 0x1a, 0x10, 0x1c, 0x20, 0x1e, - ]) - def reset - @enabled = false - @count = 0 - end - - attr_reader :count - - def enable(enabled) - @enabled = enabled - @count = 0 unless @enabled - @enabled - end - - def write(data, frame_counter_delta) - @count = @enabled ? LUT[data] : 0 if frame_counter_delta || @count == 0 - end - - def clock - return false if @count == 0 - @count -= 1 - return @count == 0 - end - end - - # Wave envelope - class Envelope - attr_reader :output, :looping - - def reset_clock - @reset = true - end - - def reset - @output = 0 - @count = 0 - @volume_base = @volume = 0 - @constant = true - @looping = false - @reset = false - update_output - end - - def clock - if @reset - @reset = false - @volume = 0x0f - else - if @count != 0 - @count -= 1 - return - end - @volume = (@volume - 1) & 0x0f if @volume != 0 || @looping - end - @count = @volume_base - update_output - end - - def write(data) - @volume_base = data & 0x0f - @constant = data[4] == 1 - @looping = data[5] == 1 - update_output - end - - def update_output - @output = (@constant ? @volume_base : @volume) * CHANNEL_OUTPUT_MUL - end - end - - # Mixer (with DC Blocking filter) - class Mixer - VOL = 192 - P_F = 900 - P_0 = 9552 * CHANNEL_OUTPUT_MUL * VOL * (P_F / 100) - P_1 = 8128 * CHANNEL_OUTPUT_MUL * P_F - P_2 = P_F * 100 - TND_F = 500 - TND_0 = 16367 * CHANNEL_OUTPUT_MUL * VOL * (TND_F / 100) - TND_1 = 24329 * CHANNEL_OUTPUT_MUL * TND_F - TND_2 = TND_F * 100 - - def initialize(pulse_0, pulse_1, triangle, noise, dmc) - @pulse_0, @pulse_1, @triangle, @noise, @dmc = pulse_0, pulse_1, triangle, noise, dmc - end - - def reset - @acc = @prev = @next = 0 - end - - def sample - dac0 = @pulse_0.sample + @pulse_1.sample - dac1 = @triangle.sample + @noise.sample + @dmc.sample - sample = (P_0 * dac0 / (P_1 + P_2 * dac0)) + (TND_0 * dac1 / (TND_1 + TND_2 * dac1)) - - @acc -= @prev - @prev = sample << 15 - @acc += @prev - @next * 3 # POLE - sample = @next = @acc >> 15 - - sample = -0x7fff if sample < -0x7fff - sample = 0x7fff if sample > 0x7fff - sample - end - end - - # base class for oscillator channels (Pulse, Triangle, and Noise) - class Oscillator - def inspect - "#<#{ self.class }>" - end - - def initialize(apu) - @apu = apu - @rate = @fixed = 1 - @envelope = @length_counter = @wave_length = nil - end - - def reset - @timer = 2048 * @fixed # 2048: reset cycles - @freq = @fixed - @amp = 0 - - @wave_length = 0 if @wave_length - @envelope.reset if @envelope - @length_counter.reset if @length_counter - @active = active? - end - - def active? - return false if @length_counter && @length_counter.count == 0 - return false if @envelope && @envelope.output == 0 - return true - end - - def poke_0(_addr, data) - if @envelope - @apu.update_latency - @envelope.write(data) - @active = active? - end - end - - def poke_2(_addr, data) - @apu.update - if @wave_length - @wave_length = (@wave_length & 0x0700) | (data & 0x00ff) - update_freq - end - end - - def poke_3(_addr, data) - delta = @apu.update_delta - if @wave_length - @wave_length = (@wave_length & 0x00ff) | ((data & 0x07) << 8) - update_freq - end - @envelope.reset_clock if @envelope - @length_counter.write(data >> 3, delta) if @length_counter - @active = active? - end - - def enable(enabled) - @length_counter.enable(enabled) - @active = active? - end - - def update_settings(r, f) - @freq = @freq / @fixed * f - @timer = @timer / @fixed * f - @rate, @fixed = r, f - end - - def status - @length_counter.count > 0 - end - - def clock_envelope - @envelope.clock - @active = active? - end - end - - #-------------------------------------------------------------------------- - - ### Pulse channel ### - class Pulse < Oscillator - MIN_FREQ = 0x0008 - MAX_FREQ = 0x07ff - WAVE_FORM = Ractor.make_shareable([0b11111101, 0b11111001, 0b11100001, 0b00000110].map {|n| (0..7).map {|i| n[i] * 0x1f } }) - - def initialize(_apu) - super - @wave_length = 0 - @envelope = Envelope.new - @length_counter = LengthCounter.new - end - - def reset - super - @freq = @fixed * 2 - @valid_freq = false - @step = 0 - @form = WAVE_FORM[0] - @sweep_rate = 0 - @sweep_count = 1 - @sweep_reload = false - @sweep_increase = -1 - @sweep_shift = 0 - end - - def active? - super && @valid_freq - end - - def update_freq - if @wave_length >= MIN_FREQ && @wave_length + (@sweep_increase & @wave_length >> @sweep_shift) <= MAX_FREQ - @freq = (@wave_length + 1) * 2 * @fixed - @valid_freq = true - else - @valid_freq = false - end - @active = active? - end - - def poke_0(_addr, data) - super - @form = WAVE_FORM[data >> 6 & 3] - end - - def poke_1(_addr, data) - @apu.update - @sweep_increase = data[3] != 0 ? 0 : -1 - @sweep_shift = data & 0x07 - @sweep_rate = 0 - if data[7] == 1 && @sweep_shift > 0 - @sweep_rate = ((data >> 4) & 0x07) + 1 - @sweep_reload = true - end - update_freq - end - - def poke_3(_addr, _data) - super - @step = 0 - end - - def clock_sweep(complement) - @active = false if !@envelope.looping && @length_counter.clock - if @sweep_rate != 0 - @sweep_count -= 1 - if @sweep_count == 0 - @sweep_count = @sweep_rate - if @wave_length >= MIN_FREQ - shifted = @wave_length >> @sweep_shift - if @sweep_increase == 0 - @wave_length += complement - shifted - update_freq - elsif @wave_length + shifted <= MAX_FREQ - @wave_length += shifted - update_freq - end - end - end - end - - return unless @sweep_reload - - @sweep_reload = false - @sweep_count = @sweep_rate - end - - def sample - sum = @timer - @timer -= @rate - if @active - if @timer < 0 - sum >>= @form[@step] - begin - v = -@timer - v = @freq if v > @freq - sum += v >> @form[@step = (@step + 1) & 7] - @timer += @freq - end while @timer < 0 - @amp = (sum * @envelope.output + @rate / 2) / @rate - else - @amp = @envelope.output >> @form[@step] - end - else - if @timer < 0 - count = (-@timer + @freq - 1) / @freq - @step = (@step + count) & 7 - @timer += count * @freq - end - return 0 if @amp < CHANNEL_OUTPUT_DECAY - @amp -= CHANNEL_OUTPUT_DECAY - end - @amp - end - end - - #-------------------------------------------------------------------------- - - ### Triangle channel ### - class Triangle < Oscillator - MIN_FREQ = 2 + 1 - WAVE_FORM = Ractor.make_shareable((0..15).to_a + (0..15).to_a.reverse) - - def initialize(_apu) - super - @wave_length = 0 - @length_counter = LengthCounter.new - end - - def reset - super - @step = 7 - @status = :counting - @linear_counter_load = 0 - @linear_counter_start = true - @linear_counter = 0 - end - - def active? - super && @linear_counter != 0 && @wave_length >= MIN_FREQ - end - - def update_freq - @freq = (@wave_length + 1) * @fixed - @active = active? - end - - def poke_0(_addr, data) - super - @apu.update - @linear_counter_load = data & 0x7f - @linear_counter_start = data[7] == 0 - end - - def poke_3(_addr, _data) - super - @status = :reload - end - - def clock_linear_counter - if @status == :counting - @linear_counter -= 1 if @linear_counter != 0 - else - @status = :counting if @linear_counter_start - @linear_counter = @linear_counter_load - end - @active = active? - end - - def clock_length_counter - @active = false if @linear_counter_start && @length_counter.clock - end - - def sample - if @active - sum = @timer - @timer -= @rate - if @timer < 0 - sum *= WAVE_FORM[@step] - begin - v = -@timer - v = @freq if v > @freq - sum += v * WAVE_FORM[@step = (@step + 1) & 0x1f] - @timer += @freq - end while @timer < 0 - @amp = (sum * CHANNEL_OUTPUT_MUL + @rate / 2) / @rate * 3 - else - @amp = WAVE_FORM[@step] * CHANNEL_OUTPUT_MUL * 3 - end - else - return 0 if @amp < CHANNEL_OUTPUT_DECAY - @amp -= CHANNEL_OUTPUT_DECAY - @step = 0 - end - @amp - end - end - - #-------------------------------------------------------------------------- - - ### Noise channel ### - class Noise < Oscillator - LUT = Ractor.make_shareable([4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068]) - NEXT_BITS_1, NEXT_BITS_6 = [1, 6].map do |shifter| - (0..0x7fff).map {|bits| bits[0] == bits[shifter] ? bits / 2 : bits / 2 + 0x4000 } - end - Ractor.make_shareable(NEXT_BITS_1) - Ractor.make_shareable(NEXT_BITS_6) - - def initialize(_apu) - super - @envelope = Envelope.new - @length_counter = LengthCounter.new - end - - def reset - super - @freq = LUT[0] * @fixed - @bits = 0x4000 - @shifter = NEXT_BITS_1 - end - - def poke_2(_addr, data) - @apu.update - @freq = LUT[data & 0x0f] * @fixed - @shifter = data[7] != 0 ? NEXT_BITS_6 : NEXT_BITS_1 - end - - def clock_length_counter - @active = false if !@envelope.looping && @length_counter.clock - end - - def sample - @timer -= @rate - if @active - return @bits.even? ? @envelope.output * 2 : 0 if @timer >= 0 - - sum = @bits.even? ? (@timer + @rate) : 0 - begin - @bits = @shifter[@bits] - if @bits.even? - v = -@timer - v = @freq if v > @freq - sum += v - end - @timer += @freq - end while @timer < 0 - return (sum * @envelope.output + @rate / 2) / @rate * 2 - else - while @timer < 0 - @bits = @shifter[@bits] - @timer += @freq - end - return 0 - end - end - end - - #-------------------------------------------------------------------------- - - ### DMC channel ### - class DMC - LUT = Ractor.make_shareable([428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54].map {|n| n * RP2A03_CC }) - - def initialize(cpu, apu) - @apu = apu - @cpu = cpu - @freq = LUT[0] - end - - def reset - @cur_sample = 0 - @lin_sample = 0 - @freq = LUT[0] - @loop = false - @irq_enable = false - @regs_length_counter = 1 - @regs_address = 0xc000 - @out_active = false - @out_shifter = 0 - @out_dac = 0 - @out_buffer = 0x00 - @dma_length_counter = 0 - @dma_buffered = false - @dma_address = 0xc000 - @dma_buffer = 0x00 - end - - attr_reader :freq - - def enable(enabled) - @cpu.clear_irq(CPU::IRQ_DMC) - if !enabled - @dma_length_counter = 0 - elsif @dma_length_counter == 0 - @dma_length_counter = @regs_length_counter - @dma_address = @regs_address - do_dma unless @dma_buffered - end - end - - def sample - if @cur_sample != @lin_sample - step = CHANNEL_OUTPUT_MUL * 8 - if @lin_sample + step < @cur_sample - @lin_sample += step - elsif @cur_sample < @lin_sample - step - @lin_sample -= step - else - @lin_sample = @cur_sample - end - end - @lin_sample - end - - def do_dma - @dma_buffer = @cpu.dmc_dma(@dma_address) - @dma_address = 0x8000 | ((@dma_address + 1) & 0x7fff) - @dma_buffered = true - @dma_length_counter -= 1 - if @dma_length_counter == 0 - if @loop - @dma_address = @regs_address - @dma_length_counter = @regs_length_counter - elsif @irq_enable - @cpu.do_irq(CPU::IRQ_DMC, @cpu.current_clock) - end - end - end - - def update - @cur_sample = @out_dac * CHANNEL_OUTPUT_MUL - end - - def poke_0(_addr, data) - @loop = data[6] != 0 - @irq_enable = data[7] != 0 - @freq = LUT[data & 0x0f] - @cpu.clear_irq(CPU::IRQ_DMC) unless @irq_enable - end - - def poke_1(_addr, data) - @apu.update - @out_dac = data & 0x7f - update - end - - def poke_2(_addr, data) - @regs_address = 0xc000 | (data << 6) - end - - def poke_3(_addr, data) - @regs_length_counter = (data << 4) + 1 - end - - def clock_dac - if @out_active - n = @out_dac + ((@out_buffer & 1) << 2) - 2 - @out_buffer >>= 1 - if 0 <= n && n <= 0x7f && n != @out_dac - @out_dac = n - return true - end - end - return false - end - - def clock_dma - if @out_shifter == 0 - @out_shifter = 7 - @out_active = @dma_buffered - if @out_active - @dma_buffered = false - @out_buffer = @dma_buffer - do_dma if @dma_length_counter != 0 - end - else - @out_shifter -= 1 - end - end - - def status - @dma_length_counter > 0 - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/config.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/config.rb deleted file mode 100644 index be11ec92..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/config.rb +++ /dev/null @@ -1,259 +0,0 @@ -module Optcarrot - # config manager and logger - class Config - OPTIONS = { - optimization: { - opt_ppu: { - type: :opts, - desc: "select PPU optimizations", - candidates: PPU::OptimizedCodeBuilder::OPTIONS, - default: nil, - }, - opt_cpu: { - type: :opts, - desc: "select CPU optimizations", - candidates: CPU::OptimizedCodeBuilder::OPTIONS, - default: nil, - }, - opt: { shortcut: %w(--opt-ppu=all --opt-cpu=all) }, - list_opts: { type: :info, desc: "list available optimizations" }, - dump_ppu: { type: :info, desc: "print generated PPU source code" }, - dump_cpu: { type: :info, desc: "print generated CPU source code" }, - load_ppu: { type: "FILE", desc: "use generated PPU source code" }, - load_cpu: { type: "FILE", desc: "use generated CPU source code" }, - }, - emulation: { - sprite_limit: { type: :switch, desc: "enable/disable sprite limit", default: false }, - frames: { type: :int, desc: "execute N frames (0 = no limit)", default: 0, aliases: [:f, :frame] }, - audio_sample_rate: { type: :int, desc: "set audio sample rate", default: 44100 }, - audio_bit_depth: { type: :int, desc: "set audio bit depth", default: 16 }, - nestopia_palette: { type: :switch, desc: "use Nestopia palette instead of de facto", default: false }, - }, - driver: { - video: { type: :driver, desc: "select video driver", candidates: Driver::DRIVER_DB[:video].keys }, - audio: { type: :driver, desc: "select audio driver", candidates: Driver::DRIVER_DB[:audio].keys }, - input: { type: :driver, desc: "select input driver", candidates: Driver::DRIVER_DB[:input].keys }, - list_drivers: { type: :info, desc: "print available drivers" }, - sdl2: { shortcut: %w(--video=sdl2 --audio=sdl2 --input=sdl2) }, - sfml: { shortcut: %w(--video=sfml --audio=sfml --input=sfml) }, - headless: { shortcut: %w(--video=none --audio=none --input=none) }, - video_output: { type: "FILE", desc: "save video to file", default: "video.EXT" }, - audio_output: { type: "FILE", desc: "save audio to file", default: "audio.wav" }, - show_fps: { type: :switch, desc: "show fps in the right-bottom corner", default: true }, - key_log: { type: "FILE", desc: "use recorded input file" }, - # key_config: { type: "KEY", desc: "key configuration" }, - }, - profiling: { - print_fps: { type: :switch, desc: "print fps of last 10 frames", default: false }, - print_p95fps: { type: :switch, desc: "print 95th percentile fps", default: false }, - print_fps_history: { type: :switch, desc: "print all fps values for each frame", default: false }, - print_video_checksum: { type: :switch, desc: "print checksum of the last video output", default: false }, - stackprof: { shortcut: "--stackprof-mode=cpu", aliases: :p }, - stackprof_mode: { type: "MODE", desc: "run under stackprof", default: nil }, - stackprof_output: { type: "FILE", desc: "stackprof output file", default: "stackprof-MODE.dump" } - }, - misc: { - benchmark: { shortcut: %w(--headless --print-fps --print-video-checksum --frames 180), aliases: :b }, - loglevel: { type: :int, desc: "set loglevel", default: 1 }, - quiet: { shortcut: "--loglevel=0", aliases: :q }, - verbose: { shortcut: "--loglevel=2", aliases: :v }, - debug: { shortcut: "--loglevel=3", aliases: :d }, - version: { type: :info, desc: "print version" }, - help: { type: :info, desc: "print this message", aliases: :h }, - }, - } - - DEFAULT_OPTIONS = {} - OPTIONS.each_value do |opts| - opts.each do |id, opt| - next if opt[:shortcut] - DEFAULT_OPTIONS[id] = opt[:default] if opt.key?(:default) - attr_reader id - end - end - Ractor.make_shareable(DEFAULT_OPTIONS) - Ractor.make_shareable(OPTIONS) - attr_reader :romfile - - def initialize(opt) - opt = Parser.new(opt).options if opt.is_a?(Array) - DEFAULT_OPTIONS.merge(opt).each {|id, val| instance_variable_set(:"@#{ id }", val) } - end - - def debug(msg) - puts "[DEBUG] " + msg if @loglevel >= 3 - end - - def info(msg) - puts "[INFO] " + msg if @loglevel >= 2 - end - - def warn(msg) - puts "[WARN] " + msg if @loglevel >= 1 - end - - def error(msg) - puts "[ERROR] " + msg - end - - def fatal(msg) - puts "[FATAL] " + msg - abort - end - - # command-line option parser - class Parser - def initialize(argv) - @argv = argv.dup - @options = DEFAULT_OPTIONS.dup - parse_option until @argv.empty? - error "ROM file is not given" unless @options[:romfile] - rescue Invalid => e - puts "[FATAL] #{ e }" - exit 1 - end - - attr_reader :options - - class Invalid < RuntimeError; end - - def error(msg) - raise Invalid, msg - end - - def find_option(arg) - OPTIONS.each_value do |opts| - opts.each do |id_base, opt| - [id_base, *opt[:aliases]].each do |id| - id = id.to_s.tr("_", "-") - return opt, id_base if id.size == 1 && arg == "-#{ id }" - return opt, id_base if arg == "--#{ id }" - return opt, id_base, true if opt[:type] == :switch && arg == "--no-#{ id }" - end - end - end - return nil - end - - def parse_option - arg, operand = @argv.shift.split("=", 2) - if arg =~ /\A-(\w{2,})\z/ - args = $1.chars.map {|a| "-#{ a }" } - args.last << "=" << operand if operand - @argv.unshift(*args) - return - end - opt, id, no = find_option(arg) - if opt - if opt[:shortcut] - @argv.unshift(*opt[:shortcut]) - return - elsif opt[:type] == :info - send(id) - exit - elsif opt[:type] == :switch - error "option `#{ arg }' doesn't allow an operand" if operand - @options[id] = !no - else - @options[id] = parse_operand(operand, arg, opt) - end - else - arg = @argv.shift if arg == "--" - error "invalid option: `#{ arg }'" if arg && arg.start_with?("-") - if arg - error "extra argument: `#{ arg }'" if @options[:romfile] - @options[:romfile] = arg - end - end - end - - def parse_operand(operand, arg, opt) - type = opt[:type] - operand ||= @argv.shift - case type - when :opts - operand = operand.split(",").map {|s| s.to_sym } - when :driver - operand = operand.to_sym - error "unknown driver: `#{ operand }'" unless opt[:candidates].include?(operand) - when :int - begin - operand = Integer(operand) - rescue - error "option `#{ arg }' requires numerical operand" - end - end - operand - end - - def help - tbl = ["Usage: #{ $PROGRAM_NAME } [OPTION]... FILE"] - long_name_width = 0 - OPTIONS.each do |kind, opts| - tbl << "" << "#{ kind } options:" - opts.each do |id_base, opt| - short_name = [*opt[:aliases]][0] - switch = args = "" - case opt[:type] - when :switch then switch = "[no-]" - when :opts then args = "=OPTS,..." - when :driver then args = "=DRIVER" - when :int then args = "=N" - when String then args = "=" + opt[:type] - end - short_name = "-#{ switch }#{ short_name }, " if short_name && short_name.size == 1 - long_name = "--" + switch + id_base.to_s.tr("_", "-") + args - if opt[:shortcut] - desc = "same as `#{ [*opt[:shortcut]].join(" ") }'" - else - desc = opt[:desc] - desc += " (default: #{ opt[:default] || "none" })" if opt.key?(:default) - end - long_name_width = [long_name_width, long_name.size].max - tbl << [short_name, long_name, desc] - end - end - tbl.each do |arg| - if arg.is_a?(String) - puts arg - else - short_name, long_name, desc = arg - puts " %4s%-*s %s" % [short_name, long_name_width, long_name, desc] - end - end - end - - def version - puts "optcarrot #{ VERSION }" - end - - def list_drivers - Driver::DRIVER_DB.each do |kind, drivers| - puts "#{ kind } drivers: #{ drivers.keys * " " }" - end - end - - def list_opts - puts "CPU core optimizations:" - CPU::OptimizedCodeBuilder::OPTIONS.each do |opt| - puts " * #{ opt }" - end - puts - puts "PPU core optimizations:" - PPU::OptimizedCodeBuilder::OPTIONS.each do |opt| - puts " * #{ opt }" - end - puts - puts "(See `doc/internal.md' in detail.)" - end - - def dump_ppu - puts PPU::OptimizedCodeBuilder.new(@options[:loglevel], @options[:opt_ppu] || []).build - end - - def dump_cpu - puts CPU::OptimizedCodeBuilder.new(@options[:loglevel], @options[:opt_cpu] || []).build - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/cpu.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/cpu.rb deleted file mode 100644 index 706bcf73..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/cpu.rb +++ /dev/null @@ -1,1183 +0,0 @@ -require_relative "opt" - -module Optcarrot - # CPU implementation - class CPU - NMI_VECTOR = 0xfffa - RESET_VECTOR = 0xfffc - IRQ_VECTOR = 0xfffe - - IRQ_EXT = 0x01 - IRQ_FRAME = 0x40 - IRQ_DMC = 0x80 - - CLK_1, CLK_2, CLK_3, CLK_4, CLK_5, CLK_6, CLK_7, CLK_8 = (1..8).map {|i| i * RP2A03_CC } - - def inspect - "#<#{ self.class }>" - end - - ########################################################################### - # initialization - - def initialize(conf) - @conf = conf - - # load the generated core - if @conf.load_cpu - eval(File.read(@conf.load_cpu)) - elsif @conf.opt_cpu - eval(OptimizedCodeBuilder.new(@conf.loglevel, @conf.opt_cpu).build, nil, "(generated CPU core)") - end - - # main memory - @fetch = [nil] * 0x10000 - @store = [nil] * 0x10000 - @peeks = {} - @pokes = {} - @ram = [0] * 0x800 - - # clock management - @clk = 0 # the current clock - @clk_frame = 0 # the next frame clock - @clk_target = 0 # the goal clock for the current CPU#run - @clk_nmi = FOREVER_CLOCK # the next NMI clock (FOREVER_CLOCK means "not scheduled") - @clk_irq = FOREVER_CLOCK # the next IRQ clock - @clk_total = 0 # the total elapsed clocks - - # interrupt - @irq_flags = 0 - @jammed = false - - @poke_nop = CPU.method(:poke_nop) - - reset - - # temporary store (valid only during each operation) - @addr = @data = 0 - - @opcode = nil - @ppu_sync = false - end - - def reset - # registers - @_a = @_x = @_y = 0 - @_sp = 0xfd - @_pc = 0xfffc - - # P register - @_p_nz = 1 - @_p_c = 0 - @_p_v = 0 - @_p_i = 0x04 - @_p_d = 0 - - # reset clocks - @clk = @clk_total = 0 - - # reset RAM - @ram.fill(0xff) - - # memory mappings by self - add_mappings(0x0000..0x07ff, @ram, @ram.method(:[]=)) - add_mappings(0x0800..0x1fff, method(:peek_ram), method(:poke_ram)) - add_mappings(0x2000..0xffff, method(:peek_nop), nil) - add_mappings(0xfffc, method(:peek_jam_1), nil) - add_mappings(0xfffd, method(:peek_jam_2), nil) - end - - def peek_ram(addr) - @ram[addr % 0x0800] - end - - def poke_ram(addr, data) - @ram[addr % 0x0800] = data - end - - def peek_nop(addr) - addr >> 8 - end - - def peek_jam_1(_addr) - @_pc = (@_pc - 1) & 0xffff - 0xfc - end - - def peek_jam_2(_addr) - 0xff - end - - ########################################################################### - # mapped memory API - - def add_mappings(addr, peek, poke) - # filter the logically equivalent objects - peek = @peeks[peek] ||= peek - poke = @pokes[poke] ||= poke - - (addr.is_a?(Integer) ? [addr] : addr).each do |a| - @fetch[a] = peek - @store[a] = poke || @poke_nop - end - end - - def self.poke_nop(_addr, _data) - end - - def fetch(addr) - @fetch[addr][addr] - end - - def store(addr, value) - @store[addr][addr, value] - end - - def peek16(addr) - @fetch[addr][addr] + (@fetch[addr + 1][addr + 1] << 8) - end - - ########################################################################### - # other APIs - - attr_reader :ram - attr_writer :apu, :ppu, :ppu_sync - - def current_clock - @clk - end - - def next_frame_clock - @clk_frame - end - - def next_frame_clock=(clk) - @clk_frame = clk - @clk_target = clk if clk < @clk_target - end - - def steal_clocks(clk) - @clk += clk - end - - def odd_clock? - (@clk_total + @clk) % CLK_2 != 0 - end - - def update - @apu.clock_dma(@clk) - @clk - end - - def dmc_dma(addr) - # This is inaccurate; it must steal *up to* 4 clocks depending upon - # whether CPU writes in this clock, but this always steals 4 clocks. - @clk += CLK_3 - dma_buffer = fetch(addr) - @clk += CLK_1 - dma_buffer - end - - def sprite_dma(addr, sp_ram) - 256.times {|i| sp_ram[i] = @ram[addr + i] } - 64.times {|i| sp_ram[i * 4 + 2] &= 0xe3 } - end - - def boot - @clk = CLK_7 - @_pc = peek16(RESET_VECTOR) - end - - def vsync - @ppu.sync(@clk) if @ppu_sync - - @clk -= @clk_frame - @clk_total += @clk_frame - - @clk_nmi -= @clk_frame if @clk_nmi != FOREVER_CLOCK - @clk_irq -= @clk_frame if @clk_irq != FOREVER_CLOCK - @clk_irq = 0 if @clk_irq < 0 - end - - ########################################################################### - # interrupts - - def clear_irq(line) - old_irq_flags = @irq_flags & (IRQ_FRAME | IRQ_DMC) - @irq_flags &= line ^ (IRQ_EXT | IRQ_FRAME | IRQ_DMC) - @clk_irq = FOREVER_CLOCK if @irq_flags == 0 - old_irq_flags - end - - def next_interrupt_clock(clk) - clk += CLK_1 + CLK_1 / 2 # interrupt edge - @clk_target = clk if @clk_target > clk - clk - end - - def do_irq(line, clk) - @irq_flags |= line - @clk_irq = next_interrupt_clock(clk) if @clk_irq == FOREVER_CLOCK && @_p_i == 0 - end - - def do_nmi(clk) - @clk_nmi = next_interrupt_clock(clk) if @clk_nmi == FOREVER_CLOCK - end - - def do_isr(vector) - return if @jammed - push16(@_pc) - push8(flags_pack) - @_p_i = 0x04 - @clk += CLK_7 - addr = vector == NMI_VECTOR ? NMI_VECTOR : fetch_irq_isr_vector - @_pc = peek16(addr) - end - - def fetch_irq_isr_vector - fetch(0x3000) if @clk >= @clk_frame - if @clk_nmi != FOREVER_CLOCK - if @clk_nmi + CLK_2 <= @clk - @clk_nmi = FOREVER_CLOCK - return NMI_VECTOR - end - @clk_nmi = @clk + 1 - end - return IRQ_VECTOR - end - - ########################################################################### - # instruction helpers - - ### P regeister ### - - def flags_pack - # NVssDIZC - ((@_p_nz | @_p_nz >> 1) & 0x80) | # N: Negative - (@_p_nz & 0xff != 0 ? 0 : 2) | # Z: Zero - @_p_c | # C: Carry - (@_p_v != 0 ? 0x40 : 0) | # V: Overflow - @_p_i | # I: Inerrupt - @_p_d | # D: Decimal - 0x20 - end - - def flags_unpack(f) - @_p_nz = (~f & 2) | ((f & 0x80) << 1) - @_p_c = f & 0x01 - @_p_v = f & 0x40 - @_p_i = f & 0x04 - @_p_d = f & 0x08 - end - - ### branch helper ### - def branch(cond) - if cond - tmp = @_pc + 1 - rel = fetch(@_pc) - @_pc = (tmp + (rel < 128 ? rel : rel | 0xff00)) & 0xffff - @clk += tmp[8] == @_pc[8] ? CLK_3 : CLK_4 - else - @_pc += 1 - @clk += CLK_2 - end - end - - ### storers ### - def store_mem - store(@addr, @data) - @clk += CLK_1 - end - - def store_zpg - @ram[@addr] = @data - end - - ### stack management ### - def push8(data) - @ram[0x0100 + @_sp] = data - @_sp = (@_sp - 1) & 0xff - end - - def push16(data) - push8(data >> 8) - push8(data & 0xff) - end - - def pull8 - @_sp = (@_sp + 1) & 0xff - @ram[0x0100 + @_sp] - end - - def pull16 - pull8 + 256 * pull8 - end - - ########################################################################### - # addressing modes - - # immediate addressing (read only) - def imm(_read, _write) - @data = fetch(@_pc) - @_pc += 1 - @clk += CLK_2 - end - - # zero-page addressing - def zpg(read, write) - @addr = fetch(@_pc) - @_pc += 1 - @clk += CLK_3 - if read - @data = @ram[@addr] - @clk += CLK_2 if write - end - end - - # zero-page indexed addressing - def zpg_reg(indexed, read, write) - @addr = (indexed + fetch(@_pc)) & 0xff - @_pc += 1 - @clk += CLK_4 - if read - @data = @ram[@addr] - @clk += CLK_2 if write - end - end - - def zpg_x(read, write) - zpg_reg(@_x, read, write) - end - - def zpg_y(read, write) - zpg_reg(@_y, read, write) - end - - # absolute addressing - def abs(read, write) - @addr = peek16(@_pc) - @_pc += 2 - @clk += CLK_3 - read_write(read, write) - end - - # absolute indexed addressing - def abs_reg(indexed, read, write) - addr = @_pc + 1 - i = indexed + fetch(@_pc) - @addr = ((fetch(addr) << 8) + i) & 0xffff - if write - addr = (@addr - (i & 0x100)) & 0xffff - fetch(addr) - @clk += CLK_4 - else - @clk += CLK_3 - if i & 0x100 != 0 - addr = (@addr - 0x100) & 0xffff # for inlining fetch - fetch(addr) - @clk += CLK_1 - end - end - read_write(read, write) - @_pc += 2 - end - - def abs_x(read, write) - abs_reg(@_x, read, write) - end - - def abs_y(read, write) - abs_reg(@_y, read, write) - end - - # indexed indirect addressing - def ind_x(read, write) - addr = fetch(@_pc) + @_x - @_pc += 1 - @clk += CLK_5 - @addr = @ram[addr & 0xff] | @ram[(addr + 1) & 0xff] << 8 - read_write(read, write) - end - - # indirect indexed addressing - def ind_y(read, write) - addr = fetch(@_pc) - @_pc += 1 - indexed = @ram[addr] + @_y - @clk += CLK_4 - if write - @clk += CLK_1 - @addr = (@ram[(addr + 1) & 0xff] << 8) + indexed - addr = @addr - (indexed & 0x100) # for inlining fetch - fetch(addr) - else - @addr = ((@ram[(addr + 1) & 0xff] << 8) + indexed) & 0xffff - if indexed & 0x100 != 0 - addr = (@addr - 0x100) & 0xffff # for inlining fetch - fetch(addr) - @clk += CLK_1 - end - end - read_write(read, write) - end - - def read_write(read, write) - if read - @data = fetch(@addr) - @clk += CLK_1 - if write - store(@addr, @data) - @clk += CLK_1 - end - end - end - - ########################################################################### - # instructions - - # load instructions - def _lda - @_p_nz = @_a = @data - end - - def _ldx - @_p_nz = @_x = @data - end - - def _ldy - @_p_nz = @_y = @data - end - - # store instructions - def _sta - @data = @_a - end - - def _stx - @data = @_x - end - - def _sty - @data = @_y - end - - # transfer instructions - def _tax - @clk += CLK_2 - @_p_nz = @_x = @_a - end - - def _tay - @clk += CLK_2 - @_p_nz = @_y = @_a - end - - def _txa - @clk += CLK_2 - @_p_nz = @_a = @_x - end - - def _tya - @clk += CLK_2 - @_p_nz = @_a = @_y - end - - # flow control instructions - def _jmp_a - @_pc = peek16(@_pc) - @clk += CLK_3 - end - - def _jmp_i - pos = peek16(@_pc) - low = fetch(pos) - pos = (pos & 0xff00) | ((pos + 1) & 0x00ff) - high = fetch(pos) - @_pc = high * 256 + low - @clk += CLK_5 - end - - def _jsr - data = @_pc + 1 - push16(data) - @_pc = peek16(@_pc) - @clk += CLK_6 - end - - def _rts - @_pc = (pull16 + 1) & 0xffff - @clk += CLK_6 - end - - def _rti - @clk += CLK_6 - packed = pull8 - @_pc = pull16 - flags_unpack(packed) - @clk_irq = @irq_flags == 0 || @_p_i != 0 ? FOREVER_CLOCK : @clk_target = 0 - end - - def _bne - branch(@_p_nz & 0xff != 0) - end - - def _beq - branch(@_p_nz & 0xff == 0) - end - - def _bmi - branch(@_p_nz & 0x180 != 0) - end - - def _bpl - branch(@_p_nz & 0x180 == 0) - end - - def _bcs - branch(@_p_c != 0) - end - - def _bcc - branch(@_p_c == 0) - end - - def _bvs - branch(@_p_v != 0) - end - - def _bvc - branch(@_p_v == 0) - end - - # math operations - def _adc - tmp = @_a + @data + @_p_c - @_p_v = ~(@_a ^ @data) & (@_a ^ tmp) & 0x80 - @_p_nz = @_a = tmp & 0xff - @_p_c = tmp[8] - end - - def _sbc - data = @data ^ 0xff - tmp = @_a + data + @_p_c - @_p_v = ~(@_a ^ data) & (@_a ^ tmp) & 0x80 - @_p_nz = @_a = tmp & 0xff - @_p_c = tmp[8] - end - - # logical operations - def _and - @_p_nz = @_a &= @data - end - - def _ora - @_p_nz = @_a |= @data - end - - def _eor - @_p_nz = @_a ^= @data - end - - def _bit - @_p_nz = ((@data & @_a) != 0 ? 1 : 0) | ((@data & 0x80) << 1) - @_p_v = @data & 0x40 - end - - def _cmp - data = @_a - @data - @_p_nz = data & 0xff - @_p_c = 1 - data[8] - end - - def _cpx - data = @_x - @data - @_p_nz = data & 0xff - @_p_c = 1 - data[8] - end - - def _cpy - data = @_y - @data - @_p_nz = data & 0xff - @_p_c = 1 - data[8] - end - - # shift operations - def _asl - @_p_c = @data >> 7 - @data = @_p_nz = @data << 1 & 0xff - end - - def _lsr - @_p_c = @data & 1 - @data = @_p_nz = @data >> 1 - end - - def _rol - @_p_nz = (@data << 1 & 0xff) | @_p_c - @_p_c = @data >> 7 - @data = @_p_nz - end - - def _ror - @_p_nz = (@data >> 1) | (@_p_c << 7) - @_p_c = @data & 1 - @data = @_p_nz - end - - # increment and decrement operations - def _dec - @data = @_p_nz = (@data - 1) & 0xff - end - - def _inc - @data = @_p_nz = (@data + 1) & 0xff - end - - def _dex - @clk += CLK_2 - @data = @_p_nz = @_x = (@_x - 1) & 0xff - end - - def _dey - @clk += CLK_2 - @data = @_p_nz = @_y = (@_y - 1) & 0xff - end - - def _inx - @clk += CLK_2 - @data = @_p_nz = @_x = (@_x + 1) & 0xff - end - - def _iny - @clk += CLK_2 - @data = @_p_nz = @_y = (@_y + 1) & 0xff - end - - # flags instructions - def _clc - @clk += CLK_2 - @_p_c = 0 - end - - def _sec - @clk += CLK_2 - @_p_c = 1 - end - - def _cld - @clk += CLK_2 - @_p_d = 0 - end - - def _sed - @clk += CLK_2 - @_p_d = 8 - end - - def _clv - @clk += CLK_2 - @_p_v = 0 - end - - def _sei - @clk += CLK_2 - if @_p_i == 0 - @_p_i = 0x04 - @clk_irq = FOREVER_CLOCK - do_isr(IRQ_VECTOR) if @irq_flags != 0 - end - end - - def _cli - @clk += CLK_2 - if @_p_i != 0 - @_p_i = 0 - if @irq_flags != 0 - clk = @clk_irq = @clk + 1 - @clk_target = clk if @clk_target > clk - end - end - end - - # stack operations - def _pha - @clk += CLK_3 - push8(@_a) - end - - def _php - @clk += CLK_3 - data = flags_pack | 0x10 - push8(data) - end - - def _pla - @clk += CLK_4 - @_p_nz = @_a = pull8 - end - - def _plp - @clk += CLK_4 - i = @_p_i - flags_unpack(pull8) - if @irq_flags != 0 - if i > @_p_i - clk = @clk_irq = @clk + 1 - @clk_target = clk if @clk_target > clk - elsif i < @_p_i - @clk_irq = FOREVER_CLOCK - do_isr(IRQ_VECTOR) - end - end - end - - def _tsx - @clk += CLK_2 - @_p_nz = @_x = @_sp - end - - def _txs - @clk += CLK_2 - @_sp = @_x - end - - # undocumented instructions, rarely used - def _anc - @_p_nz = @_a &= @data - @_p_c = @_p_nz >> 7 - end - - def _ane - @_a = (@_a | 0xee) & @_x & @data - @_p_nz = @_a - end - - def _arr - @_a = ((@data & @_a) >> 1) | (@_p_c << 7) - @_p_nz = @_a - @_p_c = @_a[6] - @_p_v = @_a[6] ^ @_a[5] - end - - def _asr - @_p_c = @data & @_a & 0x1 - @_p_nz = @_a = (@data & @_a) >> 1 - end - - def _dcp - @data = (@data - 1) & 0xff - _cmp - end - - def _isb - @data = (@data + 1) & 0xff - _sbc - end - - def _las - @_sp &= @data - @_p_nz = @_a = @_x = @_sp - end - - def _lax - @_p_nz = @_a = @_x = @data - end - - def _lxa - @_p_nz = @_a = @_x = @data - end - - def _rla - c = @_p_c - @_p_c = @data >> 7 - @data = (@data << 1 & 0xff) | c - @_p_nz = @_a &= @data - end - - def _rra - c = @_p_c << 7 - @_p_c = @data & 1 - @data = (@data >> 1) | c - _adc - end - - def _sax - @data = @_a & @_x - end - - def _sbx - @data = (@_a & @_x) - @data - @_p_c = (@data & 0xffff) <= 0xff ? 1 : 0 - @_p_nz = @_x = @data & 0xff - end - - def _sha - @data = @_a & @_x & ((@addr >> 8) + 1) - end - - def _shs - @_sp = @_a & @_x - @data = @_sp & ((@addr >> 8) + 1) - end - - def _shx - @data = @_x & ((@addr >> 8) + 1) - @addr = (@data << 8) | (@addr & 0xff) - end - - def _shy - @data = @_y & ((@addr >> 8) + 1) - @addr = (@data << 8) | (@addr & 0xff) - end - - def _slo - @_p_c = @data >> 7 - @data = @data << 1 & 0xff - @_p_nz = @_a |= @data - end - - def _sre - @_p_c = @data & 1 - @data >>= 1 - @_p_nz = @_a ^= @data - end - - # nops - def _nop - end - - # interrupts - def _brk - data = @_pc + 1 - push16(data) - data = flags_pack | 0x10 - push8(data) - @_p_i = 0x04 - @clk_irq = FOREVER_CLOCK - @clk += CLK_7 - addr = fetch_irq_isr_vector # for inlining peek16 - @_pc = peek16(addr) - end - - def _jam - @_pc = (@_pc - 1) & 0xffff - @clk += CLK_2 - unless @jammed - @jammed = true - # interrupt reset - @clk_nmi = FOREVER_CLOCK - @clk_irq = FOREVER_CLOCK - @irq_flags = 0 - end - end - - ########################################################################### - # default core - - def r_op(instr, mode) - send(mode, true, false) - send(instr) - end - - def w_op(instr, mode, store) - send(mode, false, true) - send(instr) - send(store) - end - - def rw_op(instr, mode, store) - send(mode, true, true) - send(instr) - send(store) - end - - def a_op(instr) - @clk += CLK_2 - @data = @_a - send(instr) - @_a = @data - end - - def no_op(_instr, ops, ticks) - @_pc += ops - @clk += ticks * RP2A03_CC - end - - def do_clock - clock = @apu.do_clock - - clock = @clk_frame if clock > @clk_frame - - if @clk < @clk_nmi - clock = @clk_nmi if clock > @clk_nmi - if @clk < @clk_irq - clock = @clk_irq if clock > @clk_irq - else - @clk_irq = FOREVER_CLOCK - do_isr(IRQ_VECTOR) - end - else - @clk_nmi = @clk_irq = FOREVER_CLOCK - do_isr(NMI_VECTOR) - end - @clk_target = clock - end - - def run - do_clock - begin - begin - @opcode = fetch(@_pc) - - if @conf.loglevel >= 3 - @conf.debug("PC:%04X A:%02X X:%02X Y:%02X P:%02X SP:%02X CYC:%3d : OPCODE:%02X (%d, %d)" % [ - @_pc, @_a, @_x, @_y, flags_pack, @_sp, @clk / 4 % 341, @opcode, @clk, @clk_target - ]) - end - - @_pc += 1 - - send(*Ractor.current[:DISPATCH][@opcode]) - - @ppu.sync(@clk) if @ppu_sync - end while @clk < @clk_target - do_clock - end while @clk < @clk_frame - end - - ADDRESSING_MODES = Ractor.make_shareable({ - ctl: [:imm, :zpg, :imm, :abs, nil, :zpg_x, nil, :abs_x], - rmw: [:imm, :zpg, :imm, :abs, nil, :zpg_y, nil, :abs_y], - alu: [:ind_x, :zpg, :imm, :abs, :ind_y, :zpg_x, :abs_y, :abs_x], - uno: [:ind_x, :zpg, :imm, :abs, :ind_y, :zpg_y, :abs_y, :abs_y], - }) - - def self.op(opcodes, args) - Ractor.current[:DISPATCH] ||= [] - opcodes.each do |opcode| - if args.is_a?(Array) && [:r_op, :w_op, :rw_op].include?(args[0]) - kind, op, mode = args - mode = ADDRESSING_MODES[mode][opcode >> 2 & 7] - send_args = [kind, op, mode] - send_args << (mode.to_s.start_with?("zpg") ? :store_zpg : :store_mem) if kind != :r_op - Ractor.current[:DISPATCH][opcode] = send_args - else - Ractor.current[:DISPATCH][opcode] = [*args] - end - end - end - - ON_LOAD = [] - def self.on_load(dispatch_list) - ON_LOAD << dispatch_list - end - - def load! - ON_LOAD.each do |on_load| - on_load.each do |op| - method = op.first - arg1 = op[1] - arg2 = op[2] - self.class.send(method, arg1, arg2) - end - end - end - - on_load( - # load instructions - [ - [:op,[0xa9, 0xa5, 0xb5, 0xad, 0xbd, 0xb9, 0xa1, 0xb1], [:r_op, :_lda, :alu]], - [:op,[0xa2, 0xa6, 0xb6, 0xae, 0xbe], [:r_op, :_ldx, :rmw]], - [:op,[0xa0, 0xa4, 0xb4, 0xac, 0xbc], [:r_op, :_ldy, :ctl]], - - # store instructions - [:op,[0x85, 0x95, 0x8d, 0x9d, 0x99, 0x81, 0x91], [:w_op, :_sta, :alu]], - [:op,[0x86, 0x96, 0x8e], [:w_op, :_stx, :rmw]], - [:op,[0x84, 0x94, 0x8c], [:w_op, :_sty, :ctl]], - - # transfer instructions - [:op,[0xaa], :_tax], - [:op,[0xa8], :_tay], - [:op,[0x8a], :_txa], - [:op,[0x98], :_tya], - - # flow control instructions - [:op,[0x4c], :_jmp_a], - [:op,[0x6c], :_jmp_i], - [:op,[0x20], :_jsr], - [:op,[0x60], :_rts], - [:op,[0x40], :_rti], - [:op,[0xd0], :_bne], - [:op,[0xf0], :_beq], - [:op,[0x30], :_bmi], - [:op,[0x10], :_bpl], - [:op,[0xb0], :_bcs], - [:op,[0x90], :_bcc], - [:op,[0x70], :_bvs], - [:op,[0x50], :_bvc], - - # math operations - [:op,[0x69, 0x65, 0x75, 0x6d, 0x7d, 0x79, 0x61, 0x71], [:r_op, :_adc, :alu]], - [:op,[0xe9, 0xeb, 0xe5, 0xf5, 0xed, 0xfd, 0xf9, 0xe1, 0xf1], [:r_op, :_sbc, :alu]], - - # logical operations - [:op,[0x29, 0x25, 0x35, 0x2d, 0x3d, 0x39, 0x21, 0x31], [:r_op, :_and, :alu]], - [:op,[0x09, 0x05, 0x15, 0x0d, 0x1d, 0x19, 0x01, 0x11], [:r_op, :_ora, :alu]], - [:op,[0x49, 0x45, 0x55, 0x4d, 0x5d, 0x59, 0x41, 0x51], [:r_op, :_eor, :alu]], - [:op,[0x24, 0x2c], [:r_op, :_bit, :alu]], - [:op,[0xc9, 0xc5, 0xd5, 0xcd, 0xdd, 0xd9, 0xc1, 0xd1], [:r_op, :_cmp, :alu]], - [:op,[0xe0, 0xe4, 0xec], [:r_op, :_cpx, :rmw]], - [:op,[0xc0, 0xc4, 0xcc], [:r_op, :_cpy, :rmw]], - - # shift operations - [:op,[0x0a], [:a_op, :_asl]], - [:op,[0x06, 0x16, 0x0e, 0x1e], [:rw_op, :_asl, :alu]], - [:op,[0x4a], [:a_op, :_lsr]], - [:op,[0x46, 0x56, 0x4e, 0x5e], [:rw_op, :_lsr, :alu]], - [:op,[0x2a], [:a_op, :_rol]], - [:op,[0x26, 0x36, 0x2e, 0x3e], [:rw_op, :_rol, :alu]], - [:op,[0x6a], [:a_op, :_ror]], - [:op,[0x66, 0x76, 0x6e, 0x7e], [:rw_op, :_ror, :alu]], - - # increment and decrement operations - [:op,[0xc6, 0xd6, 0xce, 0xde], [:rw_op, :_dec, :alu]], - [:op,[0xe6, 0xf6, 0xee, 0xfe], [:rw_op, :_inc, :alu]], - [:op,[0xca], :_dex], - [:op,[0x88], :_dey], - [:op,[0xe8], :_inx], - [:op,[0xc8], :_iny], - - # flags instructions - [:op,[0x18], :_clc], - [:op,[0x38], :_sec], - [:op,[0xd8], :_cld], - [:op,[0xf8], :_sed], - [:op,[0x58], :_cli], - [:op,[0x78], :_sei], - [:op,[0xb8], :_clv], - - # stack operations - [:op,[0x48], :_pha], - [:op,[0x08], :_php], - [:op,[0x68], :_pla], - [:op,[0x28], :_plp], - [:op,[0xba], :_tsx], - [:op,[0x9a], :_txs], - - # undocumented instructions, rarely used - [:op,[0x0b, 0x2b], [:r_op, :_anc, :uno]], - [:op,[0x8b], [:r_op, :_ane, :uno]], - [:op,[0x6b], [:r_op, :_arr, :uno]], - [:op,[0x4b], [:r_op, :_asr, :uno]], - [:op,[0xc7, 0xd7, 0xc3, 0xd3, 0xcf, 0xdf, 0xdb], [:rw_op, :_dcp, :alu]], - [:op,[0xe7, 0xf7, 0xef, 0xff, 0xfb, 0xe3, 0xf3], [:rw_op, :_isb, :alu]], - [:op,[0xbb], [:r_op, :_las, :uno]], - [:op,[0xa7, 0xb7, 0xaf, 0xbf, 0xa3, 0xb3], [:r_op, :_lax, :uno]], - [:op,[0xab], [:r_op, :_lxa, :uno]], - [:op,[0x27, 0x37, 0x2f, 0x3f, 0x3b, 0x23, 0x33], [:rw_op, :_rla, :alu]], - [:op,[0x67, 0x77, 0x6f, 0x7f, 0x7b, 0x63, 0x73], [:rw_op, :_rra, :alu]], - [:op,[0x87, 0x97, 0x8f, 0x83], [:w_op, :_sax, :uno]], - [:op,[0xcb], [:r_op, :_sbx, :uno]], - [:op,[0x9f, 0x93], [:w_op, :_sha, :uno]], - [:op,[0x9b], [:w_op, :_shs, :uno]], - [:op,[0x9e], [:w_op, :_shx, :rmw]], - [:op,[0x9c], [:w_op, :_shy, :ctl]], - [:op,[0x07, 0x17, 0x0f, 0x1f, 0x1b, 0x03, 0x13], [:rw_op, :_slo, :alu]], - [:op,[0x47, 0x57, 0x4f, 0x5f, 0x5b, 0x43, 0x53], [:rw_op, :_sre, :alu]], - - # nops - [:op,[0x1a, 0x3a, 0x5a, 0x7a, 0xda, 0xea, 0xfa], [:no_op, :_nop, 0, 2]], - [:op,[0x80, 0x82, 0x89, 0xc2, 0xe2], [:no_op, :_nop, 1, 2]], - [:op,[0x04, 0x44, 0x64], [:no_op, :_nop, 1, 3]], - [:op,[0x14, 0x34, 0x54, 0x74, 0xd4, 0xf4], [:no_op, :_nop, 1, 4]], - [:op,[0x0c], [:no_op, :_nop, 2, 4]], - [:op,[0x1c, 0x3c, 0x5c, 0x7c, 0xdc, 0xfc], [:r_op, :_nop, :ctl]], - - # interrupts - [:op,[0x00], :_brk], - [:op,[0x02, 0x12, 0x22, 0x32, 0x42, 0x52, 0x62, 0x72, 0x92, 0xb2, 0xd2, 0xf2], :_jam], - ] - ) - Ractor.make_shareable(ON_LOAD) - - ########################################################################### - # optimized core generator - class OptimizedCodeBuilder - include CodeOptimizationHelper - - OPTIONS = [:method_inlining, :constant_inlining, :ivar_localization, :trivial_branches].freeze - - LOCALIZE_IVARS = [:@addr, :@data, :@_a, :@_x, :@_y, :@_pc, :@_sp, :@fetch, :@store, :@ram, :@opcode].freeze - - def build - depends(:ivar_localization, :method_inlining) - - mdefs = parse_method_definitions(__FILE__) - code = build_loop(mdefs) - - # optimize! - code = cpu_expand_methods(code, mdefs) if @method_inlining - code = remove_trivial_branches(code) if @trivial_branches - code = expand_constants(code) if @constant_inlining - code = localize_instance_variables(code, LOCALIZE_IVARS) if @ivar_localization - - gen( - "def self.run", - indent(2, code), - "end", - ) - end - - # generate a main code - def build_loop(mdefs) - Ractor.current[:DISPATCH] ||= [] - dispatch = gen( - "case @opcode", - *Ractor.current[:DISPATCH].map.with_index do |args, opcode| - if args.size > 1 - mhd, instr, = args - code = expand_inline_methods("#{ mhd }(#{ args.drop(1).join(", ") })", mhd, mdefs[mhd]) - code = code.gsub(/send\((\w+), (.*?)\)/) { "#{ $1 }(#{ $2 })" } - code = code.gsub(/send\((\w+)\)/) { $1 } - code = code[1..-2].split("; ") - else - instr = code = args[0] - end - "when 0x%02x # #{ instr }\n" % opcode + indent(2, gen(*code)) - end, - "end" - ) - main = mdefs[:run].body.sub("@conf.loglevel >= 3") { @loglevel >= 3 } - main.sub(/^ *send.*\n/) { indent(4, dispatch) } - end - - # inline method calls - def cpu_expand_methods(code, mdefs) - code = expand_methods(code, mdefs, mdefs.keys.grep(/^_/)) - [ - [:_adc, :_sbc, :_cmp, :store_mem, :store_zpg], - [:imm, :abs, :zpg, :abs_x, :abs_y, :zpg_x, :zpg_y, :ind_x, :ind_y], - [:abs_reg, :zpg_reg], - [:read_write], - [:do_clock], - [:do_isr], - [:branch, :push16], - [:push8], - ].each do |meths| - code = expand_methods(code, mdefs, meths) - end - [:fetch, :peek16, :store, :pull16, :pull8].each do |meth| - code = expand_inline_methods(code, meth, mdefs[meth]) - end - code - end - - # inline constants - def expand_constants(handlers) - handlers = handlers.gsub(/CLK_(\d+)/) { eval($&) } - handlers = handlers.gsub(/FOREVER_CLOCK/) { "0xffffffff" } - handlers - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver.rb deleted file mode 100644 index d5544758..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver.rb +++ /dev/null @@ -1,165 +0,0 @@ -module Optcarrot - # A manager class for drivers (user frontend) - module Driver - DRIVER_DB = { - video: { - sdl2: :SDL2Video, - sfml: :SFMLVideo, - png: :PNGVideo, - gif: :GIFVideo, - sixel: :SixelVideo, - mplayer: :MPlayerVideo, - none: :Video, - }, - audio: { - sdl2: :SDL2Audio, - sfml: :SFMLAudio, - ao: :AoAudio, - wav: :WAVAudio, - none: :Audio, - }, - input: { - sdl2: :SDL2Input, - sfml: :SFMLInput, - term: :TermInput, - log: :LogInput, - none: :Input, - } - } - Ractor.make_shareable(DRIVER_DB) - - module_function - - def load(conf) - video = load_each(conf, :video, conf.video).new(conf) - audio = load_each(conf, :audio, conf.audio).new(conf) - input = load_each(conf, :input, conf.input).new(conf, video) - return video, audio, input - end - - def load_each(conf, type, name) - if name - klass = DRIVER_DB[type][name] - raise "unknown #{ type } driver: #{ name }" unless klass - require_relative "driver/#{ name }_#{ type }" unless name == :none - conf.debug("`#{ name }' #{ type } driver is selected") - Optcarrot.const_get(klass) - else - selected = nil - DRIVER_DB[type].each_key do |n| - begin - selected = load_each(conf, type, n) - break - rescue LoadError - conf.debug("fail to use `#{ n }' #{ type } driver") - end - end - selected - end - end - end - - # A base class of video output driver - class Video - WIDTH = 256 - TV_WIDTH = 292 - HEIGHT = 224 - - def initialize(conf) - @conf = conf - @palette_rgb = @conf.nestopia_palette ? Palette.nestopia_palette : Palette.defacto_palette - @palette = [*0..4096] # dummy palette - init - end - - attr_reader :palette - - def init - @times = [] - end - - def dispose - end - - def tick(_output) - @times << Process.clock_gettime(Process::CLOCK_MONOTONIC) - @times.shift if @times.size > 10 - @times.size < 2 ? 0 : ((@times.last - @times.first) / (@times.size - 1)) ** -1 - end - - def change_window_size(_scale) - end - - def on_resize(_width, _height) - end - end - - # A base class of audio output driver - class Audio - PACK_FORMAT = Ractor.make_shareable({ 8 => "c*", 16 => "v*" }) - BUFFER_IN_FRAME = 3 # keep audio buffer during this number of frames - - def initialize(conf) - @conf = conf - @rate = conf.audio_sample_rate - @bits = conf.audio_bit_depth - raise "sample bits must be 8 or 16" unless @bits == 8 || @bits == 16 - @pack_format = PACK_FORMAT[@bits] - - init - end - - def spec - return @rate, @bits - end - - def init - end - - def dispose - end - - def tick(_output) - end - end - - # A base class of input driver - class Input - def initialize(conf, video) - @conf = conf - @video = video - init - end - - def init - end - - def dispose - end - - def tick(_frame, _pads) - end - - def event(pads, type, code, player) - case code - when :start then pads.send(type, player, Pad::START) - when :select then pads.send(type, player, Pad::SELECT) - when :a then pads.send(type, player, Pad::A) - when :b then pads.send(type, player, Pad::B) - when :right then pads.send(type, player, Pad::RIGHT) - when :left then pads.send(type, player, Pad::LEFT) - when :down then pads.send(type, player, Pad::DOWN) - when :up then pads.send(type, player, Pad::UP) - else - return if type != :keydown - case code - when :screen_x1 then @video.change_window_size(1) - when :screen_x2 then @video.change_window_size(2) - when :screen_x3 then @video.change_window_size(3) - when :screen_full then @video.change_window_size(nil) - when :quit then exit - end - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/ao_audio.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/ao_audio.rb deleted file mode 100644 index e17d1229..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/ao_audio.rb +++ /dev/null @@ -1,63 +0,0 @@ -require "ffi" - -module Optcarrot - # A minimal binding for libao - module Ao - extend FFI::Library - ffi_lib "ao" - - # struct ao_sample_format - class SampleFormat < FFI::Struct - layout( - :bits, :int, - :rate, :int, - :channels, :int, - :byte_format, :int, - :matrix, :pointer, - ) - end - - FMT_NATIVE = 4 - - { - initialize: [[], :void], - default_driver_id: [[], :int], - open_live: [[:int, :pointer, :pointer], :pointer], - play: [[:pointer, :pointer, :int], :uint32, { blocking: true }], - close: [[:pointer], :int], - shutdown: [[], :void], - }.each do |name, params| - opt = params.last.is_a?(Hash) ? params.pop : {} - attach_function(name, :"ao_#{ name }", *params, **opt) - end - end - - # Audio output driver for libao - class AoAudio < Audio - def init - format = Ao::SampleFormat.new - format[:bits] = @bits - format[:rate] = @rate - format[:channels] = 1 - format[:byte_format] = Ao::FMT_NATIVE - format[:matrix] = nil - - Ao.initialize - driver = Ao.default_driver_id - @dev = Ao.open_live(driver, format, nil) - - @conf.fatal("ao_open_live failed") unless @dev - @buff = "".b - end - - def dispose - Ao.close(@dev) - Ao.shutdown - end - - def tick(output) - buff = output.pack(@pack_format) - Ao.play(@dev, buff, buff.bytesize) - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/gif_video.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/gif_video.rb deleted file mode 100644 index ff46fa4b..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/gif_video.rb +++ /dev/null @@ -1,71 +0,0 @@ -require_relative "misc" - -module Optcarrot - # Video output driver saving an animation GIF file - class GIFVideo < Video - def init - super - - @f = File.open(File.basename(@conf.video_output) + ".gif", "wb") - - @palette, colors = Driver.quantize_colors(@palette_rgb) - - # GIF Header - header = ["GIF89a", WIDTH, HEIGHT, 0xf7, 0, 0, *colors.flatten] - @f << header.pack("A*vvC*") - - # Application Extension - @f << [0x21, 0xff, 0x0b, "NETSCAPE", "2.0", 0x03, 0x01, 0x00, 0x00].pack("C3A8A3CCvC") - - # Graphic Control Extension - @header = [0x21, 0xf9, 0x04, 0x00, 1, 255, 0x00].pack("C4vCC") - @header << [0x2c, 0, 0, WIDTH, HEIGHT, 0, 8].pack("Cv4C*") - end - - def dispose - # Trailer - @f << [0x3b].pack("C") - - @f.close - end - - def tick(screen) - compress(screen) - super - end - - def compress(data) - @f << @header - - max_code = 257 - dict = (0..max_code).map {|n| [n, []] } - - buff = "" - out = ->(code) { buff << ("%0#{ max_code.bit_length }b" % code).reverse } - - cur_dict = dict - code = nil - out[256] # clear code - data.each do |d| - if cur_dict[d] - code, cur_dict = cur_dict[d] - else - out[code] - if max_code < 4094 - max_code += 1 - cur_dict[d] = [max_code, []] - end - code, cur_dict = dict[d] - end - end - out[code] - out[257] # end code - - buff = [buff].pack("b*") - - buff = buff.gsub(/.{1,255}/m) { [$&.size].pack("C") + $& } + [0].pack("C") - - @f << buff - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/log_input.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/log_input.rb deleted file mode 100644 index c5797962..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/log_input.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Optcarrot - # Input driver replaying a recorded input log - class LogInput < Input - def init - @log = @conf.key_log || [] - @log = Marshal.load(File.binread(@log)) if @log.is_a?(String) - @prev_state = 0 - end - - attr_writer :log - - def dispose - end - - def tick(frame, pads) - state = @log[frame] || 0 - [ - Pad::SELECT, - Pad::START, - Pad::A, - Pad::B, - Pad::RIGHT, - Pad::LEFT, - Pad::DOWN, - Pad::UP, - ].each do |i| - if @prev_state[i] == 0 && state[i] == 1 - pads.keydown(0, i) - elsif @prev_state[i] == 1 && state[i] == 0 - pads.keyup(0, i) - end - end - @prev_state = state - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/misc.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/misc.rb deleted file mode 100644 index 3f7e66b6..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/misc.rb +++ /dev/null @@ -1,123 +0,0 @@ -module Optcarrot - # some helper methods for drivers - module Driver - module_function - - def quantize_colors(colors, limit = 256) - # median-cut - @cubes = [colors.uniq] - (limit - 1).times do - cube = @cubes.pop - axis = (0..2).max_by do |a| - min, max = cube.map {|color| color[a] }.minmax - max - min - end - cube = cube.sort_by {|color| color[axis] } - @cubes << cube[0, cube.size / 2] << cube[(cube.size / 2)..-1] - @cubes.sort_by! {|a| a.size } - end - raise if @cubes.size != limit - idx = 0 - mapping = {} - unified_colors = @cubes.map do |cube| - cube.each {|color| mapping[color] = idx } - idx += 1 - cube.transpose.map {|ary| ary.inject(&:+) / ary.size } - end - palette = colors.map {|color| mapping[color] } - return palette, unified_colors - end - - def cutoff_overscan(colors) - colors[0, 2048] = EMPTY_ARRAY - colors[-2048, 2048] = EMPTY_ARRAY - end - EMPTY_ARRAY = [] - - SIZE = 1 - def show_fps(colors, fps, palette) - digits = fps > 100 ? 3 : 2 - w = (3 + digits) * 4 - - (223 - 6 * SIZE).upto(223) do |y| - (255 - w * SIZE).upto(255) do |x| - c = colors[idx = x + y * 256] - - # darken the right-bottom corner for drawing FPS - if block_given? - c = yield c - else - r = ((c >> 16) & 0xff) / 4 - g = ((c >> 8) & 0xff) / 4 - b = ((c >> 0) & 0xff) / 4 - c = (c & 0xff000000) | (r << 16) | (g << 8) | b - end - - colors[idx] = c - end - end - - # decide fps color - color = - case - when fps >= 90 then palette[0x19] # green - when fps >= 60 then palette[0x11] # blue - when fps >= 55 then palette[0x28] # yellow - else palette[0x16] # red - end - - # draw FPS - (3 + digits).times do |i| # show "xxFPS" - bits = FONT[i < digits ? fps / 10**(digits - i - 1) % 10 : i - digits + 10] - 5.times do |y| - 3.times do |x| - SIZE.times do |dy| - SIZE.times do |dx| - if bits[x + y * 3] == 1 - colors[(224 + (y - 6) * SIZE + dy) * 256 + (256 + i * 4 + x - w) * SIZE + dx] = color - end - end - end - end - end - end - end - - # tiny font data for fps - FONT = [ - 0b111_101_101_101_111, # '0' - 0b111_010_010_011_010, # '1' - 0b111_001_111_100_111, # '2' - 0b111_100_111_100_111, # '3' - 0b100_100_111_101_101, # '4' - 0b111_100_111_001_111, # '5' - 0b111_101_111_001_111, # '6' - 0b010_010_100_101_111, # '7' - 0b111_101_111_101_111, # '8' - 0b111_100_111_101_111, # '9' - 0b001_001_111_001_111, # 'F' - 0b001_011_101_101_011, # 'P' - 0b011_100_010_001_110, # 'S' - ] - - # icon data - def icon_data - width, height = 16, 16 - pixels = FFI::MemoryPointer.new(:uint8, width * height * 4) - - palette = [ - 0x00000000, 0xff0026ff, 0xff002cda, 0xff004000, 0xff0050ff, 0xff006000, 0xff007aff, 0xff00a000, 0xff00a4ff, - 0xff00e000, 0xff4f5600, 0xffa0a000, 0xffe0e000 - ] - dat = "38*2309(3:9&,8210982(32,=&8*1:=2,9=1#5$(2&3'?%(-@715+)A3'?'A-.<0$$++B1:$?B6<0$++)$43#%)'A@<:%B314@.<1" - i = 66 - "54'4-6>')+((;/7#0#,2,*//..'$%-11*(00##".scan(/../) do - dat = dat.gsub(i.chr, $&) - i -= 1 - end - dat = dat.bytes.map {|clr| palette[clr - 35] } - - return width, height, pixels.write_bytes(dat.pack("V*")) - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/mplayer_video.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/mplayer_video.rb deleted file mode 100644 index 43e83b25..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/mplayer_video.rb +++ /dev/null @@ -1,47 +0,0 @@ -require_relative "misc" - -module Optcarrot - # Video output driver using mplayer - # Inspired from https://github.com/polmuz/pypy-image-demo/blob/master/io.py - class MPlayerVideo < Video - MAX_FPS = NES::FPS - - def init - super - @mplayer = IO.popen("mplayer -really-quiet -noframedrop -vf scale - 2>/dev/null", "wb") - @mplayer.puts("YUV4MPEG2 W#{ WIDTH } H#{ HEIGHT } F#{ MAX_FPS }:1 Ip A#{ TV_WIDTH }:#{ WIDTH } C444") - - @palette = @palette_rgb.map do |r, g, b| - # From https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion - y = (+0.299 * r + 0.587 * g + 0.114 * b).to_i + 0 - cb = (-0.168736 * r - 0.331264 * g + 0.5 * b).to_i + 128 - cr = (+0.5 * r - 0.418688 * g - 0.081312 * b).to_i + 128 - [y, cr, cb] - end - end - - def dispose - @mplayer.close - end - - def tick(screen) - @mplayer.write "FRAME\n" - - Driver.cutoff_overscan(screen) - - if @conf.show_fps && @times.size >= 2 - fps = (1.0 / (@times[-1] - @times[-2])).round - Driver.show_fps(screen, fps, @palette) do |y, cr, cb| - [y / 4, cr, cb] - end - end - - colors = screen.map {|a| a[0] } + - screen.map {|a| a[1] } + - screen.map {|a| a[2] } - @mplayer.write colors.pack("C*") - - super - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/png_video.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/png_video.rb deleted file mode 100644 index e6c45b86..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/png_video.rb +++ /dev/null @@ -1,74 +0,0 @@ -module Optcarrot - # Video output driver saving a PNG file - class PNGVideo < Video - def init - super - @palette = @palette_rgb - end - - def dispose - return unless @screen && @screen.size >= WIDTH * HEIGHT - bin = PNGEncoder.new(@screen, WIDTH, HEIGHT).encode - File.binwrite(File.basename(@conf.video_output, ".EXT") + ".png", bin) - end - - def tick(screen) - @screen = screen - super - end - - # PNG data generator - class PNGEncoder - def initialize(screen, width, height) - @screen = screen - @width = width - @height = height - end - - def encode - data = [] - @height.times do |y| - data << 0 - @width.times do |x| - data.concat(@screen[x + y * @width]) - end - end - - [ - "\x89PNG\r\n\x1a\n".b, - chunk("IHDR", [@width, @height, 8, 2, 0, 0, 0].pack("NNCCCCC")), - chunk("IDAT", cheat_zlib_deflate(data)), - chunk("IEND", ""), - ].join - end - - def chunk(type, data) - [data.bytesize, type, data, crc32(type + data)].pack("NA4A*N") - end - - ADLER_MOD = 65221 - def cheat_zlib_deflate(data) - a = 1 - b = 0 - data.each {|d| b += a += d } - code = [0x78, 0x9c].pack("C2") # Zlib header (RFC 1950) - until data.empty? - s = data.shift(0xffff) - # cheat Deflate (RFC 1951) - code << [data.empty? ? 1 : 0, s.size, ~s.size, *s].pack("CvvC*") - end - code << [b % ADLER_MOD, a % ADLER_MOD].pack("nn") # Adler-32 (RFC 1950) - end - - CRC_TABLE = (0..255).map do |crc| - 8.times {|j| crc ^= 0x1db710641 << j if crc[j] == 1 } - crc >> 8 - end - def crc32(data) - crc = 0xffffffff - data.each_byte {|v| crc = (crc >> 8) ^ CRC_TABLE[(crc & 0xff) ^ v] } - crc ^ 0xffffffff - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2.rb deleted file mode 100644 index 4aeedbff..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2.rb +++ /dev/null @@ -1,214 +0,0 @@ -require "ffi" - -module Optcarrot - # A minimal binding for SDL2 - module SDL2 - extend FFI::Library - ffi_lib "SDL2" - - # struct SDL_Version - class Version < FFI::Struct - layout( - :major, :uint8, - :minor, :uint8, - :patch, :uint8, - ) - end - - INIT_TIMER = 0x00000001 - INIT_AUDIO = 0x00000010 - INIT_VIDEO = 0x00000020 - INIT_JOYSTICK = 0x00000200 - - # Video - - WINDOWPOS_UNDEFINED = 0x1fff0000 - WINDOW_FULLSCREEN = 0x00000001 - WINDOW_OPENGL = 0x00000002 - WINDOW_SHOWN = 0x00000004 - WINDOW_HIDDEN = 0x00000008 - WINDOW_BORDERLESS = 0x00000010 - WINDOW_RESIZABLE = 0x00000020 - WINDOW_MINIMIZED = 0x00000040 - WINDOW_MAXIMIZED = 0x00000080 - WINDOW_INPUT_GRABBED = 0x00000100 - WINDOW_INPUT_FOCUS = 0x00000200 - WINDOW_MOUSE_FOCUS = 0x00000400 - WINDOW_FULLSCREEN_DESKTOP = (WINDOW_FULLSCREEN | 0x00001000) - - pixels = FFI::MemoryPointer.new(:uint32) - pixels.write_int32(0x04030201) - PACKEDORDER = - case pixels.read_bytes(4).unpack("C*") - when [1, 2, 3, 4] then 3 # PACKEDORDER_ARGB - when [4, 3, 2, 1] then 8 # PACKEDORDER_BGRA - else - raise "unknown endian" - end - - PIXELFORMAT_8888 = - (1 << 28) | - (6 << 24) | # PIXELTYPE_PACKED32 - (PACKEDORDER << 20) | - (6 << 16) | # PACKEDLAYOUT_8888 - (32 << 8) | # bits - (4 << 0) # bytes - - TEXTUREACCESS_STREAMING = 1 - - # Input - - # struct SDL_KeyboardEvent - class KeyboardEvent < FFI::Struct - layout( - :type, :uint32, - :timestamp, :uint32, - :windowID, :uint32, - :state, :uint8, - :repeat, :uint8, - :padding2, :uint8, - :padding3, :uint8, - :scancode, :int, - :sym, :int, - ) - end - - # struct SDL_JoyAxisEvent - class JoyAxisEvent < FFI::Struct - layout( - :type, :uint32, - :timestamp, :uint32, - :which, :uint32, - :axis, :uint8, - :padding1, :uint8, - :padding2, :uint8, - :padding3, :uint8, - :value, :int16, - :padding4, :uint16, - ) - end - - # struct SDL_JoyButtonEvent - class JoyButtonEvent < FFI::Struct - layout( - :type, :uint32, - :timestamp, :uint32, - :which, :uint32, - :button, :uint8, - :state, :uint8, - :padding1, :uint8, - :padding2, :uint8, - ) - end - - # struct SDL_JoyDeviceEvent - class JoyDeviceEvent < FFI::Struct - layout( - :type, :uint32, - :timestamp, :uint32, - :which, :int32, - ) - end - - # Audio - - AUDIO_S8 = 0x8008 - AUDIO_S16LSB = 0x8010 - AUDIO_S16MSB = 0x9010 - - pixels = FFI::MemoryPointer.new(:uint16) - pixels.write_int16(0x0201) - AUDIO_S16SYS = - case pixels.read_bytes(2).unpack("C*") - when [1, 2] then AUDIO_S16LSB - when [2, 1] then AUDIO_S16MSB - else - raise "unknown endian" - end - - # struct SDL_AudioSpec - class AudioSpec < FFI::Struct - layout( - :freq, :int, - :format, :uint16, - :channels, :uint8, - :silence, :uint8, - :samples, :uint16, - :padding, :uint16, - :size, :uint32, - :callback, :pointer, - :userdata, :pointer, - ) - end - - # rubocop:disable Naming/MethodName - def self.AudioCallback(blk) - FFI::Function.new(:void, [:pointer, :pointer, :int], blk) - end - # rubocop:enable Naming/MethodName - - # attach_functions - - functions = { - InitSubSystem: [[:uint32], :int], - QuitSubSystem: [[:uint32], :void, { blocking: true }], - Delay: [[:int], :void, { blocking: true }], - GetError: [[], :string], - GetTicks: [[], :uint32], - - CreateWindow: [[:string, :int, :int, :int, :int, :uint32], :pointer], - DestroyWindow: [[:pointer], :void], - CreateRenderer: [[:pointer, :int, :uint32], :pointer], - DestroyRenderer: [[:pointer], :void], - CreateRGBSurfaceFrom: [[:pointer, :int, :int, :int, :int, :uint32, :uint32, :uint32, :uint32], :pointer], - FreeSurface: [[:pointer], :void], - GetWindowFlags: [[:pointer], :uint32], - SetWindowFullscreen: [[:pointer, :uint32], :int], - SetWindowSize: [[:pointer, :int, :int], :void], - SetWindowTitle: [[:pointer, :string], :void], - SetWindowIcon: [[:pointer, :pointer], :void], - SetHint: [[:string, :string], :int], - RenderSetLogicalSize: [[:pointer, :int, :int], :int], - CreateTexture: [[:pointer, :uint32, :int, :int, :int], :pointer], - DestroyTexture: [[:pointer], :void], - PollEvent: [[:pointer], :int], - UpdateTexture: [[:pointer, :pointer, :pointer, :int], :int], - RenderClear: [[:pointer], :int], - RenderCopy: [[:pointer, :pointer, :pointer, :pointer], :int], - RenderPresent: [[:pointer], :int], - - OpenAudioDevice: [[:string, :int, AudioSpec.ptr, AudioSpec.ptr, :int], :uint32, { blocking: true }], - PauseAudioDevice: [[:uint32, :int], :void, { blocking: true }], - CloseAudioDevice: [[:uint32], :void, { blocking: true }], - - NumJoysticks: [[], :int], - JoystickOpen: [[:int], :pointer], - JoystickClose: [[:pointer], :void], - JoystickNameForIndex: [[:int], :string], - JoystickNumAxes: [[:pointer], :int], - JoystickNumButtons: [[:pointer], :int], - JoystickInstanceID: [[:pointer], :uint32], - - QueueAudio: [[:uint32, :pointer, :int], :int], - GetQueuedAudioSize: [[:uint32], :uint32], - ClearQueuedAudio: [[:uint32], :void], - } - - # check SDL version - - attach_function(:GetVersion, :SDL_GetVersion, [:pointer], :void) - version = Version.new - GetVersion(version) - version = [version[:major], version[:minor], version[:patch]] - if (version <=> [2, 0, 4]) < 0 - functions.delete(:QueueAudio) - functions.delete(:GetQueuedAudioSize) - functions.delete(:ClearQueuedAudio) - end - - functions.each do |name, params| - opt = params.last.is_a?(Hash) ? params.pop : {} - attach_function(name, :"SDL_#{ name }", *params, **opt) - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_audio.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_audio.rb deleted file mode 100644 index eb85abda..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_audio.rb +++ /dev/null @@ -1,61 +0,0 @@ -require_relative "sdl2" - -module Optcarrot - # Audio output driver for SDL2 - class SDL2Audio < Audio - FORMAT = { 8 => SDL2::AUDIO_S8, 16 => SDL2::AUDIO_S16LSB } - - def init - SDL2.InitSubSystem(SDL2::INIT_AUDIO) - @max_buff_size = @rate * @bits / 8 * BUFFER_IN_FRAME / NES::FPS - - # we need to prevent this callback object from GC - @callback = SDL2.AudioCallback(method(:callback)) - - desired = SDL2::AudioSpec.new - desired[:freq] = @rate - desired[:format] = FORMAT[@bits] - desired[:channels] = 1 - desired[:samples] = @rate / 60 * 2 - desired[:callback] = defined?(SDL2.QueueAudio) ? nil : @callback - desired[:userdata] = nil - obtained = SDL2::AudioSpec.new - @dev = SDL2.OpenAudioDevice(nil, 0, desired, obtained, 0) - if @dev == 0 - @conf.error("SDL2_OpenAudioDevice failed: #{ SDL2.GetError }") - abort - end - @buff = "".b - SDL2.PauseAudioDevice(@dev, 0) - end - - def dispose - SDL2.CloseAudioDevice(@dev) - SDL2.QuitSubSystem(SDL2::INIT_AUDIO) - end - - def tick(output) - buff = output.pack(@pack_format) - if defined?(SDL2.QueueAudio) - SDL2.QueueAudio(@dev, buff, buff.bytesize) - SDL2.ClearQueuedAudio(@dev) if SDL2.GetQueuedAudioSize(@dev) > @max_buff_size - else - @buff << buff - end - end - - # for SDL 2.0.3 or below in that SDL_QueueAudio is not available - def callback(_userdata, stream, stream_len) - buff_size = @buff.size - if stream_len > buff_size - # stream.clear # is it okay? - stream.write_string_length(@buff, buff_size) - @buff.clear - else - stream.write_string_length(@buff, stream_len) - stream_len = buff_size - @max_buff_size if buff_size - stream_len > @max_buff_size - @buff[0, stream_len] = "".freeze - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_input.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_input.rb deleted file mode 100644 index fad9593a..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_input.rb +++ /dev/null @@ -1,126 +0,0 @@ -require_relative "sdl2" - -module Optcarrot - # Input driver for SDL2 - class SDL2Input < Input - def init - SDL2.InitSubSystem(SDL2::INIT_JOYSTICK) - @event = FFI::MemoryPointer.new(:uint32, 16) - - @keyboard_repeat_offset = SDL2::KeyboardEvent.offset_of(:repeat) - @keyboard_sym_offset = SDL2::KeyboardEvent.offset_of(:sym) - @joy_which_offset = SDL2::JoyAxisEvent.offset_of(:which) - @joyaxis_axis_offset = SDL2::JoyAxisEvent.offset_of(:axis) - @joyaxis_value_offset = SDL2::JoyAxisEvent.offset_of(:value) - @joybutton_button_offset = SDL2::JoyButtonEvent.offset_of(:button) - - @joysticks = {} - SDL2.NumJoysticks.times do |i| - p SDL2.JoystickNameForIndex(i) - js = SDL2.JoystickOpen(i) - @joysticks[SDL2.JoystickInstanceID(js)] = js - # SDL2.JoystickNumAxes(js) - # SDL2.JoystickNumButtons(js) - end - - @key_mapping = DEFAULT_KEY_MAPPING - end - - def dispose - @joysticks.each_value do |js| - SDL2.JoystickClose(js) - end - @joysticks.clear - SDL2.QuitSubSystem(SDL2::INIT_JOYSTICK) - end - - DEFAULT_KEY_MAPPING = { - 0x20 => [:start, 0], # space - 0x0d => [:select, 0], # return - 0x7a => [:a, 0], # `Z' - 0x78 => [:b, 0], # `X' - 0x4000_004f => [:right, 0], - 0x4000_0050 => [:left, 0], - 0x4000_0051 => [:down, 0], - 0x4000_0052 => [:up, 0], - - # 57 => [:start, 1], # space - # 58 => [:select, 1], # return - # 25 => [:a, 1], # `Z' - # 23 => [:b, 1], # `X' - # 72 => [:right, 1], # right - # 71 => [:left, 1], # left - # 74 => [:down, 1], # down - # 73 => [:up, 1], # up - - 0x31 => [:screen_x1, nil], # `1' - 0x32 => [:screen_x2, nil], # `2' - 0x33 => [:screen_x3, nil], # `3' - 0x66 => [:screen_full, nil], # `f' - 0x71 => [:quit, nil], # `q' - } - - def joystick_move(axis, value, pads) - event(pads, value > 0x7000 ? :keydown : :keyup, axis ? :right : :down, 0) - event(pads, value < -0x7000 ? :keydown : :keyup, axis ? :left : :up, 0) - end - - def joystick_buttondown(button, pads) - case button - when 0 then pads.keydown(0, Pad::A) - when 1 then pads.keydown(0, Pad::B) - when 6 then pads.keydown(0, Pad::SELECT) - when 7 then pads.keydown(0, Pad::START) - end - end - - def joystick_buttonup(button, pads) - case button - when 0 then pads.keyup(0, Pad::A) - when 1 then pads.keyup(0, Pad::B) - when 6 then pads.keyup(0, Pad::SELECT) - when 7 then pads.keyup(0, Pad::START) - end - end - - def tick(_frame, pads) - while SDL2.PollEvent(@event) != 0 - case @event.read_int - - when 0x300, 0x301 # SDL_KEYDOWN, SDL_KEYUP - next if @event.get_uint8(@keyboard_repeat_offset) != 0 - key = @key_mapping[@event.get_int(@keyboard_sym_offset)] - event(pads, @event.read_int == 0x300 ? :keydown : :keyup, *key) if key - - when 0x600 # SDL_JOYAXISMOTION - which = @event.get_uint32(@joy_which_offset) - if which == 0 # XXX - axis = @event.get_uint8(@joyaxis_axis_offset) == 0 - value = @event.get_int16(@joyaxis_value_offset) - joystick_move(axis, value, pads) - end - - when 0x603 # SDL_JOYBUTTONDOWN - which = @event.get_uint32(@joy_which_offset) - joystick_buttondown(@event.get_uint8(@joybutton_button_offset), pads) - - when 0x604 # SDL_JOYBUTTONUP - which = @event.get_uint32(@joy_which_offset) - joystick_buttonup(@event.get_uint8(@joybutton_button_offset), pads) - - when 0x605 # SDL_JOYDEVICEADDED - which = @event.get_uint32(@joy_which_offset) - js = SDL2.JoystickOpen(which) - @joysticks[SDL2.JoystickInstanceID(js)] = js - - when 0x606 # SDL_JOYDEVICEREMOVED - which = @event.get_uint32(@joy_which_offset) - @joysticks.delete(which) - - when 0x100 # SDL_QUIT - exit - end - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_video.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_video.rb deleted file mode 100644 index 3e182fb9..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sdl2_video.rb +++ /dev/null @@ -1,88 +0,0 @@ -require_relative "sdl2" -require_relative "misc" - -module Optcarrot - # Video output driver for SDL2 - class SDL2Video < Video - def init - SDL2.InitSubSystem(SDL2::INIT_VIDEO) - @ticks_log = [0] * 11 - @buf = FFI::MemoryPointer.new(:uint32, WIDTH * HEIGHT) - @titles = (0..99).map {|n| "optcarrot (%d fps)" % n } - - @window = - SDL2.CreateWindow( - "optcarrot", - SDL2::WINDOWPOS_UNDEFINED, - SDL2::WINDOWPOS_UNDEFINED, - TV_WIDTH, HEIGHT, - SDL2::WINDOW_RESIZABLE - ) - @renderer = SDL2.CreateRenderer(@window, -1, 0) - SDL2.SetHint("SDL_RENDER_SCALE_QUALITY", "linear") - SDL2.RenderSetLogicalSize(@renderer, TV_WIDTH, HEIGHT) - @texture = SDL2.CreateTexture( - @renderer, - SDL2::PIXELFORMAT_8888, - SDL2::TEXTUREACCESS_STREAMING, - WIDTH, HEIGHT - ) - - width, height, pixels = Driver.icon_data - @icon = SDL2.CreateRGBSurfaceFrom(pixels, width, height, 32, width * 4, 0x0000ff, 0x00ff00, 0xff0000, 0xff000000) - SDL2.SetWindowIcon(@window, @icon) - - @palette = @palette_rgb.map do |r, g, b| - 0xff000000 | (r << 16) | (g << 8) | b - end - end - - def change_window_size(scale) - if scale - SDL2.SetWindowFullscreen(@window, 0) - SDL2.SetWindowSize(@window, TV_WIDTH * scale, HEIGHT * scale) - elsif SDL2.GetWindowFlags(@window) & SDL2::WINDOW_FULLSCREEN_DESKTOP != 0 - SDL2.SetWindowFullscreen(@window, 0) - else - SDL2.SetWindowFullscreen(@window, SDL2::WINDOW_FULLSCREEN_DESKTOP) - end - end - - def dispose - SDL2.FreeSurface(@icon) - SDL2.DestroyTexture(@texture) - SDL2.DestroyRenderer(@renderer) - SDL2.DestroyWindow(@window) - SDL2.QuitSubSystem(SDL2::INIT_VIDEO) - end - - def tick(colors) - prev_ticks = @ticks_log[0] - wait = prev_ticks + 1000 - SDL2.GetTicks * NES::FPS - @ticks_log.rotate!(1) - if wait > 0 - SDL2.Delay(wait / NES::FPS) - @ticks_log[0] = prev_ticks + 1000 - else - @ticks_log[0] = SDL2.GetTicks * NES::FPS - end - elapsed = (@ticks_log[0] - @ticks_log[1]) / (@ticks_log.size - 1) - fps = (NES::FPS * 1000 + elapsed / 2) / elapsed - fps = 99 if fps > 99 - - SDL2.SetWindowTitle(@window, @titles[fps]) - - Driver.cutoff_overscan(colors) - Driver.show_fps(colors, fps, @palette) if @conf.show_fps - - @buf.write_array_of_uint32(colors) - - SDL2.UpdateTexture(@texture, nil, @buf, WIDTH * 4) - SDL2.RenderClear(@renderer) - SDL2.RenderCopy(@renderer, @texture, nil, nil) - SDL2.RenderPresent(@renderer) - - fps - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml.rb deleted file mode 100644 index e216729c..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml.rb +++ /dev/null @@ -1,134 +0,0 @@ -require "ffi" - -module Optcarrot - # A minimal binding for SFML (CSFML) - module SFML - extend FFI::Library - ffi_lib \ - ["csfml-system", "csfml-system-2"], - ["csfml-window", "csfml-window-2"], - ["csfml-graphics", "csfml-graphics-2"], - ["csfml-audio", "csfml-audio-2"] - - # struct sfVector2u - class Vector2u < FFI::Struct - layout( - :x, :uint, - :y, :uint, - ) - end - - # struct sfVector2f - class Vector2f < FFI::Struct - layout( - :x, :float, - :y, :float, - ) - end - - # struct sfVideoMode - class VideoMode < FFI::Struct - layout( - :width, :int, - :height, :int, - :bits_per_pixel, :int, - ) - end - - # struct sfEvent - class Event < FFI::Struct - layout( - :type, :int, - ) - end - - # struct sfSizeEvent - class SizeEvent < FFI::Struct - layout( - :type, :int, - :width, :uint, - :height, :uint, - ) - end - - # struct sfKeyEvent - class KeyEvent < FFI::Struct - layout( - :type, :int, - :code, :int, - :alt, :int, - :control, :int, - :shift, :int, - :sym, :int, - ) - end - - # struct sfColor - class Color < FFI::Struct - layout( - :r, :uint8, - :g, :uint8, - :b, :uint8, - :a, :uint8, - ) - end - - # struct sfFloatRect - class FloatRect < FFI::Struct - layout( - :left, :float, - :top, :float, - :width, :float, - :height, :float, - ) - end - - # struct sfSoundStreamChunk - class SoundStreamChunk < FFI::Struct - layout( - :samples, :pointer, - :sample_count, :uint, - ) - end - - # rubocop:disable Naming/MethodName - # typedef sfSoundStreamGetDataCallback - def self.SoundStreamGetDataCallback(blk) - FFI::Function.new(:int, [SoundStreamChunk.by_ref, :pointer], blk, blocking: true) - end - # rubocop:enable Naming/MethodName - - attach_function(:sfClock_create, [], :pointer) - attach_function(:sfClock_destroy, [:pointer], :void) - attach_function(:sfClock_getElapsedTime, [:pointer], :int64) - attach_function(:sfClock_restart, [:pointer], :int64) - attach_function(:sfRenderWindow_create, [VideoMode.by_value, :pointer, :uint32, :pointer], :pointer) - attach_function(:sfRenderWindow_clear, [:pointer, Color.by_value], :void) - attach_function(:sfRenderWindow_drawSprite, [:pointer, :pointer, :pointer], :void, blocking: true) - attach_function(:sfRenderWindow_display, [:pointer], :void, blocking: true) - attach_function(:sfRenderWindow_close, [:pointer], :void) - attach_function(:sfRenderWindow_isOpen, [:pointer], :int) - attach_function(:sfRenderWindow_pollEvent, [:pointer, :pointer], :int) - attach_function(:sfRenderWindow_destroy, [:pointer], :void) - attach_function(:sfRenderWindow_setTitle, [:pointer, :pointer], :void) - attach_function(:sfRenderWindow_setSize, [:pointer, Vector2u.by_value], :void) - attach_function(:sfRenderWindow_setFramerateLimit, [:pointer, :int], :void) - attach_function(:sfRenderWindow_setKeyRepeatEnabled, [:pointer, :int], :void) - attach_function(:sfRenderWindow_setView, [:pointer, :pointer], :void) - attach_function(:sfRenderWindow_setIcon, [:pointer, :int, :int, :pointer], :void) - attach_function(:sfTexture_create, [:int, :int], :pointer) - attach_function(:sfTexture_updateFromPixels, [:pointer, :pointer, :int, :int, :int, :int], :void, blocking: true) - attach_function(:sfSprite_create, [], :pointer) - attach_function(:sfSprite_setTexture, [:pointer, :pointer, :int], :void) - attach_function(:sfView_create, [], :pointer) - attach_function(:sfView_createFromRect, [:pointer], :pointer) - attach_function(:sfView_destroy, [:pointer], :void) - attach_function(:sfView_reset, [:pointer, FloatRect.by_value], :void) - attach_function(:sfView_setCenter, [:pointer, Vector2f.by_value], :void) - attach_function(:sfView_setSize, [:pointer, Vector2f.by_value], :void) - attach_function(:sfSoundStream_create, [:pointer, :pointer, :uint, :uint, :pointer], :pointer) - attach_function(:sfSoundStream_destroy, [:pointer], :void, blocking: true) - attach_function(:sfSoundStream_play, [:pointer], :void) - attach_function(:sfSoundStream_stop, [:pointer], :void, blocking: true) - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_audio.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_audio.rb deleted file mode 100644 index 01c0a2b0..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_audio.rb +++ /dev/null @@ -1,46 +0,0 @@ -require_relative "sfml" - -module Optcarrot - # Audio output driver for SFML - class SFMLAudio < Audio - def init - @max_buff_size = @rate * @bits / 8 * BUFFER_IN_FRAME / NES::FPS - - # we need to prevent this callback object from GC - @callback = SFML.SoundStreamGetDataCallback(method(:callback)) - - @stream = SFML.sfSoundStream_create(@callback, nil, 1, @rate, nil) - SFML.sfSoundStream_play(@stream) - @buff = "".b - @cur_buff = FFI::MemoryPointer.new(:char, @max_buff_size + 1) - end - - def dispose - SFML.sfSoundStream_stop(@stream) - SFML.sfSoundStream_destroy(@stream) - end - - def tick(output) - @buff << output.pack("v*".freeze) - end - - # XXX: support 8bit (SFML supports only 16bit, so translation is required) - def callback(chunk, _userdata) - buff_size = @buff.size - if buff_size < @max_buff_size - @cur_buff.put_string(0, @buff) - else - @buff[0, buff_size - @max_buff_size] = "".freeze - @cur_buff.put_string(0, @buff) - buff_size = @max_buff_size - end - if buff_size == 0 - @cur_buff.clear - buff_size = @max_buff_size / BUFFER_IN_FRAME - end - chunk[:samples] = @cur_buff - chunk[:sample_count] = buff_size / 2 - return 1 - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_input.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_input.rb deleted file mode 100644 index 7c6b95d9..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_input.rb +++ /dev/null @@ -1,75 +0,0 @@ -require_relative "sfml" - -module Optcarrot - # Input driver for SFML - class SFMLInput < Input - def init - raise "SFMLInput must be used with SFMLVideo" unless @video.is_a?(SFMLVideo) - - @event = FFI::MemoryPointer.new(:uint32, 16) - @keyevent_code_offset = SFML::KeyEvent.offset_of(:code) - @sizeevent_width_offset = SFML::SizeEvent.offset_of(:width) - @sizeevent_height_offset = SFML::SizeEvent.offset_of(:height) - @key_mapping = DEFAULT_KEY_MAPPING - end - - def dispose - end - - DEFAULT_KEY_MAPPING = { - 57 => [:start, 0], # space - 58 => [:select, 0], # return - 25 => [:a, 0], # `Z' - 23 => [:b, 0], # `X' - 72 => [:right, 0], # right - 71 => [:left, 0], # left - 74 => [:down, 0], # down - 73 => [:up, 0], # up - - # 57 => [:start, 1], # space - # 58 => [:select, 1], # return - # 25 => [:a, 1], # `Z' - # 23 => [:b, 1], # `X' - # 72 => [:right, 1], # right - # 71 => [:left, 1], # left - # 74 => [:down, 1], # down - # 73 => [:up, 1], # up - - 27 => [:screen_x1, nil], # `1' - 28 => [:screen_x2, nil], # `2' - 29 => [:screen_x3, nil], # `3' - 5 => [:screen_full, nil], # `f' - 16 => [:quit, nil], # `q' - } - - def tick(_frame, pads) - SFML.sfRenderWindow_setKeyRepeatEnabled(@video.window, 0) - - while SFML.sfRenderWindow_pollEvent(@video.window, @event) != 0 - case @event.read_int - when 0 # EvtClosed - SFML.sfRenderWindow_close(@video.window) - exit # tmp - when 1 # EvtResized - w = @event.get_int(@sizeevent_width_offset) - h = @event.get_int(@sizeevent_height_offset) - @video.on_resize(w, h) - when 5 # EvtKeyPressed - event(pads, :keydown, *@key_mapping[@event.get_int(@keyevent_code_offset)]) - when 6 # EvtKeyReleased - event(pads, :keyup, *@key_mapping[@event.get_int(@keyevent_code_offset)]) - when 14 # sfEvtJoystickButtonPressed - # XXX - when 15 # sfEvtJoystickButtonReleased - # XXX - when 16 # sfEvtJoystickMoved - # XXX - when 17 # sfEvtJoystickConnected - # XXX - when 18 # sfEvtJoystickDisconnected - # XXX - end - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_video.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_video.rb deleted file mode 100644 index 8200e2b2..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sfml_video.rb +++ /dev/null @@ -1,84 +0,0 @@ -require_relative "sfml" -require_relative "misc" - -module Optcarrot - # Video output driver for SFML - class SFMLVideo < Video - def init - vm = SFML::VideoMode.new - vm[:width] = TV_WIDTH - vm[:height] = HEIGHT - vm[:bits_per_pixel] = 32 - @window = SFML.sfRenderWindow_create(vm, "optcarrot", 7, nil) - @texture = SFML.sfTexture_create(WIDTH, HEIGHT) - @sprite = SFML.sfSprite_create - SFML.sfRenderWindow_setFramerateLimit(@window, 60) - SFML.sfSprite_setTexture(@sprite, @texture, 1) - @color = SFML::Color.new - @color[:r] = @color[:g] = @color[:b] = 0 - @color[:a] = 255 - @buf = FFI::MemoryPointer.new(:uint8, WIDTH * HEIGHT * 4) - - width, height, pixels = Driver.icon_data - SFML.sfRenderWindow_setIcon(@window, width, height, pixels) - - @frame = 0 - @fps = 0 - @clock = SFML.sfClock_create - @vec2u = SFML::Vector2u.new - @vec2f = SFML::Vector2f.new - @view = SFML.sfView_create - - on_resize(TV_WIDTH, HEIGHT) - - @palette = @palette_rgb.map do |r, g, b| - 0xff000000 | (b << 16) | (g << 8) | r - end - end - - def change_window_size(scale) - if scale - @vec2u[:x] = TV_WIDTH * scale - @vec2u[:y] = HEIGHT * scale - SFML.sfRenderWindow_setSize(@window, @vec2u) - end - end - - def on_resize(w, h) - @vec2f[:x] = WIDTH / 2 - @vec2f[:y] = HEIGHT / 2 - SFML.sfView_setCenter(@view, @vec2f) - - ratio = w.to_f * WIDTH / TV_WIDTH / h - if WIDTH < ratio * HEIGHT - @vec2f[:x] = HEIGHT * ratio - @vec2f[:y] = HEIGHT - else - @vec2f[:x] = WIDTH - @vec2f[:y] = WIDTH / ratio - end - SFML.sfView_setSize(@view, @vec2f) - - SFML.sfRenderWindow_setView(@window, @view) - end - - attr_reader :window - - def tick(colors) - if SFML.sfClock_getElapsedTime(@clock) >= 1_000_000 - @fps = @frame - @frame = 0 - SFML.sfClock_restart(@clock) - end - @frame += 1 - - Driver.cutoff_overscan(colors) - Driver.show_fps(colors, @fps, @palette) if @conf.show_fps - @buf.write_array_of_uint32(colors) - SFML.sfTexture_updateFromPixels(@texture, @buf, WIDTH, HEIGHT, 0, 0) - SFML.sfRenderWindow_drawSprite(@window, @sprite, nil) - SFML.sfRenderWindow_display(@window) - @fps - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sixel_video.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sixel_video.rb deleted file mode 100644 index a428825d..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/sixel_video.rb +++ /dev/null @@ -1,63 +0,0 @@ -require_relative "misc" - -module Optcarrot - # Video output driver for Sixel (this is a joke feature) - class SixelVideo < Video - def init - super - @buff = "".b - @line = "".b - @seq_setup = "\e[H\ePq" - print "\e[2J" - - @palette, colors = Driver.quantize_colors(@palette_rgb) - - colors.each_with_index do |rgb, c| - @seq_setup << "#" << [c, 2, *rgb.map {|clr| clr * 100 / 255 }].join(";") - end - @seq_clr = (0..255).map {|c| "##{ c }" } - @seq_len = (0..256).map {|i| "!#{ i }" } - @seq_len[1] = "" - @seq_end = "\e\\" - end - - def tick(screen) - @buff.replace(@seq_setup) - 40.times do |y| - offset = y * 0x600 - six_lines = screen[offset, 0x600] - six_lines.uniq.each do |c| - prev_clr = nil - len = 1 - 256.times do |i| - clr = - (six_lines[i] == c ? 0x01 : 0) + - (six_lines[i + 0x100] == c ? 0x02 : 0) + - (six_lines[i + 0x200] == c ? 0x04 : 0) + - (six_lines[i + 0x300] == c ? 0x08 : 0) + - (six_lines[i + 0x400] == c ? 0x10 : 0) + - (six_lines[i + 0x500] == c ? 0x20 : 0) + 63 - if prev_clr == clr - len += 1 - elsif prev_clr - case len - when 1 then @line << prev_clr - when 2 then @line << prev_clr << prev_clr - else @line << @seq_len[len] << prev_clr - end - len = 1 - end - prev_clr = clr - end - if prev_clr != 63 || len != 256 - @buff << @seq_clr[c] << @line << @seq_len[len] << prev_clr << 36 # $ - @line.clear - end - end - @buff << 45 # - - end - print @buff << @seq_end - super - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/term_input.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/term_input.rb deleted file mode 100644 index 54e7c3b6..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/term_input.rb +++ /dev/null @@ -1,52 +0,0 @@ -require "io/console" -require "io/wait" - -module Optcarrot - # Input driver for terminal (this is a joke feature) - class TermInput < Input - def init - $stdin.raw! - $stdin.getc if $stdin.ready? - @escape = false - @ticks = { start: 0, select: 0, a: 0, b: 0, right: 0, left: 0, down: 0, up: 0 } - end - - def dispose - $stdin.cooked! - end - - def keydown(pads, code, frame) - event(pads, :keydown, code, 0) - @ticks[code] = frame - end - - def tick(frame, pads) - while $stdin.ready? - ch = $stdin.getbyte - if @escape - @escape = false - case ch - when 0x5b then @escape = true - when 0x41 then keydown(pads, :up, frame) - when 0x42 then keydown(pads, :down, frame) - when 0x43 then keydown(pads, :right, frame) - when 0x44 then keydown(pads, :left, frame) - end - else - case ch - when 0x1b then @escape = true - when 0x58, 0x78 then keydown(pads, :a, frame) - when 0x5a, 0x7a then keydown(pads, :b, frame) - when 0x0d then keydown(pads, :select, frame) - when 0x20 then keydown(pads, :start, frame) - when 0x51, 0x71 then exit - end - end - end - - @ticks.each do |code, prev_frame| - event(pads, :keyup, code, 0) if prev_frame + 5 < frame - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/wav_audio.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/driver/wav_audio.rb deleted file mode 100644 index 430ca7a2..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/driver/wav_audio.rb +++ /dev/null @@ -1,21 +0,0 @@ -module Optcarrot - # Audio output driver saving a WAV file - class WAVAudio < Audio - def init - @buff = [] - end - - def dispose - buff = @buff.pack(@pack_format) - wav = [ - "RIFF", 44 + buff.bytesize, "WAVE", "fmt ", 16, 1, 1, - @rate, @rate * @bits / 8, @bits / 8, @bits, "data", buff.bytesize, buff - ].pack("A4VA4A4VvvVVvvA4VA*") - File.binwrite("audio.wav", wav) - end - - def tick(output) - @buff.concat output - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/cnrom.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/cnrom.rb deleted file mode 100644 index 4c916ef1..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/cnrom.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Optcarrot - # CNROM mapper: http://wiki.nesdev.com/w/index.php/CNROM - class CNROM < ROM - MAPPER_DB[0x03] = self - - def reset - @cpu.add_mappings(0x8000..0xffff, @prg_ref, @chr_ram ? nil : method(:poke_8000)) - end - - def poke_8000(_addr, data) - @chr_ref.replace(@chr_banks[data & 3]) - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/mmc1.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/mmc1.rb deleted file mode 100644 index e70549e7..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/mmc1.rb +++ /dev/null @@ -1,105 +0,0 @@ -module Optcarrot - # MMC1 mapper: http://wiki.nesdev.com/w/index.php/MMC1 - class MMC1 < ROM - MAPPER_DB[0x01] = self - - NMT_MODE = [:first, :second, :vertical, :horizontal] - PRG_MODE = [:conseq, :conseq, :fix_first, :fix_last] - CHR_MODE = [:conseq, :noconseq] - - def init - @nmt_mode = @prg_mode = @chr_mode = nil - @prg_bank = @chr_bank_0 = @chr_bank_1 = 0 - end - - def reset - @shift = @shift_count = 0 - - @chr_banks = @chr_banks.flatten.each_slice(0x1000).to_a - - @wrk_readable = @wrk_writable = true - @cpu.add_mappings(0x6000..0x7fff, method(:peek_6000), method(:poke_6000)) - @cpu.add_mappings(0x8000..0xffff, @prg_ref, method(:poke_prg)) - - update_nmt(:horizontal) - update_prg(:fix_last, 0, 0) - update_chr(:conseq, 0, 0) - end - - def poke_prg(addr, val) - if val[7] == 1 - @shift = @shift_count = 0 - else - @shift |= val[0] << @shift_count - @shift_count += 1 - if @shift_count == 0x05 - case (addr >> 13) & 0x3 - when 0 # control - nmt_mode = NMT_MODE[@shift & 3] - prg_mode = PRG_MODE[@shift >> 2 & 3] - chr_mode = CHR_MODE[@shift >> 4 & 1] - update_nmt(nmt_mode) - update_prg(prg_mode, @prg_bank, @chr_bank_0) - update_chr(chr_mode, @chr_bank_0, @chr_bank_1) - when 1 # change chr_bank_0 - # update_prg might modify @chr_bank_0 and prevent updating chr bank, - # so keep current value. - bak_chr_bank_0 = @chr_bank_0 - update_prg(@prg_mode, @prg_bank, @shift) - @chr_bank_0 = bak_chr_bank_0 - update_chr(@chr_mode, @shift, @chr_bank_1) - when 2 # change chr_bank_1 - update_chr(@chr_mode, @chr_bank_0, @shift) - when 3 # change png_bank - update_prg(@prg_mode, @shift, @chr_bank_0) - end - @shift = @shift_count = 0 - end - end - end - - def update_nmt(nmt_mode) - return if @nmt_mode == nmt_mode - @nmt_mode = nmt_mode - @ppu.nametables = @nmt_mode - end - - def update_prg(prg_mode, prg_bank, chr_bank_0) - return if prg_mode == @prg_mode && prg_bank == @prg_bank && chr_bank_0 == @chr_bank_0 - @prg_mode, @prg_bank, @chr_bank_0 = prg_mode, prg_bank, chr_bank_0 - - high_bit = chr_bank_0 & (0x10 & (@prg_banks.size - 1)) - prg_bank_ex = ((@prg_bank & 0x0f) | high_bit) & (@prg_banks.size - 1) - case @prg_mode - when :conseq - lower = prg_bank_ex & ~1 - upper = lower + 1 - when :fix_first - lower = 0 - upper = prg_bank_ex - when :fix_last - lower = prg_bank_ex - upper = ((@prg_banks.size - 1) & 0x0f) | high_bit - end - @prg_ref[0x8000, 0x4000] = @prg_banks[lower] - @prg_ref[0xc000, 0x4000] = @prg_banks[upper] - end - - def update_chr(chr_mode, chr_bank_0, chr_bank_1) - return if chr_mode == @chr_mode && chr_bank_0 == @chr_bank_0 && chr_bank_1 == @chr_bank_1 - @chr_mode, @chr_bank_0, @chr_bank_1 = chr_mode, chr_bank_0, chr_bank_1 - return if @chr_ram - - @ppu.update(0) - if @chr_mode == :conseq - lower = @chr_bank_0 & 0x1e - upper = lower + 1 - else - lower = @chr_bank_0 - upper = @chr_bank_1 - end - @chr_ref[0x0000, 0x1000] = @chr_banks[lower] - @chr_ref[0x1000, 0x1000] = @chr_banks[upper] - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/mmc3.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/mmc3.rb deleted file mode 100644 index 3fadfa91..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/mmc3.rb +++ /dev/null @@ -1,153 +0,0 @@ -module Optcarrot - # MMC3 mapper: http://wiki.nesdev.com/w/index.php/MMC3 - class MMC3 < ROM - MAPPER_DB[0x04] = self - - def init(rev = :B) # rev = :A or :B or :C - @persistant = rev != :A - - @prg_banks = @prg_banks.flatten.each_slice(0x2000).to_a - @prg_bank_swap = false - - @chr_banks = @chr_banks.flatten.each_slice(0x0400).to_a - @chr_bank_mapping = [nil] * 8 - @chr_bank_swap = false - end - - def reset - @wrk_readable = true - @wrk_writable = false - - poke_a000 = @mirroring != :FourScreen ? method(:poke_a000) : nil - @cpu.add_mappings(0x6000..0x7fff, method(:peek_6000), method(:poke_6000)) - @cpu.add_mappings(0x8000.step(0x9fff, 2), @prg_ref, method(:poke_8000)) - @cpu.add_mappings(0x8001.step(0x9fff, 2), @prg_ref, method(:poke_8001)) - @cpu.add_mappings(0xa000.step(0xbfff, 2), @prg_ref, poke_a000) - @cpu.add_mappings(0xa001.step(0xbfff, 2), @prg_ref, method(:poke_a001)) - @cpu.add_mappings(0xc000.step(0xdfff, 2), @prg_ref, method(:poke_c000)) - @cpu.add_mappings(0xc001.step(0xdfff, 2), @prg_ref, method(:poke_c001)) - @cpu.add_mappings(0xe000.step(0xffff, 2), @prg_ref, method(:poke_e000)) - @cpu.add_mappings(0xe001.step(0xffff, 2), @prg_ref, method(:poke_e001)) - - update_prg(0x8000, 0) - update_prg(0xa000, 1) - update_prg(0xc000, -2) - update_prg(0xe000, -1) - 8.times {|i| update_chr(i * 0x400, i) } - - @clock = 0 - @hold = PPU::RP2C02_CC * 16 - @ppu.monitor_a12_rising_edge(self) - @cpu.ppu_sync = true - - @count = 0 - @latch = 0 - @reload = false - @enabled = false - end - - # prg_bank_swap = F T - # 0x8000..0x9fff: 0 2 - # 0xa000..0xbfff: 1 1 - # 0xc000..0xdfff: 2 0 - # 0xe000..0xffff: 3 3 - def update_prg(addr, bank) - bank %= @prg_banks.size - addr ^= 0x4000 if @prg_bank_swap && addr[13] == 0 - @prg_ref[addr, 0x2000] = @prg_banks[bank] - end - - def update_chr(addr, bank) - return if @chr_ram - idx = addr / 0x400 - bank %= @chr_banks.size - return if @chr_bank_mapping[idx] == bank - addr ^= 0x1000 if @chr_bank_swap - @ppu.update(0) - @chr_ref[addr, 0x400] = @chr_banks[bank] - @chr_bank_mapping[idx] = bank - end - - def poke_8000(_addr, data) - @reg_select = data & 7 - prg_bank_swap = data[6] == 1 - chr_bank_swap = data[7] == 1 - - if prg_bank_swap != @prg_bank_swap - @prg_bank_swap = prg_bank_swap - @prg_ref[0x8000, 0x2000], @prg_ref[0xc000, 0x2000] = @prg_ref[0xc000, 0x2000], @prg_ref[0x8000, 0x2000] - end - - if chr_bank_swap != @chr_bank_swap - @chr_bank_swap = chr_bank_swap - unless @chr_ram - @ppu.update(0) - @chr_ref.rotate!(0x1000) - @chr_bank_mapping.rotate!(4) - end - end - end - - def poke_8001(_addr, data) - if @reg_select < 6 - if @reg_select < 2 - update_chr(@reg_select * 0x0800, data & 0xfe) - update_chr(@reg_select * 0x0800 + 0x0400, data | 0x01) - else - update_chr((@reg_select - 2) * 0x0400 + 0x1000, data) - end - else - update_prg((@reg_select - 6) * 0x2000 + 0x8000, data & 0x3f) - end - end - - def poke_a000(_addr, data) - @ppu.nametables = data[0] == 1 ? :horizontal : :vertical - end - - def poke_a001(_addr, data) - @wrk_readable = data[7] == 1 - @wrk_writable = data[6] == 0 && @wrk_readable - end - - def poke_c000(_addr, data) - @ppu.update(0) - @latch = data - end - - def poke_c001(_addr, _data) - @ppu.update(0) - @reload = true - end - - def poke_e000(_addr, _data) - @ppu.update(0) - @enabled = false - @cpu.clear_irq(CPU::IRQ_EXT) - end - - def poke_e001(_addr, _data) - @ppu.update(0) - @enabled = true - end - - def vsync - @clock = @clock > @cpu.next_frame_clock ? @clock - @cpu.next_frame_clock : 0 - end - - def a12_signaled(cycle) - clk, @clock = @clock, cycle + @hold - return if cycle < clk - flag = @persistant || @count > 0 - if @reload - @reload = false - @count = @latch - elsif @count == 0 - @count = @latch - else - @count -= 1 - end - @cpu.do_irq(CPU::IRQ_EXT, cycle) if flag && @count == 0 && @enabled - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/uxrom.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/uxrom.rb deleted file mode 100644 index 2473c2d2..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/mapper/uxrom.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Optcarrot - # UxROM mapper: http://wiki.nesdev.com/w/index.php/UxROM - class UxROM < ROM - MAPPER_DB[0x02] = self - - def reset - @cpu.add_mappings(0x8000..0xffff, @prg_ref, method(:poke_8000)) - end - - def poke_8000(_addr, data) - @prg_ref[0x8000, 0x4000] = @prg_banks[data & 7] - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/nes.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/nes.rb deleted file mode 100644 index 30ad44ba..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/nes.rb +++ /dev/null @@ -1,105 +0,0 @@ -module Optcarrot - FOREVER_CLOCK = 0xffffffff - RP2A03_CC = 12 - - # NES emulation main - class NES - FPS = 60 - - def initialize(conf = ARGV) - @conf = Config.new(conf) - - @video, @audio, @input = Driver.load(@conf) - - @cpu = CPU.new(@conf) - @apu = @cpu.apu = APU.new(@conf, @cpu, *@audio.spec) - @ppu = @cpu.ppu = PPU.new(@conf, @cpu, @video.palette) - @rom = ROM.load(@conf, @cpu, @ppu) - @pads = Pads.new(@conf, @cpu, @apu) - - @frame = 0 - @frame_target = @conf.frames == 0 ? nil : @conf.frames - @fps_history = [] if save_fps_history? - end - - def inspect - "#<#{ self.class }>" - end - - attr_reader :fps, :video, :audio, :input, :cpu, :ppu, :apu - - def reset - @cpu.reset - @apu.reset - @ppu.reset - @rom.reset - @pads.reset - @cpu.boot - @rom.load_battery - end - - def step - @ppu.setup_frame - @cpu.run - @ppu.vsync - @apu.vsync - @cpu.vsync - @rom.vsync - - @input.tick(@frame, @pads) - @fps = @video.tick(@ppu.output_pixels) - @fps_history << @fps if save_fps_history? - @audio.tick(@apu.output) - - @frame += 1 - @conf.info("frame #{ @frame }") if @conf.loglevel >= 2 - end - - def dispose - if @fps - @conf.info("fps: %.2f (in the last 10 frames)" % @fps) - if @conf.print_fps_history - puts "frame,fps-history" - @fps_history.each_with_index {|fps, frame| puts "#{ frame },#{ fps }" } - end - if @conf.print_p95fps - puts "p95 fps: #{ @fps_history.sort[(@fps_history.length * 0.05).floor] }" - end - puts "fps: #{ @fps }" if @conf.print_fps - end - if @conf.print_video_checksum && @video.instance_of?(Video) - puts "checksum: #{ @ppu.output_pixels.pack("C*").sum }" - end - @video.dispose - @audio.dispose - @input.dispose - @rom.save_battery - @ppu.dispose - end - - def run - reset - - if @conf.stackprof_mode - require "stackprof" - out = @conf.stackprof_output.sub("MODE", @conf.stackprof_mode) - StackProf.start(mode: @conf.stackprof_mode.to_sym, out: out, raw: true) - end - - step until @frame == @frame_target - - if @conf.stackprof_mode - StackProf.stop - StackProf.results - end - ensure - dispose - end - - private - - def save_fps_history? - @conf.print_fps_history || @conf.print_p95fps - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/opt.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/opt.rb deleted file mode 100644 index 7c95595c..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/opt.rb +++ /dev/null @@ -1,168 +0,0 @@ -module Optcarrot - # dirty methods manipulating and generating methods... - module CodeOptimizationHelper - def initialize(loglevel, enabled_opts) - @loglevel = loglevel - options = self.class::OPTIONS - opts = {} - enabled_opts ||= [:all] - default = - (enabled_opts == [:all] || enabled_opts != [] && enabled_opts.all? {|opt| opt.to_s.start_with?("-") }) - options.each {|opt| opts[opt] = default } - (enabled_opts - [:none, :all]).each do |opt| - val = true - if opt.to_s.start_with?("-") - opt = opt.to_s[1..-1].to_sym - val = false - end - raise "unknown optimization: `#{ opt }'" unless options.include?(opt) - opts[opt] = val - end - options.each {|opt| instance_variable_set(:"@#{ opt }", opts[opt]) } - end - - def depends(opt, depended_opt) - if instance_variable_get(:"@#{ opt }") && !instance_variable_get(:"@#{ depended_opt }") - raise "`#{ opt }' depends upon `#{ depended_opt }'" - end - end - - def gen(*codes) - codes.map {|code| code.to_s.chomp }.join("\n") + "\n" - end - - # change indent - def indent(i, code) - if i > 0 - code.gsub(/^(.+)$/) { " " * i + $1 } - elsif i < 0 - code.gsub(/^ {#{ -i }}/, "") - else - code - end - end - - # generate a branch - def branch(cond, code1, code2) - gen( - "if #{ cond }", - indent(2, code1), - "else", - indent(2, code2), - "end", - ) - end - - MethodDef = Struct.new(:params, :body) - - METHOD_DEFINITIONS_RE = / - ^(\ +)def\s+(\w+)(?:\((.*)\))?\n - ^((?:\1\ +.*\n|\n)*) - ^\1end$ - /x - # extract all method definitions - def parse_method_definitions(file) - src = File.read(file) - mdefs = {} - src.scan(METHOD_DEFINITIONS_RE) do |indent, meth, params, body| - body = indent(-indent.size - 2, body) - - # noramlize: break `when ... then` - body = body.gsub(/^( *)when +(.*?) +then +(.*)/) { $1 + "when #{ $2 }\n" + $1 + " " + $3 } - - # normalize: return unless - body = "if " + $1 + indent(2, $') + "end\n" if body =~ /\Areturn unless (.*)/ - - # normalize: if modifier -> if statement - nil while body.gsub!(/^( *)((?!#)\S.*) ((?:if|unless) .*\n)/) { indent($1.size, gen($3, " " + $2, "end")) } - - mdefs[meth.to_sym] = MethodDef[params ? params.split(", ") : nil, body] - end - mdefs - end - - # inline method calls with no arguments - def expand_methods(code, mdefs, meths = mdefs.keys) - code.gsub(/^( *)\b(#{ meths * "|" })\b(?:\((.*?)\))?\n/) do - indent, meth, args = $1, $2, $3 - body = mdefs[meth.to_sym] - body = body.body if body.is_a?(MethodDef) - if args - mdefs[meth.to_sym].params.zip(args.split(", ")) do |param, arg| - body = replace_var(body, param, arg) - end - end - indent(indent.size, body) - end - end - - def expand_inline_methods(code, meth, mdef) - code.gsub(/\b#{ meth }\b(?:\(((?:@?\w+, )*@?\w+)\))?/) do - args = $1 - b = "(#{ mdef.body.chomp.gsub(/ *#.*/, "").gsub("\n", "; ") })" - if args - mdef.params.zip(args.split(", ")) do |param, arg| - b = replace_var(b, param, arg) - end - end - b - end - end - - def replace_var(code, var, bool) - re = var.start_with?("@") ? /#{ var }\b/ : /\b#{ var }\b/ - code.gsub(re) { bool } - end - - def replace_cond_var(code, var, bool) - code.gsub(/(if|unless)\s#{ var }\b/) { $1 + " " + bool } - end - - TRIVIAL_BRANCH_RE = / - ^(\ *)(if|unless)\ (true|false)\n - ^((?:\1\ +.*\n|\n)*) - (?: - \1else\n - ((?:\1\ +.*\n|\n)*) - )? - ^\1end\n - /x - # remove "if true" or "if false" - def remove_trivial_branches(code) - code = code.dup - nil while - code.gsub!(TRIVIAL_BRANCH_RE) do - if ($2 == "if") == ($3 == "true") - indent(-2, $4) - else - $5 ? indent(-2, $5) : "" - end - end - code - end - - # replace instance variables with temporal local variables - # CAUTION: the instance variable must not be accessed out of CPU#run - def localize_instance_variables(code, ivars = code.scan(/@\w+/).uniq.sort) - ivars = ivars.map {|ivar| ivar.to_s[1..-1] } - - inits, finals = [], [] - ivars.each do |ivar| - lvar = "__#{ ivar }__" - inits << "#{ lvar } = @#{ ivar }" - finals << "@#{ ivar } = #{ lvar }" - end - - code = code.gsub(/@(#{ ivars * "|" })\b/) { "__#{ $1 }__" } - - gen( - "begin", - indent(2, inits.join("\n")), - indent(2, code), - "ensure", - indent(2, finals.join("\n")), - "end", - ) - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/pad.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/pad.rb deleted file mode 100644 index eb5def53..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/pad.rb +++ /dev/null @@ -1,92 +0,0 @@ -module Optcarrot - # Pad pair implementation (NES has two built-in game pad.) - class Pads - def inspect - "#<#{ self.class }>" - end - - ########################################################################### - # initialization - - def initialize(conf, cpu, apu) - @conf = conf - @cpu = cpu - @apu = apu - @pads = [Pad.new, Pad.new] - end - - def reset - @cpu.add_mappings(0x4016, method(:peek_401x), method(:poke_4016)) - @cpu.add_mappings(0x4017, method(:peek_401x), @apu.method(:poke_4017)) # delegate 4017H to APU - @pads[0].reset - @pads[1].reset - end - - def peek_401x(addr) - @cpu.update - @pads[addr - 0x4016].peek | 0x40 - end - - def poke_4016(_addr, data) - @pads[0].poke(data) - @pads[1].poke(data) - end - - ########################################################################### - # APIs - - def keydown(pad, btn) - @pads[pad].buttons |= 1 << btn - end - - def keyup(pad, btn) - @pads[pad].buttons &= ~(1 << btn) - end - end - - ########################################################################### - # each pad - class Pad - A = 0 - B = 1 - SELECT = 2 - START = 3 - UP = 4 - DOWN = 5 - LEFT = 6 - RIGHT = 7 - - def initialize - reset - end - - def reset - @strobe = false - @buttons = @stream = 0 - end - - def poke(data) - prev = @strobe - @strobe = data[0] == 1 - @stream = ((poll_state << 1) ^ -512) if prev && !@strobe - end - - def peek - return poll_state & 1 if @strobe - @stream >>= 1 - return @stream[0] - end - - def poll_state - state = @buttons - - # prohibit impossible simultaneous keydown (right and left, up and down) - state &= 0b11001111 if state & 0b00110000 == 0b00110000 - state &= 0b00111111 if state & 0b11000000 == 0b11000000 - - state - end - - attr_accessor :buttons - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/palette.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/palette.rb deleted file mode 100644 index 2de54d95..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/palette.rb +++ /dev/null @@ -1,65 +0,0 @@ -module Optcarrot - # NES palette generators - module Palette - module_function - - # I don't know where this palette definition came from, but many emulators are using this palette - def defacto_palette - [ - [1.00, 1.00, 1.00], # default - [1.00, 0.80, 0.81], # emphasize R - [0.78, 0.94, 0.66], # emphasize G - [0.79, 0.77, 0.63], # emphasize RG - [0.82, 0.83, 1.12], # emphasize B - [0.81, 0.71, 0.87], # emphasize RB - [0.68, 0.79, 0.79], # emphasize GB - [0.70, 0.70, 0.70], # emphasize RGB - ].flat_map do |rf, gf, bf| - # RGB default palette (I don't know where this palette came from) - [ - 0x666666, 0x002a88, 0x1412a7, 0x3b00a4, 0x5c007e, 0x6e0040, 0x6c0600, 0x561d00, - 0x333500, 0x0b4800, 0x005200, 0x004f08, 0x00404d, 0x000000, 0x000000, 0x000000, - 0xadadad, 0x155fd9, 0x4240ff, 0x7527fe, 0xa01acc, 0xb71e7b, 0xb53120, 0x994e00, - 0x6b6d00, 0x388700, 0x0c9300, 0x008f32, 0x007c8d, 0x000000, 0x000000, 0x000000, - 0xfffeff, 0x64b0ff, 0x9290ff, 0xc676ff, 0xf36aff, 0xfe6ecc, 0xfe8170, 0xea9e22, - 0xbcbe00, 0x88d800, 0x5ce430, 0x45e082, 0x48cdde, 0x4f4f4f, 0x000000, 0x000000, - 0xfffeff, 0xc0dfff, 0xd3d2ff, 0xe8c8ff, 0xfbc2ff, 0xfec4ea, 0xfeccc5, 0xf7d8a5, - 0xe4e594, 0xcfef96, 0xbdf4ab, 0xb3f3cc, 0xb5ebf2, 0xb8b8b8, 0x000000, 0x000000, - ].map do |rgb| - r = [((rgb >> 16 & 0xff) * rf).floor, 0xff].min - g = [((rgb >> 8 & 0xff) * gf).floor, 0xff].min - b = [((rgb >> 0 & 0xff) * bf).floor, 0xff].min - [r, g, b] - end - end - end - - # Nestopia generates a palette systematically (cool!), but it is not compatible with nes-tests-rom - def nestopia_palette - (0..511).map do |n| - tint, level, color = n >> 6 & 7, n >> 4 & 3, n & 0x0f - level0, level1 = [[-0.12, 0.40], [0.00, 0.68], [0.31, 1.00], [0.72, 1.00]][level] - level0 = level1 if color == 0x00 - level1 = level0 if color == 0x0d - level0 = level1 = 0 if color >= 0x0e - y = (level1 + level0) * 0.5 - s = (level1 - level0) * 0.5 - iq = Complex.polar(s, Math::PI / 6 * (color - 3)) - if tint != 0 && color <= 0x0d - if tint == 7 - y = (y * 0.79399 - 0.0782838) * 1.13 - else - level1 = (level1 * (1 - 0.79399) + 0.0782838) * 0.5 - y -= level1 * 0.5 - y -= level1 *= 0.6 if [3, 5, 6].include?(tint) - iq += Complex.polar(level1, Math::PI / 12 * ([0, 6, 10, 8, 2, 4, 0, 0][tint] * 2 - 7)) - end - end - [[105, 0.570], [251, 0.351], [15, 1.015]].map do |angle, gain| - clr = y + (Complex.polar(gain * 2, (angle - 33) * Math::PI / 180) * iq.conjugate).real - [0, (clr * 255).round, 255].sort[1] - end - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/ppu.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/ppu.rb deleted file mode 100644 index a9151116..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/ppu.rb +++ /dev/null @@ -1,1470 +0,0 @@ -require_relative "opt" - -module Optcarrot - # PPU implementation (video output) - class PPU - # clock/timing constants (stolen from Nestopia) - RP2C02_CC = 4 - RP2C02_HACTIVE = RP2C02_CC * 256 - RP2C02_HBLANK = RP2C02_CC * 85 - RP2C02_HSYNC = RP2C02_HACTIVE + RP2C02_HBLANK - RP2C02_VACTIVE = 240 - RP2C02_VSLEEP = 1 - RP2C02_VINT = 20 - RP2C02_VDUMMY = 1 - RP2C02_VBLANK = RP2C02_VSLEEP + RP2C02_VINT + RP2C02_VDUMMY - RP2C02_VSYNC = RP2C02_VACTIVE + RP2C02_VBLANK - RP2C02_HVSYNCBOOT = RP2C02_VACTIVE * RP2C02_HSYNC + RP2C02_CC * 312 - RP2C02_HVINT = RP2C02_VINT * RP2C02_HSYNC - RP2C02_HVSYNC_0 = RP2C02_VSYNC * RP2C02_HSYNC - RP2C02_HVSYNC_1 = RP2C02_VSYNC * RP2C02_HSYNC - RP2C02_CC - - # special scanlines - SCANLINE_HDUMMY = -1 # pre-render scanline - SCANLINE_VBLANK = 240 # post-render scanline - - # special horizontal clocks - HCLOCK_DUMMY = 341 - HCLOCK_VBLANK_0 = 681 - HCLOCK_VBLANK_1 = 682 - HCLOCK_VBLANK_2 = 684 - HCLOCK_BOOT = 685 - DUMMY_FRAME = [RP2C02_HVINT / RP2C02_CC - HCLOCK_DUMMY, RP2C02_HVINT, RP2C02_HVSYNC_0].freeze - BOOT_FRAME = [RP2C02_HVSYNCBOOT / RP2C02_CC - HCLOCK_BOOT, RP2C02_HVSYNCBOOT, RP2C02_HVSYNCBOOT].freeze - - # constants related to OAM (sprite) - SP_PIXEL_POSITIONS = { - 0 => [3, 7, 2, 6, 1, 5, 0, 4], # normal - 1 => [4, 0, 5, 1, 6, 2, 7, 3], # flip - } - - # A look-up table mapping: (two pattern bytes * attr) -> eight pixels - # TILE_LUT[attr][high_byte * 0x100 + low_byte] = [pixels] * 8 - TILE_LUT = [0x0, 0x4, 0x8, 0xc].map do |attr| - (0..7).map do |j| - (0...0x10000).map do |i| - clr = i[15 - j] * 2 + i[7 - j] - clr != 0 ? attr | clr : 0 - end - end.transpose - # Super dirty hack: This Array#transpose reduces page-faults. - # It might generate cache-friendly memory layout... - end - Ractor.make_shareable(TILE_LUT) - - def inspect - "#<#{ self.class }>" - end - - ########################################################################### - # initialization - - def initialize(conf, cpu, palette) - @conf = conf - @cpu = cpu - @palette = palette - - if @conf.load_ppu - eval(File.read(@conf.load_ppu)) - elsif @conf.opt_ppu - eval(OptimizedCodeBuilder.new(@conf.loglevel, @conf.opt_ppu).build, nil, "(generated PPU core)") - end - - @nmt_mem = [[0xff] * 0x400, [0xff] * 0x400] - @nmt_ref = [0, 1, 0, 1].map {|i| @nmt_mem[i] } - - @output_pixels = [] - @output_color = [@palette[0]] * 0x20 # palette size is 0x20 - - reset(mapping: false) - setup_lut - end - - def reset(opt = {}) - if opt.fetch(:mapping, true) - # setup mapped memory - @cpu.add_mappings(0x2000.step(0x3fff, 8), method(:peek_2xxx), method(:poke_2000)) - @cpu.add_mappings(0x2001.step(0x3fff, 8), method(:peek_2xxx), method(:poke_2001)) - @cpu.add_mappings(0x2002.step(0x3fff, 8), method(:peek_2002), method(:poke_2xxx)) - @cpu.add_mappings(0x2003.step(0x3fff, 8), method(:peek_2xxx), method(:poke_2003)) - @cpu.add_mappings(0x2004.step(0x3fff, 8), method(:peek_2004), method(:poke_2004)) - @cpu.add_mappings(0x2005.step(0x3fff, 8), method(:peek_2xxx), method(:poke_2005)) - @cpu.add_mappings(0x2006.step(0x3fff, 8), method(:peek_2xxx), method(:poke_2006)) - @cpu.add_mappings(0x2007.step(0x3fff, 8), method(:peek_2007), method(:poke_2007)) - @cpu.add_mappings(0x3000, method(:peek_3000), method(:poke_2000)) - @cpu.add_mappings(0x4014, method(:peek_4014), method(:poke_4014)) - end - - @palette_ram = [ - 0x3f, 0x01, 0x00, 0x01, 0x00, 0x02, 0x02, 0x0d, - 0x08, 0x10, 0x08, 0x24, 0x00, 0x00, 0x04, 0x2c, - 0x09, 0x01, 0x34, 0x03, 0x00, 0x04, 0x00, 0x14, - 0x08, 0x3a, 0x00, 0x02, 0x00, 0x20, 0x2c, 0x08, - ] - @coloring = 0x3f # not monochrome - @emphasis = 0 - update_output_color - - @run = true - - # clock management - @hclk = HCLOCK_BOOT - @vclk = 0 - @hclk_target = FOREVER_CLOCK - - # CPU-PPU interface - @io_latch = 0 - @io_buffer = 0xe8 # garbage - - @regs_oam = 0 - - # misc - @vram_addr_inc = 1 # 1 or 32 - @need_nmi = false - @pattern_end = 0x0ff0 - @any_show = false # == @bg_show || @sp_show - @sp_overflow = false - @sp_zero_hit = false - @vblanking = @vblank = false - - # PPU-nametable interface - @io_addr = 0 - @io_pattern = 0 - - @a12_monitor = nil - @a12_state = nil - - # the current scanline - @odd_frame = false - @scanline = SCANLINE_VBLANK - - # scroll state - @scroll_toggle = false - @scroll_latch = 0 - @scroll_xfine = 0 - @scroll_addr_0_4 = @scroll_addr_5_14 = 0 - @name_io_addr = 0x2000 # == (@scroll_addr_0_4 | @scroll_addr_5_14) & 0x0fff | 0x2000 - - ### BG-sprite state - @bg_enabled = false - @bg_show = false - @bg_show_edge = false - @bg_pixels = [0] * 16 - @bg_pattern_base = 0 # == 0 or 0x1000 - @bg_pattern_base_15 = 0 # == @bg_pattern_base[12] << 15 - @bg_pattern = 0 - @bg_pattern_lut = TILE_LUT[0] - @bg_pattern_lut_fetched = TILE_LUT[0] - # invariant: - # @bg_pattern_lut_fetched == TILE_LUT[ - # @nmt_ref[@io_addr >> 10 & 3][@io_addr & 0x03ff] >> - # ((@scroll_addr_0_4 & 0x2) | (@scroll_addr_5_14[6] * 0x4)) & 3 - # ] - - ### OAM-sprite state - @sp_enabled = false - @sp_active = false # == @sp_visible && @sp_enabled - @sp_show = false - @sp_show_edge = false - - # for CPU-PPU interface - @sp_base = 0 - @sp_height = 8 - - # for OAM fetcher - @sp_phase = 0 - @sp_ram = [0xff] * 0x100 # ram size is 0x100, 0xff is a OAM garbage - @sp_index = 0 - @sp_addr = 0 - @sp_latch = 0 - - # for internal state - # 8 sprites per line are allowed in standard NES, but a user may remove this limit. - @sp_limit = (@conf.sprite_limit ? 8 : 32) * 4 - @sp_buffer = [0] * @sp_limit - @sp_buffered = 0 - @sp_visible = false - @sp_map = [nil] * 264 # [[behind?, zero?, color]] - @sp_map_buffer = (0...264).map { [false, false, 0] } # preallocation for @sp_map - @sp_zero_in_line = false - end - - def update_output_color - 0x20.times do |i| - @output_color[i] = @palette[@palette_ram[i] & @coloring | @emphasis] - end - end - - def setup_lut - @lut_update = {}.compare_by_identity - - @name_lut = (0..0xffff).map do |i| - nmt_bank = @nmt_ref[i >> 10 & 3] - nmt_idx = i & 0x03ff - fixed = (i >> 12 & 7) | (i[15] << 12) - (((@lut_update[nmt_bank] ||= [])[nmt_idx] ||= [nil, nil])[0] ||= []) << [i, fixed] - nmt_bank[nmt_idx] << 4 | fixed - end - - entries = {} - @attr_lut = (0..0x7fff).map do |i| - io_addr = 0x23c0 | (i & 0x0c00) | (i >> 4 & 0x0038) | (i >> 2 & 0x0007) - nmt_bank = @nmt_ref[io_addr >> 10 & 3] - nmt_idx = io_addr & 0x03ff - attr_shift = (i & 2) | (i >> 4 & 4) - key = [io_addr, attr_shift] - entries[key] ||= [io_addr, TILE_LUT[nmt_bank[nmt_idx] >> attr_shift & 3], attr_shift] - (((@lut_update[nmt_bank] ||= [])[nmt_idx] ||= [nil, nil])[1] ||= []) << entries[key] - entries[key] - end.freeze - entries.each_value {|a| a.uniq! {|entry| entry.object_id } } - end - - ########################################################################### - # other APIs - - attr_reader :output_pixels - - def set_chr_mem(mem, writable) - @chr_mem = mem - @chr_mem_writable = writable - end - - NMT_TABLE = Ractor.make_shareable({ - horizontal: [0, 0, 1, 1], - vertical: [0, 1, 0, 1], - four_screen: [0, 1, 2, 3], - first: [0, 0, 0, 0], - second: [1, 1, 1, 1], - }) - def nametables=(mode) - update(RP2C02_CC) - idxs = NMT_TABLE[mode] - return if (0..3).all? {|i| @nmt_ref[i].equal?(@nmt_mem[idxs[i]]) } - @nmt_ref[0] = @nmt_mem[idxs[0]] - @nmt_ref[1] = @nmt_mem[idxs[1]] - @nmt_ref[2] = @nmt_mem[idxs[2]] - @nmt_ref[3] = @nmt_mem[idxs[3]] - setup_lut - end - - def update(data_setup) - sync(data_setup + @cpu.update) - end - - def setup_frame - @output_pixels.clear - @odd_frame = !@odd_frame - @vclk, @hclk_target, @cpu.next_frame_clock = @hclk == HCLOCK_DUMMY ? DUMMY_FRAME : BOOT_FRAME - end - - def vsync - if @hclk_target != FOREVER_CLOCK - @hclk_target = FOREVER_CLOCK - run - end - @output_pixels << @palette[15] while @output_pixels.size < 256 * 240 # fill black - end - - def monitor_a12_rising_edge(monitor) - @a12_monitor = monitor - end - - ########################################################################### - # helpers - - def update_vram_addr - if @vram_addr_inc == 32 - if active? - if @scroll_addr_5_14 & 0x7000 == 0x7000 - @scroll_addr_5_14 &= 0x0fff - case @scroll_addr_5_14 & 0x03e0 - when 0x03a0 then @scroll_addr_5_14 ^= 0x0800 - when 0x03e0 then @scroll_addr_5_14 &= 0x7c00 - else @scroll_addr_5_14 += 0x20 - end - else - @scroll_addr_5_14 += 0x1000 - end - else - @scroll_addr_5_14 += 0x20 - end - elsif @scroll_addr_0_4 < 0x1f - @scroll_addr_0_4 += 1 - else - @scroll_addr_0_4 = 0 - @scroll_addr_5_14 += 0x20 - end - update_scroll_address_line - end - - def update_scroll_address_line - @name_io_addr = (@scroll_addr_0_4 | @scroll_addr_5_14) & 0x0fff | 0x2000 - if @a12_monitor - a12_state = @scroll_addr_5_14 & 0x3000 == 0x1000 - @a12_monitor.a12_signaled(@cpu.current_clock) if !@a12_state && a12_state - @a12_state = a12_state - end - end - - def active? - @scanline != SCANLINE_VBLANK && @any_show - end - - def sync(elapsed) - return unless @hclk_target < elapsed - @hclk_target = elapsed / RP2C02_CC - @vclk - run - end - - def make_sure_invariants - @name_io_addr = (@scroll_addr_0_4 | @scroll_addr_5_14) & 0x0fff | 0x2000 - @bg_pattern_lut_fetched = TILE_LUT[ - @nmt_ref[@io_addr >> 10 & 3][@io_addr & 0x03ff] >> ((@scroll_addr_0_4 & 0x2) | (@scroll_addr_5_14[6] * 0x4)) & 3 - ] - end - - def io_latch_mask(data) - if active? - 0xff - elsif @regs_oam & 0x03 == 0x02 - data & 0xe3 - else - data - end - end - - ########################################################################### - # mapped memory handlers - - # PPUCTRL - def poke_2000(_addr, data) - update(RP2C02_CC) - need_nmi_old = @need_nmi - - @scroll_latch = (@scroll_latch & 0x73ff) | (data & 0x03) << 10 - @vram_addr_inc = data[2] == 1 ? 32 : 1 - @sp_base = data[3] == 1 ? 0x1000 : 0x0000 - @bg_pattern_base = data[4] == 1 ? 0x1000 : 0x0000 - @sp_height = data[5] == 1 ? 16 : 8 - @need_nmi = data[7] == 1 - - @io_latch = data - @pattern_end = @sp_base != 0 || @sp_height == 16 ? 0x1ff0 : 0x0ff0 - @bg_pattern_base_15 = @bg_pattern_base[12] << 15 - - if @need_nmi && @vblank && !need_nmi_old - clock = @cpu.current_clock + RP2C02_CC - @cpu.do_nmi(clock) if clock < RP2C02_HVINT - end - end - - # PPUMASK - def poke_2001(_addr, data) - update(RP2C02_CC) - bg_show_old, bg_show_edge_old = @bg_show, @bg_show_edge - sp_show_old, sp_show_edge_old = @sp_show, @sp_show_edge - any_show_old = @any_show - coloring_old, emphasis_old = @coloring, @emphasis - - @bg_show = data[3] == 1 - @bg_show_edge = data[1] == 1 && @bg_show - @sp_show = data[4] == 1 - @sp_show_edge = data[2] == 1 && @sp_show - @any_show = @bg_show || @sp_show - @coloring = data[0] == 1 ? 0x30 : 0x3f # 0x30: monochrome - @emphasis = (data & 0xe0) << 1 - - @io_latch = data - - if bg_show_old != @bg_show || bg_show_edge_old != @bg_show_edge || - sp_show_old != @sp_show || sp_show_edge_old != @sp_show_edge - - if @hclk < 8 || @hclk >= 248 - update_enabled_flags_edge - else - update_enabled_flags - end - update_scroll_address_line if any_show_old && !@any_show - end - - update_output_color if coloring_old != @coloring || emphasis_old != @emphasis - end - - # PPUSTATUS - def peek_2002(_addr) - update(RP2C02_CC) - v = @io_latch & 0x1f - v |= 0x80 if @vblank - v |= 0x40 if @sp_zero_hit - v |= 0x20 if @sp_overflow - @io_latch = v - @scroll_toggle = false - @vblanking = @vblank = false - @io_latch - end - - # OAMADDR - def poke_2003(_addr, data) - update(RP2C02_CC) - @regs_oam = @io_latch = data - end - - # OAMDATA (write) - def poke_2004(_addr, data) - update(RP2C02_CC) - @io_latch = @sp_ram[@regs_oam] = io_latch_mask(data) - @regs_oam = (@regs_oam + 1) & 0xff - end - - # OAMDATA (read) - def peek_2004(_addr) - if !@any_show || @cpu.current_clock - (@cpu.next_frame_clock - (341 * 241) * RP2C02_CC) >= (341 * 240) * RP2C02_CC - @io_latch = @sp_ram[@regs_oam] - else - update(RP2C02_CC) - @io_latch = @sp_latch - end - end - - # PPUSCROLL - def poke_2005(_addr, data) - update(RP2C02_CC) - @io_latch = data - @scroll_toggle = !@scroll_toggle - if @scroll_toggle - @scroll_latch = @scroll_latch & 0x7fe0 | (data >> 3) - xfine = 8 - (data & 0x7) - @bg_pixels.rotate!(@scroll_xfine - xfine) - @scroll_xfine = xfine - else - @scroll_latch = (@scroll_latch & 0x0c1f) | ((data << 2 | data << 12) & 0x73e0) - end - end - - # PPUADDR - def poke_2006(_addr, data) - update(RP2C02_CC) - @io_latch = data - @scroll_toggle = !@scroll_toggle - if @scroll_toggle - @scroll_latch = @scroll_latch & 0x00ff | (data & 0x3f) << 8 - else - @scroll_latch = (@scroll_latch & 0x7f00) | data - @scroll_addr_0_4 = @scroll_latch & 0x001f - @scroll_addr_5_14 = @scroll_latch & 0x7fe0 - update_scroll_address_line - end - end - - # PPUDATA (write) - def poke_2007(_addr, data) - update(RP2C02_CC * 4) - addr = @scroll_addr_0_4 | @scroll_addr_5_14 - update_vram_addr - @io_latch = data - if addr & 0x3f00 == 0x3f00 - addr &= 0x1f - final = @palette[data & @coloring | @emphasis] - @palette_ram[addr] = data - @output_color[addr] = final - if addr & 3 == 0 - @palette_ram[addr ^ 0x10] = data - @output_color[addr ^ 0x10] = final - end - @output_bg_color = @palette_ram[0] & 0x3f - else - addr &= 0x3fff - if addr >= 0x2000 - nmt_bank = @nmt_ref[addr >> 10 & 0x3] - nmt_idx = addr & 0x03ff - if nmt_bank[nmt_idx] != data - nmt_bank[nmt_idx] = data - - name_lut_update, attr_lut_update = @lut_update[nmt_bank][nmt_idx] - name_lut_update.each {|i, b| @name_lut[i] = data << 4 | b } if name_lut_update - attr_lut_update.each {|a| a[1] = TILE_LUT[data >> a[2] & 3] } if attr_lut_update - end - elsif @chr_mem_writable - @chr_mem[addr] = data - end - end - end - - # PPUDATA (read) - def peek_2007(_addr) - update(RP2C02_CC) - addr = (@scroll_addr_0_4 | @scroll_addr_5_14) & 0x3fff - update_vram_addr - @io_latch = (addr & 0x3f00) != 0x3f00 ? @io_buffer : @palette_ram[addr & 0x1f] & @coloring - @io_buffer = addr >= 0x2000 ? @nmt_ref[addr >> 10 & 0x3][addr & 0x3ff] : @chr_mem[addr] - @io_latch - end - - def poke_2xxx(_addr, data) - @io_latch = data - end - - def peek_2xxx(_addr) - @io_latch - end - - def peek_3000(_addr) - update(RP2C02_CC) - @io_latch - end - - # OAMDMA - def poke_4014(_addr, data) # DMA - @cpu.steal_clocks(CPU::CLK_1) if @cpu.odd_clock? - update(RP2C02_CC) - @cpu.steal_clocks(CPU::CLK_1) - data <<= 8 - if @regs_oam == 0 && data < 0x2000 && (!@any_show || @cpu.current_clock <= RP2C02_HVINT - CPU::CLK_1 * 512) - @cpu.steal_clocks(CPU::CLK_1 * 512) - @cpu.sprite_dma(data & 0x7ff, @sp_ram) - @io_latch = @sp_ram[0xff] - else - begin - @io_latch = @cpu.fetch(data) - data += 1 - @cpu.steal_clocks(CPU::CLK_1) - update(RP2C02_CC) - @cpu.steal_clocks(CPU::CLK_1) - @io_latch = io_latch_mask(@io_latch) - @sp_ram[@regs_oam] = @io_latch - @regs_oam = (@regs_oam + 1) & 0xff - end while data & 0xff != 0 - end - end - - def peek_4014(_addr) - 0x40 - end - - ########################################################################### - # helper methods for PPU#run - - # NOTE: These methods will be adhocly-inlined. Keep compatibility with - # OptimizedCodeBuilder (e.g., do not change the parameter names blindly). - - def open_pattern(exp) - return unless @any_show - @io_addr = exp - update_address_line - end - - def open_sprite(buffer_idx) - flip_v = @sp_buffer[buffer_idx + 2][7] # OAM byte2 bit7: "Flip vertically" flag - tmp = (@scanline - @sp_buffer[buffer_idx]) ^ (flip_v * 0xf) - byte1 = @sp_buffer[buffer_idx + 1] - addr = @sp_height == 16 ? ((byte1 & 0x01) << 12) | ((byte1 & 0xfe) << 4) | (tmp[3] * 0x10) : @sp_base | byte1 << 4 - addr | (tmp & 7) - end - - def load_sprite(pat0, pat1, buffer_idx) - byte2 = @sp_buffer[buffer_idx + 2] - pos = SP_PIXEL_POSITIONS[byte2[6]] # OAM byte2 bit6: "Flip horizontally" flag - pat = (pat0 >> 1 & 0x55) | (pat1 & 0xaa) | ((pat0 & 0x55) | (pat1 << 1 & 0xaa)) << 8 - x_base = @sp_buffer[buffer_idx + 3] - palette_base = 0x10 + ((byte2 & 3) << 2) # OAM byte2 bit0-1: Palette - @sp_visible ||= @sp_map.clear - 8.times do |dx| - x = x_base + dx - clr = (pat >> (pos[dx] * 2)) & 3 - next if @sp_map[x] || clr == 0 - @sp_map[x] = sprite = @sp_map_buffer[x] - # sprite[0]: behind flag, sprite[1]: zero hit flag, sprite[2]: color - sprite[0] = byte2[5] == 1 # OAM byte2 bit5: "Behind background" flag - sprite[1] = buffer_idx == 0 && @sp_zero_in_line - sprite[2] = palette_base + clr - end - @sp_active = @sp_enabled - end - - def update_address_line - if @a12_monitor - a12_state = @io_addr[12] == 1 - @a12_monitor.a12_signaled((@vclk + @hclk) * RP2C02_CC) if !@a12_state && a12_state - @a12_state = a12_state - end - end - - ########################################################################### - # actions for PPU#run - - def open_name - return unless @any_show - @io_addr = @name_io_addr - update_address_line - end - - def fetch_name - return unless @any_show - @io_pattern = @name_lut[@scroll_addr_0_4 + @scroll_addr_5_14 + @bg_pattern_base_15] - end - - def open_attr - return unless @any_show - @io_addr, @bg_pattern_lut_fetched, = @attr_lut[@scroll_addr_0_4 + @scroll_addr_5_14] - update_address_line - end - - def fetch_attr - return unless @any_show - @bg_pattern_lut = @bg_pattern_lut_fetched - # raise unless @bg_pattern_lut_fetched == - # @nmt_ref[@io_addr >> 10 & 3][@io_addr & 0x03ff] >> - # ((@scroll_addr_0_4 & 0x2) | (@scroll_addr_5_14[6] * 0x4)) & 3 - end - - def fetch_bg_pattern_0 - return unless @any_show - @bg_pattern = @chr_mem[@io_addr & 0x1fff] - end - - def fetch_bg_pattern_1 - return unless @any_show - @bg_pattern |= @chr_mem[@io_addr & 0x1fff] * 0x100 - end - - def scroll_clock_x - return unless @any_show - if @scroll_addr_0_4 < 0x001f - @scroll_addr_0_4 += 1 - @name_io_addr += 1 # make cache consistent - else - @scroll_addr_0_4 = 0 - @scroll_addr_5_14 ^= 0x0400 - @name_io_addr ^= 0x041f # make cache consistent - end - end - - def scroll_reset_x - return unless @any_show - @scroll_addr_0_4 = @scroll_latch & 0x001f - @scroll_addr_5_14 = (@scroll_addr_5_14 & 0x7be0) | (@scroll_latch & 0x0400) - @name_io_addr = (@scroll_addr_0_4 | @scroll_addr_5_14) & 0x0fff | 0x2000 # make cache consistent - end - - def scroll_clock_y - return unless @any_show - if @scroll_addr_5_14 & 0x7000 != 0x7000 - @scroll_addr_5_14 += 0x1000 - else - mask = @scroll_addr_5_14 & 0x03e0 - # rubocop:disable Style/CaseLikeIf - if mask == 0x03a0 - @scroll_addr_5_14 ^= 0x0800 - @scroll_addr_5_14 &= 0x0c00 - elsif mask == 0x03e0 - @scroll_addr_5_14 &= 0x0c00 - else - @scroll_addr_5_14 = (@scroll_addr_5_14 & 0x0fe0) + 32 - end - # rubocop:enable Style/CaseLikeIf - end - - @name_io_addr = (@scroll_addr_0_4 | @scroll_addr_5_14) & 0x0fff | 0x2000 # make cache consistent - end - - def preload_tiles - return unless @any_show - @bg_pixels[@scroll_xfine, 8] = @bg_pattern_lut[@bg_pattern] - end - - def load_tiles - return unless @any_show - @bg_pixels.rotate!(8) - @bg_pixels[@scroll_xfine, 8] = @bg_pattern_lut[@bg_pattern] - end - - def evaluate_sprites_even - return unless @any_show - @sp_latch = @sp_ram[@sp_addr] - end - - def evaluate_sprites_odd - return unless @any_show - - # we first check phase 1 since it is the most-likely case - if @sp_phase # nil represents phase 1 - # the second most-likely case is phase 9 - if @sp_phase == 9 - evaluate_sprites_odd_phase_9 - else - # other cases are relatively rare - case @sp_phase - # when 1 then evaluate_sprites_odd_phase_1 - # when 9 then evaluate_sprites_odd_phase_9 - when 2 then evaluate_sprites_odd_phase_2 - when 3 then evaluate_sprites_odd_phase_3 - when 4 then evaluate_sprites_odd_phase_4 - when 5 then evaluate_sprites_odd_phase_5 - when 6 then evaluate_sprites_odd_phase_6 - when 7 then evaluate_sprites_odd_phase_7 - when 8 then evaluate_sprites_odd_phase_8 - end - end - else - evaluate_sprites_odd_phase_1 - end - end - - def evaluate_sprites_odd_phase_1 - @sp_index += 1 - if @sp_latch <= @scanline && @scanline < @sp_latch + @sp_height - @sp_addr += 1 - @sp_phase = 2 - @sp_buffer[@sp_buffered] = @sp_latch - elsif @sp_index == 64 - @sp_addr = 0 - @sp_phase = 9 - elsif @sp_index == 2 - @sp_addr = 8 - else - @sp_addr += 4 - end - end - - def evaluate_sprites_odd_phase_2 - @sp_addr += 1 - @sp_phase = 3 - @sp_buffer[@sp_buffered + 1] = @sp_latch - end - - def evaluate_sprites_odd_phase_3 - @sp_addr += 1 - @sp_phase = 4 - @sp_buffer[@sp_buffered + 2] = @sp_latch - end - - def evaluate_sprites_odd_phase_4 - @sp_buffer[@sp_buffered + 3] = @sp_latch - @sp_buffered += 4 - if @sp_index != 64 - @sp_phase = @sp_buffered != @sp_limit ? nil : 5 - if @sp_index != 2 - @sp_addr += 1 - @sp_zero_in_line ||= @sp_index == 1 - else - @sp_addr = 8 - end - else - @sp_addr = 0 - @sp_phase = 9 - end - end - - def evaluate_sprites_odd_phase_5 - if @sp_latch <= @scanline && @scanline < @sp_latch + @sp_height - @sp_phase = 6 - @sp_addr = (@sp_addr + 1) & 0xff - @sp_overflow = true - else - @sp_addr = ((@sp_addr + 4) & 0xfc) + ((@sp_addr + 1) & 3) - if @sp_addr <= 5 - @sp_phase = 9 - @sp_addr &= 0xfc - end - end - end - - def evaluate_sprites_odd_phase_6 - @sp_phase = 7 - @sp_addr = (@sp_addr + 1) & 0xff - end - - def evaluate_sprites_odd_phase_7 - @sp_phase = 8 - @sp_addr = (@sp_addr + 1) & 0xff - end - - def evaluate_sprites_odd_phase_8 - @sp_phase = 9 - @sp_addr = (@sp_addr + 1) & 0xff - @sp_addr += 1 if @sp_addr & 3 == 3 - @sp_addr &= 0xfc - end - - def evaluate_sprites_odd_phase_9 - @sp_addr = (@sp_addr + 4) & 0xff - end - - def load_extended_sprites - return unless @any_show - if 32 < @sp_buffered - buffer_idx = 32 - begin - addr = open_sprite(buffer_idx) - pat0 = @chr_mem[addr] - pat1 = @chr_mem[addr | 8] - load_sprite(pat0, pat1, buffer_idx) if pat0 != 0 || pat1 != 0 - buffer_idx += 4 - end while buffer_idx != @sp_buffered - end - end - - def render_pixel - if @any_show - pixel = @bg_enabled ? @bg_pixels[@hclk % 8] : 0 - if @sp_active && (sprite = @sp_map[@hclk]) - if pixel % 4 == 0 - pixel = sprite[2] - else - @sp_zero_hit = true if sprite[1] && @hclk != 255 - pixel = sprite[2] unless sprite[0] - end - end - else - pixel = @scroll_addr_5_14 & 0x3f00 == 0x3f00 ? @scroll_addr_0_4 : 0 - @bg_pixels[@hclk % 8] = 0 - end - @output_pixels << @output_color[pixel] - end - - # just a placeholder; used for batch_render_pixels optimization - def batch_render_eight_pixels - end - - def boot - @vblank = true - @hclk = HCLOCK_DUMMY - @hclk_target = FOREVER_CLOCK - end - - def vblank_0 - @vblanking = true - @hclk = HCLOCK_VBLANK_1 - end - - def vblank_1 - @vblank ||= @vblanking - @vblanking = false - @sp_visible = false - @sp_active = false - @hclk = HCLOCK_VBLANK_2 - end - - def vblank_2 - @vblank ||= @vblanking - @vblanking = false - @hclk = HCLOCK_DUMMY - @hclk_target = FOREVER_CLOCK - @cpu.do_nmi(@cpu.next_frame_clock) if @need_nmi && @vblank - end - - def update_enabled_flags - return unless @any_show - @bg_enabled = @bg_show - @sp_enabled = @sp_show - @sp_active = @sp_enabled && @sp_visible - end - - def update_enabled_flags_edge - @bg_enabled = @bg_show_edge - @sp_enabled = @sp_show_edge - @sp_active = @sp_enabled && @sp_visible - end - - ########################################################################### - # default core - - def debug_logging(scanline, hclk, hclk_target) - hclk = "forever" if hclk == FOREVER_CLOCK - hclk_target = "forever" if hclk_target == FOREVER_CLOCK - - @conf.debug("ppu: scanline #{ scanline }, hclk #{ hclk }->#{ hclk_target }") - end - - def run - @fiber ||= Fiber.new do - main_loop - :done - end - - debug_logging(@scanline, @hclk, @hclk_target) if @conf.loglevel >= 3 - - make_sure_invariants - - @hclk_target = (@vclk + @hclk) * RP2C02_CC unless @fiber.resume - end - - def dispose - @run = false - raise 'PPU Fiber should have finished' unless @fiber.resume == :done - @fiber = nil - end - - def wait_frame - Fiber.yield true - end - - def wait_zero_clocks - Fiber.yield if @hclk_target <= @hclk - end - - def wait_one_clock - @hclk += 1 - Fiber.yield if @hclk_target <= @hclk - end - - def wait_two_clocks - @hclk += 2 - Fiber.yield if @hclk_target <= @hclk - end - - ### main-loop structure - # - # # wait for boot - # clk_685 - # - # loop do - # # pre-render scanline - # clk_341, clk_342, ..., clk_659 - # while true - # # visible scanline (not shown) - # clk_320, clk_321, ..., clk_337 - # - # # increment scanline - # clk_338 - # break if @scanline == 240 - # - # # visible scanline (shown) - # clk_0, clk_1, ..., clk_319 - # end - # - # # post-render sacnline (vblank) - # do_681,682,684 - # end - # - # This method definition also serves as a template for OptimizedCodeBuilder. - # Comments like "when NNN" are markers for the purpose. - # - # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Style/SoleNestedConditional - def main_loop - # when 685 - - # wait for boot - boot - wait_frame - - while @run - # pre-render scanline - - 341.step(589, 8) do - # when 341, 349, ..., 589 - if @hclk == 341 - @sp_overflow = @sp_zero_hit = @vblanking = @vblank = false - @scanline = SCANLINE_HDUMMY - end - open_name - wait_two_clocks - - # when 343, 351, ..., 591 - open_attr - wait_two_clocks - - # when 345, 353, ..., 593 - open_pattern(@bg_pattern_base) - wait_two_clocks - - # when 347, 355, ..., 595 - open_pattern(@io_addr | 8) - wait_two_clocks - end - - 597.step(653, 8) do - # when 597, 605, ..., 653 - if @any_show - if @hclk == 645 - @scroll_addr_0_4 = @scroll_latch & 0x001f - @scroll_addr_5_14 = @scroll_latch & 0x7fe0 - @name_io_addr = (@scroll_addr_0_4 | @scroll_addr_5_14) & 0x0fff | 0x2000 # make cache consistent - end - end - open_name - wait_two_clocks - - # when 599, 607, ..., 655 - # Nestopia uses open_name here? - open_attr - wait_two_clocks - - # when 601, 609, ..., 657 - open_pattern(@pattern_end) - wait_two_clocks - - # when 603, 611, ..., 659 - open_pattern(@io_addr | 8) - if @hclk == 659 - @hclk = 320 - @vclk += HCLOCK_DUMMY - @hclk_target -= HCLOCK_DUMMY - else - wait_two_clocks - end - wait_zero_clocks - end - - while true - # visible scanline (not shown) - - # when 320 - load_extended_sprites - open_name - @sp_latch = @sp_ram[0] if @any_show - @sp_buffered = 0 - @sp_zero_in_line = false - @sp_index = 0 - @sp_phase = 0 - wait_one_clock - - # when 321 - fetch_name - wait_one_clock - - # when 322 - open_attr - wait_one_clock - - # when 323 - fetch_attr - scroll_clock_x - wait_one_clock - - # when 324 - open_pattern(@io_pattern) - wait_one_clock - - # when 325 - fetch_bg_pattern_0 - wait_one_clock - - # when 326 - open_pattern(@io_pattern | 8) - wait_one_clock - - # when 327 - fetch_bg_pattern_1 - wait_one_clock - - # when 328 - preload_tiles - open_name - wait_one_clock - - # when 329 - fetch_name - wait_one_clock - - # when 330 - open_attr - wait_one_clock - - # when 331 - fetch_attr - scroll_clock_x - wait_one_clock - - # when 332 - open_pattern(@io_pattern) - wait_one_clock - - # when 333 - fetch_bg_pattern_0 - wait_one_clock - - # when 334 - open_pattern(@io_pattern | 8) - wait_one_clock - - # when 335 - fetch_bg_pattern_1 - wait_one_clock - - # when 336 - open_name - wait_one_clock - - # when 337 - if @any_show - update_enabled_flags_edge - @cpu.next_frame_clock = RP2C02_HVSYNC_1 if @scanline == SCANLINE_HDUMMY && @odd_frame - end - wait_one_clock - - # when 338 - open_name - @scanline += 1 - if @scanline != SCANLINE_VBLANK - if @any_show - line = @scanline != 0 || !@odd_frame ? 341 : 340 - else - update_enabled_flags_edge - line = 341 - end - @hclk = 0 - @vclk += line - @hclk_target = @hclk_target <= line ? 0 : @hclk_target - line - else - @hclk = HCLOCK_VBLANK_0 - wait_zero_clocks - break - end - wait_zero_clocks - - # visible scanline (shown) - 0.step(248, 8) do - # when 0, 8, ..., 248 - if @any_show - if @hclk == 64 - @sp_addr = @regs_oam & 0xf8 # SP_OFFSET_TO_0_1 - @sp_phase = nil - @sp_latch = 0xff - end - load_tiles - batch_render_eight_pixels - evaluate_sprites_even if @hclk >= 64 - open_name - end - render_pixel - wait_one_clock - - # when 1, 9, ..., 249 - if @any_show - fetch_name - evaluate_sprites_odd if @hclk >= 64 - end - render_pixel - wait_one_clock - - # when 2, 10, ..., 250 - if @any_show - evaluate_sprites_even if @hclk >= 64 - open_attr - end - render_pixel - wait_one_clock - - # when 3, 11, ..., 251 - if @any_show - fetch_attr - evaluate_sprites_odd if @hclk >= 64 - scroll_clock_y if @hclk == 251 - scroll_clock_x - end - render_pixel - wait_one_clock - - # when 4, 12, ..., 252 - if @any_show - evaluate_sprites_even if @hclk >= 64 - open_pattern(@io_pattern) - end - render_pixel - wait_one_clock - - # when 5, 13, ..., 253 - if @any_show - fetch_bg_pattern_0 - evaluate_sprites_odd if @hclk >= 64 - end - render_pixel - wait_one_clock - - # when 6, 14, ..., 254 - if @any_show - evaluate_sprites_even if @hclk >= 64 - open_pattern(@io_pattern | 8) - end - render_pixel - wait_one_clock - - # when 7, 15, ..., 255 - if @any_show - fetch_bg_pattern_1 - evaluate_sprites_odd if @hclk >= 64 - end - render_pixel - # rubocop:disable Style/NestedModifier, Style/IfUnlessModifierOfIfUnless: - update_enabled_flags if @hclk != 255 if @any_show - # rubocop:enable Style/NestedModifier, Style/IfUnlessModifierOfIfUnless: - wait_one_clock - end - - 256.step(312, 8) do - # rubocop:disable Style/IdenticalConditionalBranches - if @hclk == 256 - # when 256 - open_name - @sp_latch = 0xff if @any_show - wait_one_clock - - # when 257 - scroll_reset_x - @sp_visible = false - @sp_active = false - wait_one_clock - else - # when 264, 272, ..., 312 - open_name - wait_two_clocks - end - # rubocop:enable Style/IdenticalConditionalBranches - - # when 258, 266, ..., 314 - # Nestopia uses open_name here? - open_attr - wait_two_clocks - - # when 260, 268, ..., 316 - if @any_show - buffer_idx = (@hclk - 260) / 2 - open_pattern(buffer_idx >= @sp_buffered ? @pattern_end : open_sprite(buffer_idx)) - # rubocop:disable Style/NestedModifier, Style/IfUnlessModifierOfIfUnless: - @regs_oam = 0 if @scanline == 238 if @hclk == 316 - # rubocop:enable Style/NestedModifier, Style/IfUnlessModifierOfIfUnless: - end - wait_one_clock - - # when 261, 269, ..., 317 - if @any_show - @io_pattern = @chr_mem[@io_addr & 0x1fff] if (@hclk - 261) / 2 < @sp_buffered - end - wait_one_clock - - # when 262, 270, ..., 318 - open_pattern(@io_addr | 8) - wait_one_clock - - # when 263, 271, ..., 319 - if @any_show - buffer_idx = (@hclk - 263) / 2 - if buffer_idx < @sp_buffered - pat0 = @io_pattern - pat1 = @chr_mem[@io_addr & 0x1fff] - load_sprite(pat0, pat1, buffer_idx) if pat0 != 0 || pat1 != 0 - end - end - wait_one_clock - end - end - - # post-render scanline (vblank) - - # when 681 - vblank_0 - wait_zero_clocks - - # when 682 - vblank_1 - wait_zero_clocks - - # when 684 - vblank_2 - wait_frame - end - end - # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Style/SoleNestedConditional - - ########################################################################### - # optimized core generator - class OptimizedCodeBuilder - include CodeOptimizationHelper - - OPTIONS = [ - :method_inlining, :ivar_localization, - :split_show_mode, :split_a12_checks, :clock_specialization, - :fastpath, :batch_render_pixels, :oneline, - ] - - def build - depends(:ivar_localization, :method_inlining) - depends(:batch_render_pixels, :fastpath) - - mdefs = parse_method_definitions(__FILE__) - handlers = parse_clock_handlers(mdefs[:main_loop].body) - - handlers = specialize_clock_handlers(handlers) if @clock_specialization - if @fastpath - handlers = add_fastpath(handlers) do |fastpath, hclk| - @batch_render_pixels ? batch_render_pixels(fastpath, hclk) : fastpath - end - end - code = build_loop(handlers) - code = ppu_expand_methods(code, mdefs) if @method_inlining - - if @split_show_mode - code, code_no_show = split_mode(code, "@any_show") - if @split_a12_checks - code, code_no_a12 = split_mode(code, "@a12_monitor") - code = branch("@a12_monitor", code, code_no_a12) - end - code = branch("@any_show", code, code_no_show) - end - - code = gen( - mdefs[:make_sure_invariants].body, - code, - "@hclk_target = (@vclk + @hclk) * RP2C02_CC" - ) - - code = localize_instance_variables(code) if @ivar_localization - - code = gen( - "def self.run", - *(@loglevel >= 3 ? [" debug_logging(@scanline, @hclk, @hclk_target)"] : []), - indent(2, code), - "end", - ) - - code = oneline(code) if @oneline - - code - end - - COMMANDS = { - wait_zero_clocks: "", - wait_one_clock: "@hclk += 1\n", - wait_two_clocks: "@hclk += 2\n", - wait_frame: "return\n", - } - - # extracts the actions for each clock from CPU#main_loop - def parse_clock_handlers(main_loop) - handlers = {} - main_loop.scan(/^( *)# when (.*)\n((?:\1.*\n|\n)*?\1wait_.*\n)/) do |indent, hclks, body| - body = indent(-indent.size, body) - body = body.gsub(/^( *)break\n/, "") - body = expand_methods(body, COMMANDS) - if hclks =~ /^(\d+), (\d+), \.\.\., (\d+)$/ - first, second, last = $1.to_i, $2.to_i, $3.to_i - first.step(last, second - first) do |hclk| - handlers[hclk] = body - end - else - handlers[hclks.to_i] = body - end - end - handlers - end - - # split clock handlers that contains a branch depending on clock - def specialize_clock_handlers(handlers) - handlers.each do |hclk, handler| - # pre-caluculate some conditions like `@hclk == 64` with `false` - handler = handler.gsub(/@hclk (==|>=|!=) (\d+)/) { hclk.send($1.to_sym, $2.to_i) } - - # remove disabled branches like `if false ... end` - handlers[hclk] = remove_trivial_branches(handler) - end - end - - # pass a fastpath - def add_fastpath(handlers) - handlers.each do |hclk, handler| - next unless hclk % 8 == 0 && hclk < 256 - fastpath = gen(*(0..7).map {|i| handlers[hclk + i] }) - fastpath = yield fastpath, hclk - handlers[hclk] = branch("@hclk + 8 <= @hclk_target", fastpath, handler) - end - end - - # replace eight `render_pixel` calls with one optimized batch version - def batch_render_pixels(fastpath, hclk) - fastpath = expand_methods(fastpath, render_pixel: gen( - "unless @any_show", - " @bg_pixels[@hclk % 8] = 0", - " @output_pixels << @output_color[@scroll_addr_5_14 & 0x3f00 == 0x3f00 ? @scroll_addr_0_4 : 0]", - "end", - )) - expand_methods(fastpath, batch_render_eight_pixels: gen( - "# batch-version of render_pixel", - "if @any_show", - " if @sp_active", - " if @bg_enabled", - *(0..7).flat_map do |i| - [ - " pixel#{ i } = @bg_pixels[#{ i }]", - " if sprite = @sp_map[@hclk#{ i != 0 ? " + #{ i }" : "" }]", - " if pixel#{ i } % 4 == 0", - " pixel#{ i } = sprite[2]", - " else", - *(hclk + i == 255 ? [] : [" @sp_zero_hit = true if sprite[1]"]), - " pixel#{ i } = sprite[2] unless sprite[0]", - " end", - " end", - ] - end, - " @output_pixels << " + (0..7).map {|n| "@output_color[pixel#{ n }]" } * " << ", - " else", - *(0..7).map do |i| - " pixel#{ i } = (sprite = @sp_map[@hclk #{ i != 0 ? " + #{ i }" : "" }]) ? sprite[2] : 0" - end, - " @output_pixels << " + (0..7).map {|n| "@output_color[pixel#{ n }]" } * " << ", - " end", - " else", - " if @bg_enabled # this is the true hot-spot", - " @output_pixels << " + (0..7).map {|n| "@output_color[@bg_pixels[#{ n }]]" } * " << ", - " else", - " clr = @output_color[0]", - " @output_pixels << " + ["clr"] * 8 * " << ", - " end", - " end", - "end", - )) - end - - # remove all newlines (this will reduce `trace` instructions) - def oneline(code) - code.gsub(/^ *|#.*/, "").gsub("[\n", "[").gsub(/\n *\]/, "]").tr("\n", ";") - end - - # inline method calls - def ppu_expand_methods(code, mdefs) - code = expand_inline_methods(code, :open_sprite, mdefs[:open_sprite]) - - # twice is enough - expand_methods(expand_methods(code, mdefs), mdefs) - end - - # create two version of the same code by evaluating easy branches - # CAUTION: the condition must be invariant during PPU#run - def split_mode(code, cond) - %w(true false).map do |bool| - rebuild_loop(remove_trivial_branches(replace_cond_var(code, cond, bool))) - end - end - - # generate a main code - def build_loop(handlers) - clauses = {} - handlers.sort.each do |hclk, handler| - (clauses[handler] ||= []) << hclk - end - - gen( - "while @hclk_target > @hclk", - " case @hclk", - *clauses.invert.sort.map do |hclks, handler| - " when #{ hclks * ", " }\n" + indent(4, handler) - end, - " end", - "end", - ) - end - - # deconstruct a loop, unify handlers, and re-generate a new loop - def rebuild_loop(code) - handlers = {} - code.scan(/^ when ((?:\d+, )*\d+)\n((?: .*\n|\n)*)/) do |hclks, handler| - hclks.split(", ").each do |hclk| - handlers[hclk.to_i] = indent(-4, handler) - end - end - build_loop(handlers) - end - end - end -end diff --git a/benchmarks-ractor/optcarrot/lib/optcarrot/rom.rb b/benchmarks-ractor/optcarrot/lib/optcarrot/rom.rb deleted file mode 100644 index db6510a5..00000000 --- a/benchmarks-ractor/optcarrot/lib/optcarrot/rom.rb +++ /dev/null @@ -1,144 +0,0 @@ -module Optcarrot - # Cartridge class (with NROM mapper implemented) - class ROM - MAPPER_DB = { 0x00 => self } - - # These are optional - require_relative "mapper/mmc1" - require_relative "mapper/uxrom" - require_relative "mapper/cnrom" - require_relative "mapper/mmc3" - Ractor.make_shareable(MAPPER_DB) - - def self.zip_extract(filename) - require "zlib" - bin = File.binread(filename) - loop do - sig, _, flags, comp, _, _, _, data_len, _, fn_len, ext_len = bin.slice!(0, 30).unpack("a4v5V3v2") - break if sig != "PK\3\4".b - fn = bin.slice!(0, fn_len) - bin.slice!(0, ext_len) - data = bin.slice!(0, data_len) - next if File.extname(fn).downcase != ".nes" - next if flags & 0x11 != 0 - next if comp != 0 && comp != 8 - if comp == 8 - zs = Zlib::Inflate.new(-15) - data = zs.inflate(data) - zs.finish - zs.close - end - return data - end - raise "failed to extract ROM file from `#{ filename }'" - end - - def self.load(conf, cpu, ppu) - filename = conf.romfile - basename = File.basename(filename) - - blob = (File.extname(filename) == ".zip" ? zip_extract(filename) : File.binread(filename)).bytes - - # parse mapper - mapper = (blob[6] >> 4) | (blob[7] & 0xf0) - - klass = MAPPER_DB[mapper] - raise NotImplementedError, "Unsupported mapper type 0x%02x" % mapper unless klass - klass.new(conf, cpu, ppu, basename, blob) - end - - class InvalidROM < StandardError - end - - def parse_header(buf) - raise InvalidROM, "Missing 16-byte header" if buf.size < 16 - raise InvalidROM, "Missing 'NES' constant in header" if buf[0, 4] != "NES\x1a".bytes - raise NotImplementedError, "trainer not supported" if buf[6][2] == 1 - raise NotImplementedError, "VS cart not supported" if buf[7][0] == 1 - raise NotImplementedError, "PAL not supported" unless buf[9][0] == 0 - - prg_banks = buf[4] - chr_banks = buf[5] - @mirroring = buf[6][0] == 0 ? :horizontal : :vertical - @mirroring = :four_screen if buf[6][3] == 1 - @battery = buf[6][1] == 1 - @mapper = (buf[6] >> 4) | (buf[7] & 0xf0) - ram_banks = [1, buf[8]].max - - return prg_banks, chr_banks, ram_banks - end - - def initialize(conf, cpu, ppu, basename, buf) - @conf = conf - @cpu = cpu - @ppu = ppu - @basename = basename - - prg_count, chr_count, wrk_count = parse_header(buf.slice!(0, 16)) - - raise InvalidROM, "EOF in ROM bank data" if buf.size < 0x4000 * prg_count - @prg_banks = (0...prg_count).map { buf.slice!(0, 0x4000) } - - raise InvalidROM, "EOF in CHR bank data" if buf.size < 0x2000 * chr_count - @chr_banks = (0...chr_count).map { buf.slice!(0, 0x2000) } - - @prg_ref = [nil] * 0x10000 - @prg_ref[0x8000, 0x4000] = @prg_banks.first - @prg_ref[0xc000, 0x4000] = @prg_banks.last - - @chr_ram = chr_count == 0 # No CHR bank implies CHR-RAM (writable CHR bank) - @chr_ref = @chr_ram ? [0] * 0x2000 : @chr_banks[0].dup - - @wrk_readable = wrk_count > 0 - @wrk_writable = false - @wrk = wrk_count > 0 ? (0x6000..0x7fff).map {|addr| addr >> 8 } : nil - - init - - @ppu.nametables = @mirroring - @ppu.set_chr_mem(@chr_ref, @chr_ram) - end - - def init - end - - def reset - @cpu.add_mappings(0x8000..0xffff, @prg_ref, nil) - end - - def inspect - [ - "Mapper: #{ @mapper } (#{ self.class.to_s.split("::").last })", - "PRG Banks: #{ @prg_banks.size }", - "CHR Banks: #{ @chr_banks.size }", - "Mirroring: #{ @mirroring }", - ].join("\n") - end - - def peek_6000(addr) - @wrk_readable ? @wrk[addr - 0x6000] : (addr >> 8) - end - - def poke_6000(addr, data) - @wrk[addr - 0x6000] = data if @wrk_writable - end - - def vsync - end - - def load_battery - return unless @battery - sav = @basename + ".sav" - return unless File.readable?(sav) - sav = File.binread(sav) - @wrk.replace(sav.bytes) - end - - def save_battery - return unless @battery - sav = @basename + ".sav" - puts "Saving: " + sav - File.binwrite(sav, @wrk.pack("C*")) - end - end -end diff --git a/benchmarks-ractor/optcarrot/optcarrot.gemspec b/benchmarks-ractor/optcarrot/optcarrot.gemspec deleted file mode 100644 index a1acabc0..00000000 --- a/benchmarks-ractor/optcarrot/optcarrot.gemspec +++ /dev/null @@ -1,26 +0,0 @@ -lib = File.expand_path("lib", __dir__) -$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require "optcarrot" - -Gem::Specification.new do |spec| - spec.name = "optcarrot" - spec.version = Optcarrot::VERSION - spec.authors = ["Yusuke Endoh"] - spec.email = ["mame@ruby-lang.org"] - - spec.summary = "A NES emulator written in Ruby." - spec.description = - 'An "enjoyable" benchmark for Ruby implementation. The goal of this project is to drive Ruby3x3.' - spec.homepage = "https://github.com/mame/optcarrot" - spec.license = "MIT" - - spec.files = `git ls-files -z`.split("\x0").reject {|f| f.match(%r{^tmp/|^tools/|^examples/|^\.}) } - spec.bindir = "bin" - spec.executables = ["optcarrot"] - spec.require_paths = ["lib"] - - spec.add_runtime_dependency "ffi", "~> 1.9" - spec.add_development_dependency "bundler", "~> 1.11" - spec.add_development_dependency "rake", "~> 10.0" - spec.add_development_dependency "stackprof", "~> 0.2" -end diff --git a/benchmarks.yml b/benchmarks.yml index 2932094c..19c69623 100644 --- a/benchmarks.yml +++ b/benchmarks.yml @@ -198,5 +198,3 @@ ractor/json_parse_float: desc: test the performance of parsing multiple lists of json floats with ractors. ractor/json_parse_string: desc: test the performance of parsing multiple lists of strings with ractors. -ractor/optcarrot: - desc: The NES emulator optcarrot, refactored to run inside multiple ractors.