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