├── .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 | ![69x49 slope gap](docs/gap.png) 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 | ![X-major slope gaps comparison](docs/gap-comparison.png) 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 | --------------------------------------------------------------------------------