├── .clang-format
├── .gitignore
├── LICENSE
├── README.md
├── docs
├── gap-comparison.png
└── gap.png
├── nds-interp.sln
└── nds-interp
├── data
├── .gitignore
├── README.md
├── data.7z
├── images
│ ├── BL.7z
│ ├── BR.7z
│ ├── TL.7z
│ └── TR.7z
└── src
│ ├── .gitignore
│ ├── Makefile
│ └── source
│ └── main.cpp
├── main.cpp
├── nds-interp.vcxproj
├── nds-interp.vcxproj.filters
└── slope.h
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | # BasedOnStyle: LLVM
4 | AccessModifierOffset: -4
5 | AlignAfterOpenBracket: Align
6 | AlignConsecutiveAssignments: false
7 | AlignConsecutiveDeclarations: false
8 | AlignEscapedNewlines: Left
9 | AlignOperands: true
10 | AlignTrailingComments: true
11 | AllowAllParametersOfDeclarationOnNextLine: true
12 | AllowShortBlocksOnASingleLine: false
13 | AllowShortCaseLabelsOnASingleLine: true
14 | AllowShortFunctionsOnASingleLine: All
15 | AllowShortIfStatementsOnASingleLine: true
16 | AllowShortLoopsOnASingleLine: false
17 | AlwaysBreakAfterDefinitionReturnType: None
18 | AlwaysBreakAfterReturnType: None
19 | AlwaysBreakBeforeMultilineStrings: false
20 | AlwaysBreakTemplateDeclarations: true
21 | BinPackArguments: true
22 | BinPackParameters: true
23 | BraceWrapping:
24 | AfterClass: false
25 | AfterControlStatement: false
26 | AfterEnum: false
27 | AfterFunction: false
28 | AfterNamespace: false
29 | AfterObjCDeclaration: false
30 | AfterStruct: false
31 | AfterUnion: false
32 | AfterExternBlock: false
33 | BeforeCatch: false
34 | BeforeElse: false
35 | IndentBraces: false
36 | SplitEmptyFunction: true
37 | SplitEmptyRecord: true
38 | SplitEmptyNamespace: true
39 | BreakBeforeBinaryOperators: None
40 | BreakBeforeBraces: Attach
41 | BreakBeforeInheritanceComma: false
42 | BreakBeforeTernaryOperators: true
43 | BreakConstructorInitializersBeforeComma: true
44 | BreakConstructorInitializers: BeforeColon
45 | BreakAfterJavaFieldAnnotations: false
46 | BreakStringLiterals: true
47 | ColumnLimit: 120
48 | CommentPragmas: '^ IWYU pragma:'
49 | CompactNamespaces: false
50 | ConstructorInitializerAllOnOneLineOrOnePerLine: false
51 | ConstructorInitializerIndentWidth: 4
52 | ContinuationIndentWidth: 4
53 | Cpp11BracedListStyle: true
54 | DerivePointerAlignment: false
55 | DisableFormat: false
56 | ExperimentalAutoDetectBinPacking: false
57 | FixNamespaceComments: true
58 | ForEachMacros:
59 | - foreach
60 | - Q_FOREACH
61 | - BOOST_FOREACH
62 | IncludeBlocks: Preserve
63 | IncludeCategories:
64 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
65 | Priority: 2
66 | - Regex: '^(<|"(gtest|gmock|isl|json)/)'
67 | Priority: 3
68 | - Regex: '.*'
69 | Priority: 1
70 | IncludeIsMainRegex: '(Test)?$'
71 | IndentCaseLabels: false
72 | IndentPPDirectives: BeforeHash
73 | IndentWidth: 4
74 | IndentWrappedFunctionNames: false
75 | JavaScriptQuotes: Leave
76 | JavaScriptWrapImports: true
77 | KeepEmptyLinesAtTheStartOfBlocks: true
78 | MacroBlockBegin: ''
79 | MacroBlockEnd: ''
80 | MaxEmptyLinesToKeep: 1
81 | NamespaceIndentation: Inner
82 | ObjCBlockIndentWidth: 2
83 | ObjCSpaceAfterProperty: false
84 | ObjCSpaceBeforeProtocolList: true
85 | PenaltyBreakAssignment: 2
86 | PenaltyBreakBeforeFirstCallParameter: 19
87 | PenaltyBreakComment: 300
88 | PenaltyBreakFirstLessLess: 120
89 | PenaltyBreakString: 1000
90 | PenaltyExcessCharacter: 1000000
91 | PenaltyReturnTypeOnItsOwnLine: 60
92 | PointerAlignment: Right
93 | ReflowComments: true
94 | SortIncludes: true
95 | SortUsingDeclarations: true
96 | SpaceAfterCStyleCast: false
97 | SpaceAfterTemplateKeyword: true
98 | SpaceBeforeAssignmentOperators: true
99 | SpaceBeforeParens: ControlStatements
100 | SpaceInEmptyParentheses: false
101 | SpacesBeforeTrailingComments: 1
102 | SpacesInAngles: false
103 | SpacesInContainerLiterals: true
104 | SpacesInCStyleCastParentheses: false
105 | SpacesInParentheses: false
106 | SpacesInSquareBrackets: false
107 | Standard: c++20
108 | TabWidth: 4
109 | UseTab: Never
110 | ...
111 |
112 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Compiled Static libraries
20 | *.lai
21 | *.la
22 | *.a
23 | *.lib
24 |
25 | # Executables
26 | *.exe
27 | *.out
28 | *.app
29 |
30 | # CMake build folder
31 | build/
32 | cmake-build-*/
33 |
34 | # CLion project metadata
35 | .idea/
36 |
37 | ## Ignore Visual Studio temporary files, build results, and
38 | ## files generated by popular Visual Studio add-ons.
39 | ##
40 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
41 |
42 | # User-specific files
43 | *.rsuser
44 | *.suo
45 | *.user
46 | *.userosscache
47 | *.sln.docstates
48 |
49 | # User-specific files (MonoDevelop/Xamarin Studio)
50 | *.userprefs
51 |
52 | # Mono auto generated files
53 | mono_crash.*
54 |
55 | # Build results
56 | [Dd]ebug/
57 | [Dd]ebugPublic/
58 | [Rr]elease/
59 | [Rr]eleases/
60 | x64/
61 | x86/
62 | [Ww][Ii][Nn]32/
63 | [Aa][Rr][Mm]/
64 | [Aa][Rr][Mm]64/
65 | bld/
66 | [Bb]in/
67 | [Oo]bj/
68 | [Ll]og/
69 | [Ll]ogs/
70 |
71 | # Visual Studio 2015/2017 cache/options directory
72 | .vs/
73 | # Uncomment if you have tasks that create the project's static files in wwwroot
74 | #wwwroot/
75 |
76 | # Visual Studio 2017 auto generated files
77 | Generated\ Files/
78 |
79 | # MSTest test Results
80 | [Tt]est[Rr]esult*/
81 | [Bb]uild[Ll]og.*
82 |
83 | # NUnit
84 | *.VisualState.xml
85 | TestResult.xml
86 | nunit-*.xml
87 |
88 | # Build Results of an ATL Project
89 | [Dd]ebugPS/
90 | [Rr]eleasePS/
91 | dlldata.c
92 |
93 | # Benchmark Results
94 | BenchmarkDotNet.Artifacts/
95 |
96 | # .NET Core
97 | project.lock.json
98 | project.fragment.lock.json
99 | artifacts/
100 |
101 | # ASP.NET Scaffolding
102 | ScaffoldingReadMe.txt
103 |
104 | # StyleCop
105 | StyleCopReport.xml
106 |
107 | # Files built by Visual Studio
108 | *_i.c
109 | *_p.c
110 | *_h.h
111 | *.ilk
112 | *.meta
113 | *.obj
114 | *.iobj
115 | *.pch
116 | *.pdb
117 | *.ipdb
118 | *.pgc
119 | *.pgd
120 | *.rsp
121 | *.sbr
122 | *.tlb
123 | *.tli
124 | *.tlh
125 | *.tmp
126 | *.tmp_proj
127 | *_wpftmp.csproj
128 | *.log
129 | *.tlog
130 | *.vspscc
131 | *.vssscc
132 | .builds
133 | *.pidb
134 | *.svclog
135 | *.scc
136 |
137 | # Chutzpah Test files
138 | _Chutzpah*
139 |
140 | # Visual C++ cache files
141 | ipch/
142 | *.aps
143 | *.ncb
144 | *.opendb
145 | *.opensdf
146 | *.sdf
147 | *.cachefile
148 | *.VC.db
149 | *.VC.VC.opendb
150 |
151 | # Visual Studio profiler
152 | *.psess
153 | *.vsp
154 | *.vspx
155 | *.sap
156 |
157 | # Visual Studio Trace Files
158 | *.e2e
159 |
160 | # TFS 2012 Local Workspace
161 | $tf/
162 |
163 | # Guidance Automation Toolkit
164 | *.gpState
165 |
166 | # ReSharper is a .NET coding add-in
167 | _ReSharper*/
168 | *.[Rr]e[Ss]harper
169 | *.DotSettings.user
170 |
171 | # TeamCity is a build add-in
172 | _TeamCity*
173 |
174 | # DotCover is a Code Coverage Tool
175 | *.dotCover
176 |
177 | # AxoCover is a Code Coverage Tool
178 | .axoCover/*
179 | !.axoCover/settings.json
180 |
181 | # Coverlet is a free, cross platform Code Coverage Tool
182 | coverage*.json
183 | coverage*.xml
184 | coverage*.info
185 |
186 | # Visual Studio code coverage results
187 | *.coverage
188 | *.coveragexml
189 |
190 | # NCrunch
191 | _NCrunch_*
192 | .*crunch*.local.xml
193 | nCrunchTemp_*
194 |
195 | # MightyMoose
196 | *.mm.*
197 | AutoTest.Net/
198 |
199 | # Web workbench (sass)
200 | .sass-cache/
201 |
202 | # Installshield output folder
203 | [Ee]xpress/
204 |
205 | # DocProject is a documentation generator add-in
206 | DocProject/buildhelp/
207 | DocProject/Help/*.HxT
208 | DocProject/Help/*.HxC
209 | DocProject/Help/*.hhc
210 | DocProject/Help/*.hhk
211 | DocProject/Help/*.hhp
212 | DocProject/Help/Html2
213 | DocProject/Help/html
214 |
215 | # Click-Once directory
216 | publish/
217 |
218 | # Publish Web Output
219 | *.[Pp]ublish.xml
220 | *.azurePubxml
221 | # Note: Comment the next line if you want to checkin your web deploy settings,
222 | # but database connection strings (with potential passwords) will be unencrypted
223 | *.pubxml
224 | *.publishproj
225 |
226 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
227 | # checkin your Azure Web App publish settings, but sensitive information contained
228 | # in these scripts will be unencrypted
229 | PublishScripts/
230 |
231 | # NuGet Packages
232 | *.nupkg
233 | # NuGet Symbol Packages
234 | *.snupkg
235 | # The packages folder can be ignored because of Package Restore
236 | **/[Pp]ackages/*
237 | # except build/, which is used as an MSBuild target.
238 | !**/[Pp]ackages/build/
239 | # Uncomment if necessary however generally it will be regenerated when needed
240 | #!**/[Pp]ackages/repositories.config
241 | # NuGet v3's project.json files produces more ignorable files
242 | *.nuget.props
243 | *.nuget.targets
244 |
245 | # Nuget personal access tokens and Credentials
246 | nuget.config
247 |
248 | # Microsoft Azure Build Output
249 | csx/
250 | *.build.csdef
251 |
252 | # Microsoft Azure Emulator
253 | ecf/
254 | rcf/
255 |
256 | # Windows Store app package directories and files
257 | AppPackages/
258 | BundleArtifacts/
259 | Package.StoreAssociation.xml
260 | _pkginfo.txt
261 | *.appx
262 | *.appxbundle
263 | *.appxupload
264 |
265 | # Visual Studio cache files
266 | # files ending in .cache can be ignored
267 | *.[Cc]ache
268 | # but keep track of directories ending in .cache
269 | !?*.[Cc]ache/
270 |
271 | # Others
272 | ClientBin/
273 | ~$*
274 | *~
275 | *.dbmdl
276 | *.dbproj.schemaview
277 | *.jfm
278 | *.pfx
279 | *.publishsettings
280 | orleans.codegen.cs
281 |
282 | # Including strong name files can present a security risk
283 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
284 | #*.snk
285 |
286 | # Since there are multiple workflows, uncomment next line to ignore bower_components
287 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
288 | #bower_components/
289 |
290 | # RIA/Silverlight projects
291 | Generated_Code/
292 |
293 | # Backup & report files from converting an old project file
294 | # to a newer Visual Studio version. Backup files are not needed,
295 | # because we have git ;-)
296 | _UpgradeReport_Files/
297 | Backup*/
298 | UpgradeLog*.XML
299 | UpgradeLog*.htm
300 | ServiceFabricBackup/
301 | *.rptproj.bak
302 |
303 | # SQL Server files
304 | *.mdf
305 | *.ldf
306 | *.ndf
307 |
308 | # Business Intelligence projects
309 | *.rdl.data
310 | *.bim.layout
311 | *.bim_*.settings
312 | *.rptproj.rsuser
313 | *- [Bb]ackup.rdl
314 | *- [Bb]ackup ([0-9]).rdl
315 | *- [Bb]ackup ([0-9][0-9]).rdl
316 |
317 | # Microsoft Fakes
318 | FakesAssemblies/
319 |
320 | # GhostDoc plugin setting file
321 | *.GhostDoc.xml
322 |
323 | # Node.js Tools for Visual Studio
324 | .ntvs_analysis.dat
325 | node_modules/
326 |
327 | # Visual Studio 6 build log
328 | *.plg
329 |
330 | # Visual Studio 6 workspace options file
331 | *.opt
332 |
333 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
334 | *.vbw
335 |
336 | # Visual Studio LightSwitch build output
337 | **/*.HTMLClient/GeneratedArtifacts
338 | **/*.DesktopClient/GeneratedArtifacts
339 | **/*.DesktopClient/ModelManifest.xml
340 | **/*.Server/GeneratedArtifacts
341 | **/*.Server/ModelManifest.xml
342 | _Pvt_Extensions
343 |
344 | # Paket dependency manager
345 | .paket/paket.exe
346 | paket-files/
347 |
348 | # FAKE - F# Make
349 | .fake/
350 |
351 | # CodeRush personal settings
352 | .cr/personal
353 |
354 | # Python Tools for Visual Studio (PTVS)
355 | __pycache__/
356 | *.pyc
357 |
358 | # Cake - Uncomment if you are using it
359 | # tools/**
360 | # !tools/packages.config
361 |
362 | # Tabs Studio
363 | *.tss
364 |
365 | # Telerik's JustMock configuration file
366 | *.jmconfig
367 |
368 | # BizTalk build output
369 | *.btp.cs
370 | *.btm.cs
371 | *.odx.cs
372 | *.xsd.cs
373 |
374 | # OpenCover UI analysis results
375 | OpenCover/
376 |
377 | # Azure Stream Analytics local run output
378 | ASALocalRun/
379 |
380 | # MSBuild Binary and Structured Log
381 | *.binlog
382 |
383 | # NVidia Nsight GPU debugger configuration file
384 | *.nvuser
385 |
386 | # MFractors (Xamarin productivity tool) working folder
387 | .mfractor/
388 |
389 | # Local History for Visual Studio
390 | .localhistory/
391 |
392 | # BeatPulse healthcheck temp database
393 | healthchecksdb
394 |
395 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
396 | MigrationBackup/
397 |
398 | # Ionide (cross platform F# VS Code tools) working folder
399 | .ionide/
400 |
401 | # Fody - auto-generated XML schema
402 | FodyWeavers.xsd
403 |
404 | # VS Code files for those working on multiple tools
405 | .vscode/*
406 | !.vscode/settings.json
407 | !.vscode/tasks.json
408 | !.vscode/launch.json
409 | !.vscode/extensions.json
410 | *.code-workspace
411 |
412 | # Local History for Visual Studio Code
413 | .history/
414 |
415 | # Windows Installer files from build outputs
416 | *.cab
417 | *.msi
418 | *.msix
419 | *.msm
420 | *.msp
421 |
422 | # JetBrains Rider
423 | .idea/
424 | *.sln.iml
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Ivan Roberto de Oliveira
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nds-interp
2 |
3 | This is a pixel-perfect implementation of the Nintendo DS's interpolation algorithm for slopes. Every possible slope is generated with perfect accuracy, including the gaps that occasionally appear in some slopes.
4 |
5 | ## Nintendo DS slope interpolation
6 |
7 | The Nintendo DS hardware uses 32-bit integers with 18-bit fractional parts throughout the interpolation process, with one notable exception in X-major slopes.
8 |
9 | To calculate the X increment per scanline (
), the hardware first computes the reciprocal of Y1-Y0 then multiplies the result by X1-X0. This order of operations avoids a multiplication overflow at the cost of precision on the division.
10 |
11 | For X-major lines, the interpolator produces line spans for each scanline. The start of the span is calculated by first offseting the Y coordinate to the Y0..Y1 range (subtracting Y0 from Y), then multiplying the offset Y by
, adding the X0 offset and finally a +0.5 bias. The end on the span is computed based on its starting coordinate, discarding (masking out) the 9 least significant bits (which could be seen as rounding down, or the floor function), then adding
and subtracting 1.0. The exact algorithm is unknown, but it is possible that the starting coordinate is shifted right by 9 for an intermediate calculation then shifted back left by 9 to restore the fractional part.
12 |
13 | The formulae for determining the starting and ending X coordinates of a span of an X-major slope are:
14 |
15 | >
\
16 | > \
17 | >
\
18 | > \
19 | >
20 |
21 | Due to the 9 least significant bits being discarded, certain X-major slopes (such as 69x49, 70x66, 71x49 and more) display a one-pixel gap on hardware (as seen below). This is calculated accurately with the formulae above.
22 |
23 | 
24 |
25 | Y-major slopes contain only one pixel per scanline. The formula for interpolating the X coordinate based on the Y coordinate is very similar to that of the X-major interpolation, with the only difference being that the +0.5 bias is not applied.
26 |
27 | >
28 |
29 | Note that there is no need to compute a span as there's always going to be only one pixel per scanline. Also, there are no one-pixel gaps on Y-major lines since the Nintendo DS's rasterizer is scanline-based and the interpolation is computed on every scanline.
30 |
31 | Negative slopes work in a similar fashion. In fact, negative slopes perfectly match their positive counterparts down to the one-pixel gaps which happen in exactly the same spots. The gaps in negative slopes are to the left of a span, while in positive slopes the gaps are to the right of a span, as shown below (rows 34 to 41 of 69x49 slopes):
32 |
33 | 
34 |
35 | ## Building and running
36 |
37 | This project is a Visual Studio solution, but the code should easily compile on other platforms with a C++17 compiler. The NDS ROMs used to generate the data files as well as their source code is located in the [nds-interp/data/src](nds-interp/data/src) folder. See its [README](nds-interp/data#readme) for more details.
38 |
39 | The program requires the slope data captured from a Nintendo DS, DS Lite, DSi or 3DS, which you can find in [`nds-interp/data/data.7z`](nds-interp/data/data.7z). Simply extract that file into the containing folder and you should be good to go, if you're using Visual Studio. On other IDEs or platforms you might have to set the working directory to the folder containing the `data` folder (i.e. `nds-interp`).
40 |
--------------------------------------------------------------------------------
/docs/gap-comparison.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrikerX3/nds-interp/e3acde4c35ac53e628a2548238870a287409733e/docs/gap-comparison.png
--------------------------------------------------------------------------------
/docs/gap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrikerX3/nds-interp/e3acde4c35ac53e628a2548238870a287409733e/docs/gap.png
--------------------------------------------------------------------------------
/nds-interp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31321.278
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nds-interp", "nds-interp\nds-interp.vcxproj", "{95B244C4-FC99-45E0-9629-99FC4B5ED09B}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x64 = Debug|x64
11 | Debug|x86 = Debug|x86
12 | Release|x64 = Release|x64
13 | Release|x86 = Release|x86
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {95B244C4-FC99-45E0-9629-99FC4B5ED09B}.Debug|x64.ActiveCfg = Debug|x64
17 | {95B244C4-FC99-45E0-9629-99FC4B5ED09B}.Debug|x64.Build.0 = Debug|x64
18 | {95B244C4-FC99-45E0-9629-99FC4B5ED09B}.Debug|x86.ActiveCfg = Debug|Win32
19 | {95B244C4-FC99-45E0-9629-99FC4B5ED09B}.Debug|x86.Build.0 = Debug|Win32
20 | {95B244C4-FC99-45E0-9629-99FC4B5ED09B}.Release|x64.ActiveCfg = Release|x64
21 | {95B244C4-FC99-45E0-9629-99FC4B5ED09B}.Release|x64.Build.0 = Release|x64
22 | {95B244C4-FC99-45E0-9629-99FC4B5ED09B}.Release|x86.ActiveCfg = Release|Win32
23 | {95B244C4-FC99-45E0-9629-99FC4B5ED09B}.Release|x86.Build.0 = Release|Win32
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {AE0589D4-66C8-4B10-9CEB-9DC27290DB6F}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/nds-interp/data/.gitignore:
--------------------------------------------------------------------------------
1 | *.bin
2 |
--------------------------------------------------------------------------------
/nds-interp/data/README.md:
--------------------------------------------------------------------------------
1 | `data.7z` contains the binary files used to validate the line interpolation. These were generated from the programs in the `bin` folder, which were built with [devkitARM](https://devkitpro.org/wiki/Getting_Started) from the sources in `src`. The files contain a simplified screen dump of the lines produced by every possible interpolation, with the line origin at the top left (TL), top right (TR), bottom left (BL) or bottom right (BR). In order to run the main program you'll need to extract the contents of `data.7z` into this directory.
2 |
3 | At the top of [`src/source/main.cpp`](src/source/main.cpp) there are some adjustable parameters. The program can be configured to either generate or validate line interpolation through the `generateData` flag. In validation mode, the program expects to find a `data.bin` file in NitroFS. In order to embed the generated test data, simply create a `nitrofiles` folder in this directory, copy the desired data file into it and call it `data.bin`. The other parameters allow adjusting the origin of the line (top left, top right, bottom left or bottom right) and the extent of the area to test, which are only used when generating the data files. By default, the program will generate lines spanning the entire screen. You can also take a screen capture (a VRAM dump) of the last frame with `screencap` -- this will generate a file named `linetest-screencap.bin` which can then be converted to a TGA file with the main program at the root of this repository.
4 |
5 | The `images` folder contains compressed files that contain screen captures of every possible slope the Nintendo DS can generate for each of the four origin points.
6 |
--------------------------------------------------------------------------------
/nds-interp/data/data.7z:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrikerX3/nds-interp/e3acde4c35ac53e628a2548238870a287409733e/nds-interp/data/data.7z
--------------------------------------------------------------------------------
/nds-interp/data/images/BL.7z:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrikerX3/nds-interp/e3acde4c35ac53e628a2548238870a287409733e/nds-interp/data/images/BL.7z
--------------------------------------------------------------------------------
/nds-interp/data/images/BR.7z:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrikerX3/nds-interp/e3acde4c35ac53e628a2548238870a287409733e/nds-interp/data/images/BR.7z
--------------------------------------------------------------------------------
/nds-interp/data/images/TL.7z:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrikerX3/nds-interp/e3acde4c35ac53e628a2548238870a287409733e/nds-interp/data/images/TL.7z
--------------------------------------------------------------------------------
/nds-interp/data/images/TR.7z:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StrikerX3/nds-interp/e3acde4c35ac53e628a2548238870a287409733e/nds-interp/data/images/TR.7z
--------------------------------------------------------------------------------
/nds-interp/data/src/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | *.elf
3 | *.nds
4 |
--------------------------------------------------------------------------------
/nds-interp/data/src/Makefile:
--------------------------------------------------------------------------------
1 | #---------------------------------------------------------------------------------
2 | .SUFFIXES:
3 | #---------------------------------------------------------------------------------
4 |
5 | ifeq ($(strip $(DEVKITARM)),)
6 | $(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM")
7 | endif
8 |
9 | include $(DEVKITARM)/ds_rules
10 |
11 | #---------------------------------------------------------------------------------
12 | # TARGET is the name of the output
13 | # BUILD is the directory where object files & intermediate files will be placed
14 | # SOURCES is a list of directories containing source code
15 | # INCLUDES is a list of directories containing extra header files
16 | #---------------------------------------------------------------------------------
17 | TARGET := $(shell basename $(CURDIR))
18 | BUILD := build
19 | SOURCES := source
20 | DATA := data
21 | INCLUDES := include
22 | NITRODATA := nitrofiles
23 |
24 | #---------------------------------------------------------------------------------
25 | # options for code generation
26 | #---------------------------------------------------------------------------------
27 | ARCH := -mthumb -mthumb-interwork -march=armv5te -mtune=arm946e-s
28 |
29 | CFLAGS := -g -Wall -O2\
30 | -fomit-frame-pointer\
31 | -ffast-math \
32 | $(ARCH)
33 |
34 | CFLAGS += $(INCLUDE) -DARM9
35 | CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
36 |
37 | ASFLAGS := -g $(ARCH)
38 | LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
39 |
40 | #---------------------------------------------------------------------------------
41 | # any extra libraries we wish to link with the project (order is important)
42 | #---------------------------------------------------------------------------------
43 | LIBS := -lfilesystem -lfat -lnds9
44 |
45 |
46 | #---------------------------------------------------------------------------------
47 | # list of directories containing libraries, this must be the top level containing
48 | # include and lib
49 | #---------------------------------------------------------------------------------
50 | LIBDIRS := $(LIBNDS)
51 |
52 | #---------------------------------------------------------------------------------
53 | # no real need to edit anything past this point unless you need to add additional
54 | # rules for different file extensions
55 | #---------------------------------------------------------------------------------
56 | ifneq ($(BUILD),$(notdir $(CURDIR)))
57 | #---------------------------------------------------------------------------------
58 | export TOPDIR := $(CURDIR)
59 |
60 | export OUTPUT := $(CURDIR)/$(TARGET)
61 |
62 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
63 | $(foreach dir,$(DATA),$(CURDIR)/$(dir))
64 |
65 | export DEPSDIR := $(CURDIR)/$(BUILD)
66 |
67 | ifneq ($(strip $(NITRODATA)),)
68 | export NITRO_FILES := $(CURDIR)/$(NITRODATA)
69 | endif
70 |
71 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
72 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
73 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
74 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
75 |
76 | #---------------------------------------------------------------------------------
77 | # use CXX for linking C++ projects, CC for standard C
78 | #---------------------------------------------------------------------------------
79 | ifeq ($(strip $(CPPFILES)),)
80 | #---------------------------------------------------------------------------------
81 | export LD := $(CC)
82 | #---------------------------------------------------------------------------------
83 | else
84 | #---------------------------------------------------------------------------------
85 | export LD := $(CXX)
86 | #---------------------------------------------------------------------------------
87 | endif
88 | #---------------------------------------------------------------------------------
89 |
90 | export OFILES := $(addsuffix .o,$(BINFILES)) \
91 | $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
92 |
93 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
94 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \
95 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \
96 | -I$(CURDIR)/$(BUILD)
97 |
98 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
99 |
100 | .PHONY: $(BUILD) clean
101 |
102 | #---------------------------------------------------------------------------------
103 | $(BUILD):
104 | @[ -d $@ ] || mkdir -p $@
105 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
106 |
107 | #---------------------------------------------------------------------------------
108 | clean:
109 | @echo clean ...
110 | @rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds $(TARGET).ds.gba
111 |
112 |
113 | #---------------------------------------------------------------------------------
114 | else
115 |
116 | DEPENDS := $(OFILES:.o=.d)
117 |
118 | #---------------------------------------------------------------------------------
119 | # main targets
120 | #---------------------------------------------------------------------------------
121 | $(OUTPUT).nds : $(OUTPUT).elf
122 | $(OUTPUT).nds : $(shell find $(TOPDIR)/$(NITRODATA))
123 | $(OUTPUT).elf : $(OFILES)
124 |
125 | #---------------------------------------------------------------------------------
126 | %.bin.o : %.bin
127 | #---------------------------------------------------------------------------------
128 | @echo $(notdir $<)
129 | $(bin2o)
130 |
131 | -include $(DEPSDIR)/*.d
132 |
133 | #---------------------------------------------------------------------------------------
134 | endif
135 | #---------------------------------------------------------------------------------------
136 |
--------------------------------------------------------------------------------
/nds-interp/data/src/source/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | constexpr int WIDTH = 256, HEIGHT = 192;
7 | constexpr int WIDTH2 = WIDTH / 2, HEIGHT2 = HEIGHT / 2;
8 | constexpr int X_DIFF = 0x3000 / WIDTH2, Y_DIFF = 0x3000 / HEIGHT2;
9 |
10 | constexpr int TEST_TOP_LEFT = 0;
11 | constexpr int TEST_TOP_RIGHT = 1;
12 | constexpr int TEST_BOTTOM_LEFT = 2;
13 | constexpr int TEST_BOTTOM_RIGHT = 3;
14 |
15 | // --- Configurable settings -----------------------------------------------------------------
16 |
17 | // Select the dataset you wish to generate from one of the constants above
18 | constexpr int TEST_TYPE = TEST_BOTTOM_RIGHT;
19 |
20 | // Specify the bounding box of the area to be tested
21 | constexpr u16 minX = 0;
22 | constexpr u16 maxX = WIDTH;
23 | constexpr u8 minY = 0;
24 | constexpr u8 maxY = HEIGHT;
25 |
26 | // Specify whether to take a screen capture of the last generated frame
27 | constexpr bool screencap = false;
28 |
29 | // Set to true to generate test data, or false to test/validate
30 | constexpr bool generateData = true;
31 |
32 | // -------------------------------------------------------------------------------------------
33 |
34 | template
35 | T min(T x, T y) {
36 | return (x < y) ? x : y;
37 | }
38 |
39 | int16_t originalVerts[3][3] = {
40 | { -WIDTH2 * X_DIFF, HEIGHT2 * Y_DIFF, 0 },
41 | { -WIDTH2 * X_DIFF, HEIGHT2 * Y_DIFF, 0 },
42 | { -WIDTH2 * X_DIFF, HEIGHT2 * Y_DIFF, 0 },
43 | };
44 |
45 | int clip[16] = {
46 | 0x1000, 0, 0, 0,
47 | 0, 0x1000, 0, 0,
48 | 0, 0, 0x1000, 0,
49 | 0, 0, 0, 0x3000,
50 | };
51 | uint8_t colors[3][3] = {
52 | { 218, 165, 32 },
53 | { 112,128,144 },
54 | { 173,255,47 },
55 | };
56 |
57 | int16_t verts[3][3];
58 |
59 | const char* cullStrs[3] = { "Front", "Back", "None" };
60 |
61 | void test(PrintConsole* pc);
62 | void generate(PrintConsole* pc);
63 | void draw(u8 r, u8 g, u8 b);
64 |
65 | int main() {
66 | PrintConsole* pc = consoleDemoInit();
67 |
68 | //set mode 0, enable BG0 and set it to 3D
69 | videoSetMode(MODE_0_3D);
70 |
71 | // initialize gl
72 | glInit();
73 |
74 | // setup the rear plane
75 | glClearColor(0,0,0,31); // BG must be opaque for AA to work
76 | glClearPolyID(63); // BG must have a unique polygon ID for AA to work
77 | glClearDepth(0x7FFF);
78 |
79 | //this should work the same as the normal gl call
80 | glViewport(0,0,255,191);
81 |
82 | m4x4 mat;
83 | memcpy(mat.m, clip, sizeof(clip));
84 | memcpy(verts, originalVerts, sizeof(originalVerts));
85 |
86 | glMatrixMode(GL_PROJECTION);
87 | glLoadMatrix4x4(&mat);
88 | glMatrixMode(GL_MODELVIEW);
89 | glLoadIdentity();
90 |
91 | int left = (TEST_TYPE & 1) ? WIDTH : 0;
92 | int top = (TEST_TYPE & 2) ? HEIGHT : 0;
93 | verts[0][0] += left * X_DIFF; verts[0][1] -= top * Y_DIFF;
94 | verts[1][0] += left * X_DIFF; verts[1][1] -= top * Y_DIFF;
95 | verts[2][0] += left * X_DIFF; verts[2][1] -= top * Y_DIFF;
96 |
97 | vramSetBankA(VRAM_A_LCD);
98 | REG_DISPCAPCNT = DCAP_MODE(DCAP_MODE_A)
99 | | DCAP_SRC_A(DCAP_SRC_A_3DONLY)
100 | | DCAP_SIZE(DCAP_SIZE_256x192)
101 | | DCAP_OFFSET(0)
102 | | DCAP_BANK(DCAP_BANK_VRAM_A);
103 |
104 | if (generateData) {
105 | generate(pc);
106 | } else {
107 | test(pc);
108 | }
109 |
110 | return 0;
111 | }
112 |
113 | void test(PrintConsole* pc) {
114 | nitroFSInit(NULL);
115 | FILE* file = fopen("data.bin", "rb");
116 | fseek(file, 0, SEEK_SET);
117 |
118 | char testType;
119 | fread(&testType, 1, sizeof(testType), file);
120 |
121 | u16 minX, maxX;
122 | u8 minY, maxY;
123 | fread(&minX, 1, sizeof(minX), file);
124 | fread(&maxX, 1, sizeof(maxX), file);
125 | fread(&minY, 1, sizeof(minY), file);
126 | fread(&maxY, 1, sizeof(maxY), file);
127 |
128 | glDisable(GL_ANTIALIAS);
129 | consoleClear();
130 | pc->cursorX = 0;
131 | pc->cursorY = 0;
132 | int prevX = 0, prevY = 0;
133 | for(int y = minY; y <= maxY; y++) {
134 | for(int x = minX; x <= maxX; x++) {
135 | swiWaitForVBlank();
136 | REG_DISPCAPCNT |= DCAP_ENABLE;
137 | verts[2][0] = originalVerts[2][0] + (x * X_DIFF);
138 | verts[2][1] = originalVerts[2][1] - (y * Y_DIFF);
139 |
140 | glPolyFmt(POLY_ALPHA(0) | POLY_CULL_NONE);
141 | draw(255, 255, 255);
142 | glFlush(0);
143 |
144 | while(REG_DISPCAPCNT & DCAP_ENABLE);
145 |
146 | u8 pos[2];
147 | fread(pos, 1, sizeof(pos), file);
148 | if(pos[0] != (u8)prevX || pos[1] != (u8)prevY) {
149 | printf("Invalid File\n");
150 | while(1);
151 | }
152 |
153 | int startY = (testType & 2) ? prevY : 0;
154 | int endY = (testType & 2) ? (HEIGHT - 1) : prevY;
155 | if (startY >= HEIGHT) startY = HEIGHT - 1;
156 | if (endY >= HEIGHT) endY = HEIGHT - 1;
157 | for(int checkY = startY; checkY <= endY; checkY++) {
158 | int startX = (testType & 1) ? prevX : 0;
159 | int endX = (testType & 1) ? (WIDTH - 1) : prevX;
160 | if (startX >= WIDTH) startX = WIDTH - 1;
161 | if (endX >= WIDTH) endX = WIDTH - 1;
162 |
163 | u8 first = 0, actualFirst;
164 | u8 last = endX, actualLast;
165 | bool found = false, actualFound;
166 |
167 | for(int checkX = startX; checkX <= endX; checkX++) {
168 | if (!found) {
169 | if(VRAM_A[checkY * WIDTH + checkX] & 0x7FFF) {
170 | found = true;
171 | first = checkX;
172 | }
173 | } else {
174 | if ((VRAM_A[checkY * WIDTH + checkX] & 0x7FFF) == 0) {
175 | last = checkX - 1;
176 | break;
177 | }
178 | }
179 | }
180 | fread(&actualFound, 1, sizeof(actualFound), file);
181 | fread(&actualFirst, 1, sizeof(actualFirst), file);
182 | fread(&actualLast, 1, sizeof(actualLast), file);
183 | if(found != actualFound) {
184 | if (found) printf("%ix%i Y=%i extra pixel\n", prevX, prevY, checkY);
185 | if (actualFound) printf("%ix%i Y=%i missing pixel\n", prevX, prevY, checkY);
186 | }
187 | if(found && actualFound && (first != actualFirst || last != actualLast)) {
188 | printf("%ix%i Y=%i %i-%i != %i-%i\n", prevX, prevY, checkY, first, last, actualFirst, actualLast);
189 | }
190 | }
191 | prevX = x;
192 | prevY = y;
193 | }
194 | }
195 |
196 | while(REG_DISPCAPCNT & DCAP_ENABLE);
197 | int startY = (testType & 2) ? prevY : 0;
198 | int endY = (testType & 2) ? (HEIGHT - 1) : prevY;
199 | if (startY >= HEIGHT) startY = HEIGHT - 1;
200 | if (endY >= HEIGHT) endY = HEIGHT - 1;
201 | for(int checkY = startY; checkY <= endY; checkY++) {
202 | int startX = (testType & 1) ? prevX : 0;
203 | int endX = (testType & 1) ? (WIDTH - 1) : prevX;
204 | if (startX >= WIDTH) startX = WIDTH - 1;
205 | if (endX >= WIDTH) endX = WIDTH - 1;
206 |
207 | u8 first = 0, actualFirst;
208 | u8 last = endX, actualLast;
209 | bool found = false, actualFound;
210 |
211 | for(int checkX = startX; checkX <= endX; checkX++) {
212 | if (!found) {
213 | if(VRAM_A[checkY * WIDTH + checkX] & 0x7FFF) {
214 | found = true;
215 | first = checkX;
216 | }
217 | } else {
218 | if ((VRAM_A[checkY * WIDTH + checkX] & 0x7FFF) == 0) {
219 | last = checkX - 1;
220 | break;
221 | }
222 | }
223 | }
224 | fread(&actualFound, 1, sizeof(actualFound), file);
225 | fread(&actualFirst, 1, sizeof(actualFirst), file);
226 | fread(&actualLast, 1, sizeof(actualLast), file);
227 | if(found != actualFound) {
228 | if (found) printf("%ix%i Y=%i extra pixel\n", prevX, prevY, checkY);
229 | if (actualFound) printf("%ix%i Y=%i missing pixel\n", prevX, prevY, checkY);
230 | }
231 | if(found && actualFound && (first != actualFirst || last != actualLast)) {
232 | printf("%ix%i Y=%i %i-%i != %i-%i\n", prevX, prevY, checkY, first, last, actualFirst, actualLast);
233 | }
234 | }
235 | fclose(file);
236 |
237 | while(1);
238 | }
239 |
240 | void generate(PrintConsole* pc) {
241 | fatInitDefault();
242 | const char *filename;
243 | switch (TEST_TYPE) {
244 | case TEST_TOP_LEFT: filename = "TL.bin"; break;
245 | case TEST_TOP_RIGHT: filename = "TR.bin"; break;
246 | case TEST_BOTTOM_LEFT: filename = "BL.bin"; break;
247 | case TEST_BOTTOM_RIGHT: filename = "BR.bin"; break;
248 | default: filename = "UNK.bin"; break;
249 | }
250 | FILE* file = fopen(filename, "wb");
251 | char testType = TEST_TYPE;
252 | fwrite(&testType, 1, sizeof(testType), file);
253 |
254 | fwrite(&minX, 1, sizeof(minX), file);
255 | fwrite(&maxX, 1, sizeof(maxX), file);
256 | fwrite(&minY, 1, sizeof(minY), file);
257 | fwrite(&maxY, 1, sizeof(maxY), file);
258 |
259 | auto countAndWrite = [&](int startX, int endX, int checkY) {
260 | u8 first = startX;
261 | u8 last = endX;
262 | bool found = false;
263 | for(int checkX = startX; checkX <= endX; checkX++) {
264 | u16 color = VRAM_A[checkY * WIDTH + checkX] & 0x7FFF;
265 | if (!found) {
266 | if (color != 0) {
267 | found = true;
268 | first = checkX;
269 | }
270 | } else {
271 | if (color == 0) {
272 | last = checkX - 1;
273 | break;
274 | }
275 | }
276 | }
277 | fwrite(&found, 1, sizeof(found), file);
278 | fwrite(&first, 1, sizeof(first), file);
279 | fwrite(&last, 1, sizeof(last), file);
280 | };
281 |
282 | auto drawFrame = [&](int x, int y) {
283 | swiWaitForVBlank();
284 | REG_DISPCAPCNT |= DCAP_ENABLE;
285 | pc->cursorX = 0;
286 | pc->cursorY = 0;
287 | consoleClear();
288 | printf("%i %i\n", x, y);
289 |
290 | verts[2][0] = originalVerts[2][0] + (x * X_DIFF);
291 | verts[2][1] = originalVerts[2][1] - (y * Y_DIFF);
292 | glPolyFmt(POLY_ALPHA(0) | POLY_CULL_NONE);
293 | draw(255, 255, 255);
294 | glFlush(0);
295 |
296 | while(REG_DISPCAPCNT & DCAP_ENABLE);
297 | };
298 |
299 | glDisable(GL_ANTIALIAS);
300 | glEnable(GL_BLEND);
301 |
302 | int prevX = 0, prevY = 0;
303 | for(int y = minY; y <= maxY; y++) {
304 | for(int x = minX; x <= maxX; x++) {
305 | drawFrame(x, y);
306 |
307 | u8 xCopy = prevX, yCopy = prevY;
308 | fwrite(&xCopy, 1, sizeof(xCopy), file); fwrite(&yCopy, 1, sizeof(yCopy), file);
309 |
310 | int startX = (TEST_TYPE & 1) ? min(prevX, WIDTH - 1) : 0;
311 | int endX = (TEST_TYPE & 1) ? (WIDTH - 1) : min(prevX, WIDTH - 1);
312 | int startY = (TEST_TYPE & 2) ? min(prevY, HEIGHT - 1) : 0;
313 | int endY = (TEST_TYPE & 2) ? (HEIGHT - 1) : min(prevY, HEIGHT - 1);
314 | for(int checkY = startY; checkY <= endY; checkY++) {
315 | countAndWrite(startX, endX, checkY);
316 | }
317 | prevX = x;
318 | prevY = y;
319 | }
320 | }
321 |
322 | drawFrame(maxX, maxY);
323 |
324 | if (screencap) {
325 | FILE* fileFS = fopen("linetest-screencap.bin", "wb");
326 | fseek(fileFS, 0, SEEK_SET);
327 | fwrite(VRAM_A, 256*192, sizeof(uint16_t), fileFS);
328 | fclose(fileFS);
329 | }
330 |
331 | int startX = (TEST_TYPE & 1) ? min(prevX, WIDTH - 1) : 0;
332 | int endX = (TEST_TYPE & 1) ? (WIDTH - 1) : min(prevX, WIDTH - 1);
333 | int startY = (TEST_TYPE & 2) ? min(prevY, HEIGHT - 1) : 0;
334 | int endY = (TEST_TYPE & 2) ? (HEIGHT - 1) : min(prevY, HEIGHT - 1);
335 | for(int checkY = startY; checkY <= endY; checkY++) {
336 | countAndWrite(startX, endX, checkY);
337 | }
338 | fclose(file);
339 |
340 | while(1) {
341 | pc->cursorX = 0;
342 | pc->cursorY = 0;
343 | printf("Done\n");
344 | swiWaitForVBlank();
345 | }
346 | }
347 |
348 | void draw(u8 r, u8 g, u8 b) {
349 | glBegin(GL_TRIANGLE);
350 | glColor3b(r, g, b);
351 |
352 | for(int16_t* vertex : verts) {
353 | glVertex3v16(vertex[0], vertex[1], vertex[2]);
354 | }
355 |
356 | glEnd();
357 | }
358 |
--------------------------------------------------------------------------------
/nds-interp/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "slope.h"
14 |
15 | using u8 = uint8_t;
16 | using u16 = uint16_t;
17 | using u32 = uint32_t;
18 | using i32 = int32_t;
19 |
20 | template
21 | std::vector LoadBin(const std::filesystem::path &path) {
22 | std::basic_ifstream file{path, std::ios::binary};
23 | return {std::istreambuf_iterator{file}, {}};
24 | }
25 |
26 | struct membuf : std::streambuf {
27 | membuf(char *begin, char *end) { this->setg(begin, begin, end); }
28 | };
29 |
30 | struct Span {
31 | bool exists;
32 | u8 start, end;
33 | };
34 |
35 | struct Line {
36 | std::array spans;
37 | };
38 |
39 | struct Data {
40 | u8 type;
41 | u16 minX, maxX;
42 | u8 minY, maxY;
43 | std::array, 192 + 1> lines;
44 | };
45 |
46 | std::unique_ptr readFile(std::filesystem::path path) {
47 | if (!std::filesystem::is_regular_file(path)) {
48 | std::cout << path.string() << " does not exist or is not a file.\n";
49 | return nullptr;
50 | }
51 |
52 | std::cout << "Loading " << path.string() << "... ";
53 | std::vector buffer = LoadBin(path);
54 | membuf mbuf(buffer.data(), buffer.data() + buffer.size());
55 | std::istream in{&mbuf};
56 |
57 | auto pData = std::make_unique();
58 | auto &lines = pData->lines;
59 |
60 | in.read((char *)&pData->type, 1);
61 | in.read((char *)&pData->minX, sizeof(pData->minX));
62 | in.read((char *)&pData->maxX, sizeof(pData->maxX));
63 | in.read((char *)&pData->minY, sizeof(pData->minY));
64 | in.read((char *)&pData->maxY, sizeof(pData->maxY));
65 |
66 | switch (pData->type) {
67 | case 0: std::cout << "Top left"; break;
68 | case 1: std::cout << "Bottom left"; break;
69 | case 2: std::cout << "Top right"; break;
70 | case 3: std::cout << "Bottom right"; break;
71 | default: std::cout << "Invalid type (" << (int)pData->type << ")"; return nullptr;
72 | }
73 | std::cout << ", " << pData->minX << "x" << (int)pData->minY << " to " << pData->maxX << "x" << (int)pData->maxY;
74 |
75 | u8 coords[2];
76 | int prevX = 0;
77 | int prevY = 0;
78 | for (int y = pData->minY; y <= pData->maxY; y++) {
79 | for (int x = pData->minX; x <= pData->maxX; x++) {
80 | in.read((char *)coords, sizeof(coords));
81 | if (coords[0] != (u8)prevX || coords[1] != (u8)prevY) {
82 | std::cout << " -- Invalid file\n";
83 | return nullptr;
84 | }
85 | int startY = (pData->type & 2) ? prevY : 0;
86 | int endY = (pData->type & 2) ? 191 : prevY;
87 | if (startY >= 192) startY = 191;
88 | if (endY >= 192) endY = 191;
89 | for (int checkY = startY; checkY <= endY; checkY++) {
90 | Span &span = lines[prevY][prevX].spans[checkY];
91 | in.read((char *)&span.exists, 1);
92 | in.read((char *)&span.start, 1);
93 | in.read((char *)&span.end, 1);
94 | }
95 | prevX = x;
96 | prevY = y;
97 | }
98 | }
99 |
100 | int startY = (pData->type & 2) ? std::min((int)pData->minY, 191) : 0;
101 | int endY = (pData->type & 2) ? 191 : std::min((int)pData->maxY, 191);
102 | for (int y = startY; y <= endY; y++) {
103 | Span &span = lines[prevY][prevX].spans[y];
104 | in.read((char *)&span.exists, 1);
105 | in.read((char *)&span.start, 1);
106 | in.read((char *)&span.end, 1);
107 | }
108 |
109 | std::cout << " -- OK\n";
110 |
111 | return std::move(pData);
112 | }
113 |
114 | // Converts the raw screen capture taken from the NDS into a TGA file
115 | void convertScreenCap(std::filesystem::path binPath, std::filesystem::path tgaPath) {
116 | std::ifstream in{binPath, std::ios::binary};
117 | std::ofstream out{tgaPath, std::ios::binary | std::ios::trunc};
118 |
119 | u8 tga[18];
120 | std::fill_n(tga, 18, 0);
121 | tga[2] = 2; // uncompressed truecolor
122 | *reinterpret_cast(&tga[12]) = 256; // width
123 | *reinterpret_cast(&tga[14]) = 192; // height
124 | tga[16] = 24; // bits per pixel
125 | tga[17] = 32; // image descriptor: top to bottom, left to right
126 |
127 | out.write((char *)tga, sizeof(tga));
128 |
129 | u16 clr;
130 | for (size_t y = 0; y < 192; y++) {
131 | for (size_t x = 0; x < 256; x++) {
132 | in.read((char *)&clr, sizeof(clr));
133 | u8 r5 = (clr >> 0) & 0x1F;
134 | u8 g5 = (clr >> 5) & 0x1F;
135 | u8 b5 = (clr >> 10) & 0x1F;
136 | u8 r8 = (r5 << 3) | (r5 >> 2);
137 | u8 g8 = (g5 << 3) | (g5 >> 2);
138 | u8 b8 = (b5 << 3) | (b5 >> 2);
139 | out.write((char *)&b8, sizeof(b8));
140 | out.write((char *)&g8, sizeof(g8));
141 | out.write((char *)&r8, sizeof(r8));
142 | }
143 | }
144 | }
145 |
146 | // Lists all unique colors present in the raw screen capture taken from the NDS
147 | void uniqueColors(std::filesystem::path binPath) {
148 | std::ifstream in{binPath, std::ios::binary};
149 | std::unordered_set clrs;
150 |
151 | u16 clr;
152 | for (size_t y = 0; y < 192; y++) {
153 | for (size_t x = 0; x < 256; x++) {
154 | in.read((char *)&clr, sizeof(clr));
155 | if (clrs.insert(clr).second) {
156 | int r5 = (clr >> 0) & 0x1F;
157 | int g5 = (clr >> 5) & 0x1F;
158 | int b5 = (clr >> 10) & 0x1F;
159 | int r8 = (r5 << 3) | (r5 >> 2);
160 | int g8 = (g5 << 3) | (g5 >> 2);
161 | int b8 = (b5 << 3) | (b5 >> 2);
162 | std::cout << " " << std::hex << clr;
163 | std::cout << std::dec << " (" << r5 << ", " << g5 << ", " << b5 << ") --> (" << r8 << ", " << g8
164 | << ", " << b8 << ")\n";
165 | }
166 | }
167 | }
168 | std::cout << "\n";
169 | }
170 |
171 | // Writes a series of TGA files with a rendering of every scanline captured from the NDS in the given data file
172 | void writeImages(Data &data, std::filesystem::path outDir) {
173 | auto &lines = data.lines;
174 |
175 | u8 tga[18];
176 | std::fill_n(tga, 18, 0);
177 | tga[2] = 3; // uncompressed greyscale
178 | *reinterpret_cast(&tga[12]) = 256; // width
179 | *reinterpret_cast(&tga[14]) = 192; // height
180 | tga[16] = 8; // bits per pixel
181 | tga[17] = 32; // image descriptor: top to bottom, left to right
182 |
183 | for (size_t sizeY = data.minY; sizeY <= data.maxY; sizeY++) {
184 | for (size_t sizeX = data.minX; sizeX <= data.maxX; sizeX++) {
185 | u8 pixels[192][256];
186 | memset(pixels, 0, 256 * 192);
187 | for (size_t y = 0; y < 192; y++) {
188 | Span &span = lines[sizeY][sizeX].spans[y];
189 | if (span.exists) {
190 | for (size_t x = span.start; x <= span.end; x++) {
191 | pixels[y][x] = 255;
192 | }
193 | }
194 | }
195 |
196 | std::string name;
197 | switch (data.type) {
198 | case 0: name = "TL"; break;
199 | case 1: name = "TR"; break;
200 | case 2: name = "BL"; break;
201 | case 3: name = "BR"; break;
202 | }
203 |
204 | std::ostringstream filename;
205 | filename << name << "-" << sizeX << "x" << sizeY << ".tga";
206 |
207 | std::filesystem::create_directories(outDir);
208 | std::ofstream out{outDir / filename.str(), std::ios::binary | std::ios::trunc};
209 | out.write((char *)tga, sizeof(tga));
210 | out.write((char *)pixels, 256 * 192);
211 | }
212 | }
213 | }
214 |
215 | void testSlope(const Data &data, i32 testX, i32 testY, i32 x0, i32 y0, i32 x1, i32 y1, bool &mismatch) {
216 | // Always rasterize top to bottom
217 | if (y0 > y1) {
218 | std::swap(x0, x1);
219 | std::swap(y0, y1);
220 | }
221 |
222 | // Y0 coinciding with Y1 is equivalent to Y0 and Y1 being 1 pixel apart
223 | if (y0 == y1) y1++;
224 |
225 | // Helper function that prints the mismatch message on the first occurrence of a mismatch
226 | auto foundMismatch = [&] {
227 | if (!mismatch) {
228 | mismatch = true;
229 | std::cout << "found mismatch\n";
230 | }
231 | };
232 |
233 | // Create and configure the slope
234 | Slope slope;
235 | slope.Setup(x0, y0, x1, y1);
236 |
237 | for (i32 y = y0; y < y1; y++) {
238 | // Get span for the current scanline
239 | i32 startX = slope.FracXStart(y);
240 | i32 endX = slope.FracXEnd(y);
241 | i32 startScrX = slope.XStart(y);
242 | i32 endScrX = slope.XEnd(y);
243 |
244 | // Spans are reversed when the slope is negative
245 | if (slope.IsNegative()) {
246 | std::swap(startX, endX);
247 | std::swap(startScrX, endScrX);
248 | }
249 |
250 | // Skip scanlines out of view
251 | if (startScrX >= 256) continue;
252 | if (y == 192) break;
253 |
254 | // Compare generated spans with those captured from hardware
255 | const Span &span = data.lines[testY][testX].spans[y];
256 | if (!span.exists) {
257 | foundMismatch();
258 |
259 | // clang-format off
260 | std::cout << std::setw(3) << testX << "x" << std::setw(3) << testY << " Y=" << std::setw(3) << y << ": span doesn't exist\n";
261 | // clang-format on
262 | } else if (span.start != startScrX || span.end != endScrX) {
263 | foundMismatch();
264 |
265 | // clang-format off
266 | std::cout << std::setw(3) << testX << "x" << std::setw(3) << testY << " Y=" << std::setw(3) << y << ": ";
267 |
268 | std::cout << std::setw(3) << startScrX << ".." << std::setw(3) << endScrX;
269 | std::cout << " != ";
270 | std::cout << std::setw(3) << (u32)span.start << ".." << std::setw(3) << (u32)span.end;
271 |
272 | std::cout << " (" << std::showpos << (i32)(startScrX - span.start) << ".." << (i32)(endScrX - span.end) << ")" << std::noshowpos;
273 |
274 | std::cout << " raw X = " << std::setw(10) << endX << " lastX = " << std::setw(10) << startX;
275 | std::cout << " masked X = " << std::setw(10) << (endX % Slope::kOne) << " lastX = " << std::setw(10) << (startX % Slope::kOne);
276 | std::cout << " inc = " << std::setw(10) << slope.DX();
277 | std::cout << "\n";
278 | // clang-format on
279 | }
280 | }
281 | }
282 |
283 | void testSlopes(Data &data, i32 x0, i32 y0, const char *name) {
284 | std::cout << "Testing " << name << " slopes... ";
285 |
286 | bool mismatch = false;
287 | for (i32 y1 = 0; y1 <= 192; y1++) {
288 | for (i32 x1 = 0; x1 <= 256; x1++) {
289 | testSlope(data, x1, y1, x0, y0, x1, y1, mismatch);
290 | }
291 | }
292 | if (!mismatch) {
293 | std::cout << "OK!\n";
294 | }
295 | }
296 |
297 | void test(Data &data) {
298 | switch (data.type) {
299 | case 0: testSlopes(data, 0, 0, "top left"); break;
300 | case 1: testSlopes(data, 256, 0, "top right"); break;
301 | case 2: testSlopes(data, 0, 192, "bottom left"); break;
302 | case 3: testSlopes(data, 256, 192, "bottom right"); break;
303 | }
304 | }
305 |
306 | int main() {
307 | // convertScreenCap("data/screencap.bin", "data/screencap.tga");
308 | // uniqueColors("data/screencap.bin");
309 |
310 | auto dataTL = readFile("data/TL.bin");
311 | auto dataTR = readFile("data/TR.bin");
312 | auto dataBL = readFile("data/BL.bin");
313 | auto dataBR = readFile("data/BR.bin");
314 |
315 | if (dataTL) test(*dataTL);
316 | if (dataTR) test(*dataTR);
317 | if (dataBL) test(*dataBL);
318 | if (dataBR) test(*dataBR);
319 |
320 | // if (dataTL) writeImages(*dataTL, "C:/temp/TL");
321 | // if (dataTR) writeImages(*dataTR, "C:/temp/TR");
322 | // if (dataBL) writeImages(*dataBL, "C:/temp/BL");
323 | // if (dataBR) writeImages(*dataBR, "C:/temp/BR");
324 |
325 | return 0;
326 | }
327 |
--------------------------------------------------------------------------------
/nds-interp/nds-interp.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | 16.0
23 | Win32Proj
24 | {95b244c4-fc99-45e0-9629-99fc4b5ed09b}
25 | nds-interp
26 | 10.0
27 |
28 |
29 |
30 | Application
31 | true
32 | v142
33 | Unicode
34 |
35 |
36 | Application
37 | false
38 | v142
39 | true
40 | Unicode
41 |
42 |
43 | Application
44 | true
45 | ClangCL
46 | Unicode
47 |
48 |
49 | Application
50 | false
51 | ClangCL
52 | true
53 | Unicode
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | true
75 |
76 |
77 | false
78 |
79 |
80 | true
81 |
82 |
83 | false
84 |
85 |
86 |
87 | Level3
88 | true
89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
90 | true
91 |
92 |
93 | Console
94 | true
95 |
96 |
97 |
98 |
99 | Level3
100 | true
101 | true
102 | true
103 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
104 | true
105 |
106 |
107 | Console
108 | true
109 | true
110 | true
111 |
112 |
113 |
114 |
115 | Level3
116 | true
117 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
118 | true
119 | stdcpplatest
120 | stdc17
121 |
122 |
123 | Console
124 | true
125 |
126 |
127 |
128 |
129 | Level3
130 | true
131 | true
132 | true
133 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
134 | true
135 | stdcpplatest
136 | stdc17
137 |
138 |
139 | Console
140 | true
141 | true
142 | true
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/nds-interp/nds-interp.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Header Files
20 |
21 |
22 |
23 |
24 | Source Files
25 |
26 |
27 |
--------------------------------------------------------------------------------
/nds-interp/slope.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | ///
7 | /// Computes 3D rasterization slopes based on Nintendo DS's hardware interpolation.
8 | ///
9 | ///
10 | /// The algorithm implemented by this class produces pixel-perfect slopes matching the Nintendo DS's 3D interpolator.
11 | ///
12 | /// The hardware uses 32-bit integers with 18-bit fractional parts throughout the interpolation process, with one
13 | /// notable exception in X-major slopes.
14 | ///
15 | /// To calculate the X increment per scanline (DX), the hardware first computes the reciprocal of Y1-Y0 then multiplies
16 | /// the result by X1-X0. This order of operations avoids a multiplication overflow at the cost of precision on the
17 | /// division.
18 | ///
19 | /// For X-major lines, the interpolator produces line spans for each scanline. The start of the span is calculated by
20 | /// first offseting the Y coordinate to the Y0-Y1 range (subtracting Y0 from Y), then multiplying the offset Y by DX,
21 | /// adding the X0 offset and finally a +0.5 bias. The end on the span is computed based on its starting coordinate,
22 | /// discarding (masking out) the 9 least significant bits (which could be seen as rounding down, or the floor function),
23 | /// then adding DX and subtracting 1.0. The exact algorithm is unknown, but it is possible that the starting coordinate
24 | /// is shifted right by 9 for an intermediate calculation then shifted back left by 9 to restore the fractional part.
25 | ///
26 | /// The formulae for determining the starting and ending X coordinates of a span of an X-major slope are:
27 | ///
28 | /// DX = 1 / (Y1 - Y0) * (X1 - X0)
29 | /// Xstart = (Y - Y0) * DX + X0 + 0.5
30 | /// Xend = Xstart[discarding 9 LSBs] + DX - 1.0
31 | ///
32 | /// Due to the 9 LSBs being discarded, certain X-major slopes (such as 69x49, 70x66, 71x49 and more) display a one-pixel
33 | /// gap on hardware. This is calculated accurately with the formulae above.
34 | ///
35 | /// Y-major slopes contain only one pixel per scanline. The formula for interpolating the X coordinate based on the Y
36 | /// coordinate is very similar to that of the X-major interpolation, with the only difference being that the +0.5 bias
37 | /// is not applied.
38 | ///
39 | /// X = (Y - Y0) * DX + X0
40 | ///
41 | /// Note that there is no need to compute a span as there's always going to be only one pixel per scanline. Also, there
42 | /// are no one-pixel gaps on Y-major lines since the Nintendo DS's rasterizer is scanline-based and the interpolation is
43 | /// computed on every scanline.
44 | ///
45 | /// Negative slopes work in a similar fashion. In fact, negative slopes perfectly match their positive counterparts down
46 | /// to the one-pixel gaps which happen in exactly the same spots. The gaps in negative slopes are to the left of a span,
47 | /// while in positive slopes the gaps are to the right of a span, as shown below (rows 35 to 39 of 69x49 slopes):
48 | ///
49 | /// Positive slope Negative slope
50 | /// ## +---- mind the gap ----+ ##
51 | /// # | | #
52 | /// #V V#
53 | /// # #
54 | /// # #
55 | ///
56 | /// The behavior for negative slopes is implemented in this class in the following manner, compared to positive slopes:
57 | /// - The raw value of X0 coordinate used to compute the starting X coordinate of a span is decremented by 1, that is,
58 | /// the value is subtracted an amount equal to 1.0 / 2^fractionalBits
59 | /// - X0 and X1 are swapped; as a consequence, DX remains positive
60 | /// - The starting X coordinate is the span's rightmost pixel; conversely, the ending X coordinate is its leftmost pixel
61 | /// - The starting X coordinate is decremented by the computed Y*DX displacement instead of incremented
62 | /// - The nine least significant bits of the ending X coordinate are rounded up the to the largest number less than 1.0
63 | /// (511 as a raw integer with 9 fractional bits)
64 | ///
65 | /// All other operations are otherwise identical.
66 | ///
67 | class Slope {
68 | using u32 = uint32_t;
69 | using i32 = int32_t;
70 |
71 | public:
72 | ///
73 | /// The number of fractional bits (aka resolution) of the interpolator.
74 | ///
75 | ///
76 | /// The Nintendo DS uses 18 fractional bits for interpolation.
77 | ///
78 | static constexpr u32 kFracBits = 18;
79 |
80 | ///
81 | /// The value 1.0 with fractional bits.
82 | ///
83 | static constexpr u32 kOne = (1 << kFracBits);
84 |
85 | ///
86 | /// The bias applied to the interpolation of X-major spans.
87 | ///
88 | static constexpr u32 kBias = (kOne >> 1);
89 |
90 | ///
91 | /// The mask applied during interpolation of X-major spans, removing half of the least significant fractional bits
92 | /// (rounded down).
93 | ///
94 | static constexpr u32 kMask = (~0u << (kFracBits / 2));
95 |
96 | ///
97 | /// Configures the slope to interpolate the line (X0,X1)-(Y0,Y1) using screen coordinates.
98 | ///
99 | /// First X coordinate
100 | /// First Y coordinate
101 | /// Second X coordinate
102 | /// Second Y coordinate
103 | constexpr void Setup(i32 x0, i32 y0, i32 x1, i32 y1) {
104 | // Always interpolate top to bottom
105 | if (y1 < y0) {
106 | std::swap(x0, x1);
107 | std::swap(y0, y1);
108 | }
109 |
110 | // Store reference coordinates
111 | m_x0 = x0 << kFracBits;
112 | m_y0 = y0;
113 |
114 | // Determine if this is a negative slope and adjust accordingly
115 | m_negative = (x1 < x0);
116 | if (m_negative) {
117 | m_x0--;
118 | std::swap(x0, x1);
119 | }
120 |
121 | // Compute coordinate deltas and determine if the slope is X-major
122 | i32 dx = (x1 - x0);
123 | i32 dy = (y1 - y0);
124 | m_xMajor = (dx > dy);
125 |
126 | // Precompute bias for X-major or diagonal slopes
127 | if (m_xMajor || dx == dy) {
128 | if (m_negative) {
129 | m_x0 -= kBias;
130 | } else {
131 | m_x0 += kBias;
132 | }
133 | }
134 |
135 | // Compute X displacement per scanline
136 | m_dx = dx;
137 | if (dy != 0) {
138 | m_dx *= kOne / dy; // This ensures the division is performed before the multiplication
139 | } else {
140 | m_dx *= kOne;
141 | }
142 | }
143 |
144 | ///
145 | /// Computes the starting position of the span at the specified Y coordinate, including the fractional part.
146 | ///
147 | /// The Y coordinate, which must be between Y0 and Y1 specified in Setup.
148 | /// The starting X coordinate of the specified scanline's span
149 | constexpr i32 FracXStart(i32 y) const {
150 | i32 displacement = (y - m_y0) * m_dx;
151 | if (m_negative) {
152 | return m_x0 - displacement;
153 | } else {
154 | return m_x0 + displacement;
155 | }
156 | }
157 |
158 | ///
159 | /// Computes the ending position of the span at the specified Y coordinate, including the fractional part.
160 | ///
161 | /// The Y coordinate, which must be between Y0 and Y1 specified in Setup.
162 | /// The ending X coordinate of the specified scanline's span
163 | constexpr i32 FracXEnd(i32 y) const {
164 | i32 result = FracXStart(y);
165 | if (m_xMajor) {
166 | if (m_negative) {
167 | // The bit manipulation sequence (~mask - (x & ~mask)) acts like a ceiling function.
168 | // Since we're working in the opposite direction here, the "floor" is actually the ceiling.
169 | result = result + (~kMask - (result & ~kMask)) - m_dx + kOne;
170 | } else {
171 | result = (result & kMask) + m_dx - kOne;
172 | }
173 | }
174 | return result;
175 | }
176 |
177 | ///
178 | /// Computes the starting position of the span at the specified Y coordinate as a screen coordinate (dropping the
179 | /// fractional part).
180 | ///
181 | /// The Y coordinate, which must be between Y0 and Y1 specified in Setup.
182 | /// The starting X screen coordinate of the scanline's span
183 | constexpr i32 XStart(i32 y) const { return FracXStart(y) >> kFracBits; }
184 |
185 | ///
186 | /// Computes the ending position of the span at the specified Y coordinate as a screen coordinate (dropping the
187 | /// fractional part).
188 | ///
189 | /// The Y coordinate, which must be between Y0 and Y1 specified in Setup.
190 | /// The ending X screen coordinate of the scanline's span
191 | constexpr i32 XEnd(i32 y) const { return FracXEnd(y) >> kFracBits; }
192 |
193 | ///
194 | /// Retrieves the X coordinate increment per scanline.
195 | ///
196 | /// The X displacement per scanline (DX)
197 | constexpr i32 DX() const { return m_dx; }
198 |
199 | ///
200 | /// Determines if the slope is X-major.
201 | ///
202 | /// true if the slope is X-major.
203 | constexpr bool IsXMajor() const { return m_xMajor; }
204 |
205 | ///
206 | /// Determines if the slope is negative (i.e. X decreases as Y increases).
207 | ///
208 | /// true if the slope is negative.
209 | constexpr bool IsNegative() const { return m_negative; }
210 |
211 | private:
212 | i32 m_x0; // X0 coordinate (minus 1 if this is a negative slope)
213 | i32 m_y0; // Y0 coordinate
214 | i32 m_dx; // X displacement per scanline
215 | bool m_negative; // True if the slope is negative (X1 < X0)
216 | bool m_xMajor; // True if the slope is X-major (X1-X0 > Y1-Y0)
217 | };
218 |
--------------------------------------------------------------------------------