1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include <chrono>
19#include <cmath>
20
21#include "ProfilingRunner.hxx"
22#include "FSNode.hxx"
23#include "CartDetector.hxx"
24#include "Cart.hxx"
25#include "MD5.hxx"
26#include "Control.hxx"
27#include "M6502.hxx"
28#include "M6532.hxx"
29#include "TIA.hxx"
30#include "ConsoleTiming.hxx"
31#include "FrameManager.hxx"
32#include "YStartDetector.hxx"
33#include "FrameLayoutDetector.hxx"
34#include "EmulationTiming.hxx"
35#include "ConsoleTiming.hxx"
36#include "System.hxx"
37#include "Joystick.hxx"
38#include "Random.hxx"
39#include "DispatchResult.hxx"
40
41using namespace std::chrono;
42
43namespace {
44 static constexpr uInt32 RUNTIME_DEFAULT = 60;
45
46 void updateProgress(uInt32 from, uInt32 to) {
47 while (from < to) {
48 if (from % 10 == 0 && from > 0) cout << from << "%";
49 else cout << ".";
50
51 cout.flush();
52
53 from++;
54 }
55 }
56}
57
58
59// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
60ProfilingRunner::ProfilingRunner(int argc, char* argv[])
61 : profilingRuns(std::max(argc - 2, 0))
62{
63 for (int i = 2; i < argc; i++) {
64 ProfilingRun& run(profilingRuns[i-2]);
65
66 string arg = argv[i];
67 size_t splitPoint = arg.find_first_of(":");
68
69 run.romFile = splitPoint == string::npos ? arg : arg.substr(0, splitPoint);
70
71 if (splitPoint == string::npos) run.runtime = RUNTIME_DEFAULT;
72 else {
73 int runtime = atoi(arg.substr(splitPoint+1, string::npos).c_str());
74 run.runtime = runtime > 0 ? runtime : RUNTIME_DEFAULT;
75 }
76 }
77
78 mySettings.setValue("fastscbios", true);
79}
80
81// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
82bool ProfilingRunner::run()
83{
84 cout << "Profiling Stella..." << endl;
85
86 for (ProfilingRun& run : profilingRuns) {
87 cout << endl << "running " << run.romFile << " for " << run.runtime << " seconds..." << endl;
88
89 if (!runOne(run)) return false;
90 }
91
92 return true;
93}
94
95// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
96bool ProfilingRunner::runOne(const ProfilingRun run)
97{
98 FilesystemNode imageFile(run.romFile);
99
100 if (!imageFile.isFile()) {
101 cout << "ERROR: " << run.romFile << " is not a ROM image" << endl;
102 return false;
103 }
104
105 ByteBuffer image;
106 uInt32 size = imageFile.read(image);
107 if (size == 0) {
108 cout << "ERROR: unable to read " << run.romFile << endl;
109 return false;
110 }
111
112 string md5 = MD5::hash(image, size);
113 string type = "";
114 unique_ptr<Cartridge> cartridge = CartDetector::create(imageFile, image, size, md5, type, mySettings);
115
116 if (!cartridge) {
117 cout << "ERROR: unable to determine cartridge type" << endl;
118 return false;
119 }
120
121 IO consoleIO;
122 Random rng(0);
123 Event event;
124
125 M6502 cpu(mySettings);
126 M6532 riot(consoleIO, mySettings);
127 TIA tia(consoleIO, []() { return ConsoleTiming::ntsc; }, mySettings);
128 System system(rng, cpu, riot, tia, *cartridge);
129
130 consoleIO.myLeftControl = make_unique<Joystick>(Controller::Jack::Left, event, system);
131 consoleIO.myRightControl = make_unique<Joystick>(Controller::Jack::Right, event, system);
132 consoleIO.mySwitches = make_unique<Switches>(event, myProps, mySettings);
133
134 tia.bindToControllers();
135 cartridge->setStartBankFromPropsFunc([]() { return -1; });
136 system.initialize();
137
138 FrameLayoutDetector frameLayoutDetector;
139 tia.setFrameManager(&frameLayoutDetector);
140 system.reset();
141
142 (cout << "detecting frame layout... ").flush();
143 for(int i = 0; i < 60; ++i) tia.update();
144
145 FrameLayout frameLayout = frameLayoutDetector.detectedLayout();
146 ConsoleTiming consoleTiming = ConsoleTiming::ntsc;
147
148 switch (frameLayout) {
149 case FrameLayout::ntsc:
150 cout << "NTSC";
151 consoleTiming = ConsoleTiming::ntsc;
152 break;
153
154 case FrameLayout::pal:
155 cout << "PAL";
156 consoleTiming = ConsoleTiming::pal;
157 break;
158 }
159
160 (cout << endl).flush();
161
162 YStartDetector ystartDetector;
163 tia.setFrameManager(&ystartDetector);
164 system.reset();
165
166 (cout << "detecting ystart... ").flush();
167 for (int i = 0; i < 80; i++) tia.update();
168
169 uInt32 yStart = ystartDetector.detectedYStart();
170 (cout << yStart << endl).flush();
171
172 FrameManager frameManager;
173 tia.setFrameManager(&frameManager);
174 tia.setLayout(frameLayout);
175 tia.setYStart(yStart);
176
177 system.reset();
178
179 EmulationTiming emulationTiming(frameLayout, consoleTiming);
180 uInt64 cycles = 0;
181 uInt64 cyclesTarget = run.runtime * emulationTiming.cyclesPerSecond();
182
183 DispatchResult dispatchResult;
184 dispatchResult.setOk(0);
185
186 uInt32 percent = 0;
187 (cout << "0%").flush();
188
189 time_point<high_resolution_clock> tp = high_resolution_clock::now();
190
191 while (cycles < cyclesTarget && dispatchResult.getStatus() == DispatchResult::Status::ok) {
192 tia.update(dispatchResult);
193 cycles += dispatchResult.getCycles();
194
195 if (tia.newFramePending()) tia.renderToFrameBuffer();
196
197 uInt32 percentNow = uInt32(std::min((100 * cycles) / cyclesTarget, static_cast<uInt64>(100)));
198 updateProgress(percent, percentNow);
199
200 percent = percentNow;
201 }
202
203 double realtimeUsed = duration_cast<duration<double>>(high_resolution_clock::now () - tp).count();
204
205 if (dispatchResult.getStatus() != DispatchResult::Status::ok) {
206 cout << endl << "ERROR: emulation failed after " << cycles << " cycles";
207 return false;
208 }
209
210 (cout << "100%" << endl).flush();
211 cout << "real time: " << realtimeUsed << " seconds" << endl;
212
213 return true;
214}
215