1 // Copyright 2017 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "Core/HW/DVD/DVDMath.h"
6
7 #include <cinttypes>
8 #include <cmath>
9
10 #include "Common/CommonTypes.h"
11 #include "Common/Logging/Log.h"
12
13 namespace DVDMath
14 {
15 // The size of the first Wii disc layer in bytes (2294912 sectors, 2048 bytes per sector)
16 constexpr u64 WII_DISC_LAYER_SIZE = 0x118240000;
17
18 // 24 mm
19 constexpr double DVD_INNER_RADIUS = 0.024;
20 // 58 mm
21 constexpr double WII_DVD_OUTER_RADIUS = 0.058;
22 // 38 mm
23 constexpr double GC_DVD_OUTER_RADIUS = 0.038;
24 // 0.74 um
25 constexpr double DVD_TRACK_PITCH = 0.00000074;
26
27 // Approximate read speeds at the inner and outer locations of Wii and GC
28 // discs. These speeds are approximations of speeds measured on real Wiis.
29 constexpr double GC_DISC_INNER_READ_SPEED = 1024 * 1024 * 2.1; // bytes/s
30 constexpr double GC_DISC_OUTER_READ_SPEED = 1024 * 1024 * 3.325; // bytes/s
31 constexpr double WII_DISC_INNER_READ_SPEED = 1024 * 1024 * 3.48; // bytes/s
32 constexpr double WII_DISC_OUTER_READ_SPEED = 1024 * 1024 * 8.41; // bytes/s
33
34 // The speed at which discs rotate. These have not been directly measured on hardware -
35 // rather, the read speeds above have been matched to the closest standard DVD speed
36 // (3x for GC and 6x for Wii) and the rotational speeds of those have been used.
37 constexpr double GC_ROTATIONS_PER_SECOND = 28.5;
38 constexpr double WII_ROTATIONS_PER_SECOND = 57;
39
40 // Experimentally measured seek constants. The time to seek appears to be
41 // linear, but short seeks appear to be lower velocity.
42 constexpr double SHORT_SEEK_MAX_DISTANCE = 0.001; // 1 mm
43 constexpr double SHORT_SEEK_CONSTANT = 0.035; // seconds
44 constexpr double SHORT_SEEK_VELOCITY_INVERSE = 50; // inverse: s/m
45 constexpr double LONG_SEEK_CONSTANT = 0.075; // seconds
46 constexpr double LONG_SEEK_VELOCITY_INVERSE = 4.5; // inverse: s/m
47
48 // We can approximate the relationship between a byte offset on disc and its
49 // radial distance from the center by using an approximation for the length of
50 // a rolled material, which is the area of the material divided by the pitch
51 // (ie: assume that you can squish and deform the area of the disc into a
52 // rectangle as thick as the track pitch).
53 //
54 // In practice this yields good-enough numbers as a more exact formula
55 // involving the integral over a polar equation (too complex to describe here)
56 // or the approximation of a DVD as a set of concentric circles (which is a
57 // better approximation, but makes futher derivations more complicated than
58 // they need to be).
59 //
60 // From the area approximation, we end up with this formula:
61 //
62 // L = pi*(r.outer^2-r.inner^2)/pitch
63 //
64 // Where:
65 // L = the data track's physical length
66 // r.{inner,outer} = the inner/outer radii (24 mm and 58 mm)
67 // pitch = the track pitch (.74 um)
68 //
69 // We can then use this equation to compute the radius for a given sector in
70 // the disc by mapping it along the length to a linear position and inverting
71 // the equation and solving for r.outer (using the DVD's r.inner and pitch)
72 // given that linear position:
73 //
74 // r.outer = sqrt(L * pitch / pi + r.inner^2)
75 //
76 // Where:
77 // L = the offset's linear position, as offset/density
78 // r.outer = the radius for the offset
79 // r.inner and pitch are the same as before.
80 //
81 // The data density of the disc is just the number of bytes addressable on a
82 // DVD, divided by the spiral length holding that data. offset/density yields
83 // the linear position for a given offset.
84 //
85 // When we put it all together and simplify, we can compute the radius for a
86 // given byte offset as a drastically simplified:
87 //
88 // r = sqrt(offset/total_bytes*(r.outer^2-r.inner^2) + r.inner^2)
CalculatePhysicalDiscPosition(u64 offset)89 double CalculatePhysicalDiscPosition(u64 offset)
90 {
91 // Just in case someone has an overly large disc image
92 // that can't exist in reality...
93 offset %= WII_DISC_LAYER_SIZE * 2;
94
95 // Assumption: the layout on the second disc layer is opposite of the first,
96 // ie layer 2 starts where layer 1 ends and goes backwards.
97 if (offset > WII_DISC_LAYER_SIZE)
98 offset = WII_DISC_LAYER_SIZE * 2 - offset;
99
100 // Note that because Wii and GC discs have identical data densities
101 // we can simply use the Wii numbers in both cases
102 return std::sqrt(
103 static_cast<double>(offset) / WII_DISC_LAYER_SIZE *
104 (WII_DVD_OUTER_RADIUS * WII_DVD_OUTER_RADIUS - DVD_INNER_RADIUS * DVD_INNER_RADIUS) +
105 DVD_INNER_RADIUS * DVD_INNER_RADIUS);
106 }
107
108 // Returns the time in seconds to move the read head from one offset to
109 // another, plus the number of ticks to read one ECC block immediately
110 // afterwards. Based on hardware testing, this appears to be a function of the
111 // linear distance between the radius of the first and second positions on the
112 // disc, though the head speed varies depending on the length of the seek.
CalculateSeekTime(u64 offset_from,u64 offset_to)113 double CalculateSeekTime(u64 offset_from, u64 offset_to)
114 {
115 const double position_from = CalculatePhysicalDiscPosition(offset_from);
116 const double position_to = CalculatePhysicalDiscPosition(offset_to);
117
118 // Seek time is roughly linear based on head distance travelled
119 const double distance = fabs(position_from - position_to);
120
121 if (distance < SHORT_SEEK_MAX_DISTANCE)
122 return distance * SHORT_SEEK_VELOCITY_INVERSE + SHORT_SEEK_CONSTANT;
123 else
124 return distance * LONG_SEEK_VELOCITY_INVERSE + LONG_SEEK_CONSTANT;
125 }
126
127 // Returns the time in seconds it takes for the disc to spin to the angle where the
128 // read head is over the given offset, starting from the given time in seconds.
CalculateRotationalLatency(u64 offset,double time,bool wii_disc)129 double CalculateRotationalLatency(u64 offset, double time, bool wii_disc)
130 {
131 // The data track on the disc is modelled as an Archimedean spiral.
132
133 // We assume that the inserted disc is spinning constantly, which
134 // is not always true (especially not when no disc is inserted),
135 // but doesn't lead to any problems in practice. It's as if every
136 // newly inserted disc is inserted at such an angle that it ends
137 // up rotating identically to a disc inserted at boot.
138
139 // To make the calculations simpler, the angles go up to 1, not tau.
140 // But tau (or anything else) would also work.
141 constexpr double MAX_ANGLE = 1;
142
143 const double rotations_per_second = wii_disc ? WII_ROTATIONS_PER_SECOND : GC_ROTATIONS_PER_SECOND;
144
145 const double target_angle =
146 fmod(CalculatePhysicalDiscPosition(offset) / DVD_TRACK_PITCH * MAX_ANGLE, MAX_ANGLE);
147 const double start_angle = fmod(time * rotations_per_second * MAX_ANGLE, MAX_ANGLE);
148
149 // MAX_ANGLE is added so that fmod can't get a negative argument
150 const double angle_diff = fmod(target_angle + MAX_ANGLE - start_angle, MAX_ANGLE);
151
152 const double result = angle_diff / MAX_ANGLE / rotations_per_second;
153
154 DEBUG_LOG(DVDINTERFACE, "Rotational latency: %lf ms", result * 1000);
155
156 return result;
157 }
158
159 // Returns the time in seconds it takes to read an amount of data from a disc,
160 // ignoring factors such as seek times. This is the streaming rate of the
161 // drive and varies between ~3-8MiB/s for Wii discs. Note that there is technically
162 // a DMA delay on top of this, but we model that as part of this read time.
CalculateRawDiscReadTime(u64 offset,u64 length,bool wii_disc)163 double CalculateRawDiscReadTime(u64 offset, u64 length, bool wii_disc)
164 {
165 // The Wii/GC have a CAV drive and the data has a constant pit length
166 // regardless of location on disc. This means we can linearly interpolate
167 // speed from the inner to outer radius. This matches a hardware test.
168 // We're just picking a point halfway into the read as our benchmark for
169 // read speed as speeds don't change materially in this small window.
170 const double physical_offset = CalculatePhysicalDiscPosition(offset + length / 2);
171
172 double speed;
173 if (wii_disc)
174 {
175 speed = (physical_offset - DVD_INNER_RADIUS) / (WII_DVD_OUTER_RADIUS - DVD_INNER_RADIUS) *
176 (WII_DISC_OUTER_READ_SPEED - WII_DISC_INNER_READ_SPEED) +
177 WII_DISC_INNER_READ_SPEED;
178 }
179 else
180 {
181 speed = (physical_offset - DVD_INNER_RADIUS) / (GC_DVD_OUTER_RADIUS - DVD_INNER_RADIUS) *
182 (GC_DISC_OUTER_READ_SPEED - GC_DISC_INNER_READ_SPEED) +
183 GC_DISC_INNER_READ_SPEED;
184 }
185
186 DEBUG_LOG(DVDINTERFACE, "Read 0x%" PRIx64 " @ 0x%" PRIx64 " @%lf mm: %lf us, %lf MiB/s", length,
187 offset, physical_offset * 1000, length / speed * 1000 * 1000, speed / 1024 / 1024);
188
189 return length / speed;
190 }
191
192 } // namespace DVDMath
193