├── .gitattributes ├── .gitignore ├── .vscode └── settings.json ├── Directory.Build.props ├── GraphicsHostApp.Desktop ├── GraphicsHostApp.Desktop.csproj ├── Program.cs ├── Properties │ └── launchSettings.json └── app.manifest ├── GraphicsHostApp.OpenGL ├── CMakeLists.txt ├── Graphics │ ├── Camera.cpp │ ├── Camera.h │ ├── GLObject.h │ ├── Mesh.cpp │ ├── Mesh.h │ ├── MeshFactory.cpp │ ├── MeshFactory.h │ ├── RenderPipeline.cpp │ ├── RenderPipeline.h │ ├── Renderer.cpp │ ├── Renderer.h │ ├── Shader.cpp │ ├── Shader.h │ └── Vertex.h ├── GraphicsHostApp.OpenGL.cpp ├── GraphicsHostApp.OpenGL.h └── glad │ ├── include │ ├── KHR │ │ └── khrplatform.h │ └── glad │ │ └── glad.h │ └── src │ └── glad.c ├── GraphicsHostApp.sln ├── GraphicsHostApp ├── App.axaml ├── App.axaml.cs ├── Assets │ └── avalonia-logo.ico ├── Contracts │ └── Services │ │ └── IDrawingService.cs ├── Graphics │ ├── Camera.cs │ ├── Disposable.cs │ ├── IGraphicsHost.cs │ ├── OpenGL │ │ ├── Buffer.cs │ │ ├── Frame.cs │ │ ├── GraphicsResource.cs │ │ ├── Mesh.cs │ │ ├── RenderPipeline.cs │ │ ├── Renderer.cs │ │ ├── Shader.cs │ │ └── Texture.cs │ └── Vertex.cs ├── GraphicsHostApp.csproj ├── Helpers │ ├── MathExtensions.cs │ ├── MathHelper.cs │ └── MeshFactory.cs ├── Resources │ ├── Dependencies │ │ ├── GraphicsHostApp.OpenGL.dll │ │ └── libGraphicsHostApp.OpenGL.so │ ├── Models │ │ └── Sphere.glb │ └── Shaders │ │ ├── Canvas.frag │ │ ├── Canvas.vert │ │ ├── Simple.frag │ │ ├── Simple.vert │ │ ├── SolidColor.frag │ │ └── SolidColor.vert ├── Services │ ├── ExternalDrawingService.cs │ └── SimpleDrawingService.cs ├── ViewModels │ ├── MainViewModel.cs │ └── ViewModelBase.cs └── Views │ ├── MainView.axaml │ ├── MainView.axaml.cs │ ├── MainWindow.axaml │ └── MainWindow.axaml.cs └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | build/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # Tye 67 | .tye/ 68 | 69 | # ASP.NET Scaffolding 70 | ScaffoldingReadMe.txt 71 | 72 | # StyleCop 73 | StyleCopReport.xml 74 | 75 | # Files built by Visual Studio 76 | *_i.c 77 | *_p.c 78 | *_h.h 79 | *.ilk 80 | *.meta 81 | *.obj 82 | *.iobj 83 | *.pch 84 | *.pdb 85 | *.ipdb 86 | *.pgc 87 | *.pgd 88 | *.rsp 89 | *.sbr 90 | *.tlb 91 | *.tli 92 | *.tlh 93 | *.tmp 94 | *.tmp_proj 95 | *_wpftmp.csproj 96 | *.log 97 | *.vspscc 98 | *.vssscc 99 | .builds 100 | *.pidb 101 | *.svclog 102 | *.scc 103 | 104 | # Chutzpah Test files 105 | _Chutzpah* 106 | 107 | # Visual C++ cache files 108 | ipch/ 109 | *.aps 110 | *.ncb 111 | *.opendb 112 | *.opensdf 113 | *.sdf 114 | *.cachefile 115 | *.VC.db 116 | *.VC.VC.opendb 117 | 118 | # Visual Studio profiler 119 | *.psess 120 | *.vsp 121 | *.vspx 122 | *.sap 123 | 124 | # Visual Studio Trace Files 125 | *.e2e 126 | 127 | # TFS 2012 Local Workspace 128 | $tf/ 129 | 130 | # Guidance Automation Toolkit 131 | *.gpState 132 | 133 | # ReSharper is a .NET coding add-in 134 | _ReSharper*/ 135 | *.[Rr]e[Ss]harper 136 | *.DotSettings.user 137 | 138 | # TeamCity is a build add-in 139 | _TeamCity* 140 | 141 | # DotCover is a Code Coverage Tool 142 | *.dotCover 143 | 144 | # AxoCover is a Code Coverage Tool 145 | .axoCover/* 146 | !.axoCover/settings.json 147 | 148 | # Coverlet is a free, cross platform Code Coverage Tool 149 | coverage*.json 150 | coverage*.xml 151 | coverage*.info 152 | 153 | # Visual Studio code coverage results 154 | *.coverage 155 | *.coveragexml 156 | 157 | # NCrunch 158 | _NCrunch_* 159 | .*crunch*.local.xml 160 | nCrunchTemp_* 161 | 162 | # MightyMoose 163 | *.mm.* 164 | AutoTest.Net/ 165 | 166 | # Web workbench (sass) 167 | .sass-cache/ 168 | 169 | # Installshield output folder 170 | [Ee]xpress/ 171 | 172 | # DocProject is a documentation generator add-in 173 | DocProject/buildhelp/ 174 | DocProject/Help/*.HxT 175 | DocProject/Help/*.HxC 176 | DocProject/Help/*.hhc 177 | DocProject/Help/*.hhk 178 | DocProject/Help/*.hhp 179 | DocProject/Help/Html2 180 | DocProject/Help/html 181 | 182 | # Click-Once directory 183 | publish/ 184 | 185 | # Publish Web Output 186 | *.[Pp]ublish.xml 187 | *.azurePubxml 188 | # Note: Comment the next line if you want to checkin your web deploy settings, 189 | # but database connection strings (with potential passwords) will be unencrypted 190 | *.pubxml 191 | *.publishproj 192 | 193 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 194 | # checkin your Azure Web App publish settings, but sensitive information contained 195 | # in these scripts will be unencrypted 196 | PublishScripts/ 197 | 198 | # NuGet Packages 199 | *.nupkg 200 | # NuGet Symbol Packages 201 | *.snupkg 202 | # The packages folder can be ignored because of Package Restore 203 | **/[Pp]ackages/* 204 | # except build/, which is used as an MSBuild target. 205 | !**/[Pp]ackages/build/ 206 | # Uncomment if necessary however generally it will be regenerated when needed 207 | #!**/[Pp]ackages/repositories.config 208 | # NuGet v3's project.json files produces more ignorable files 209 | *.nuget.props 210 | *.nuget.targets 211 | 212 | # Microsoft Azure Build Output 213 | csx/ 214 | *.build.csdef 215 | 216 | # Microsoft Azure Emulator 217 | ecf/ 218 | rcf/ 219 | 220 | # Windows Store app package directories and files 221 | AppPackages/ 222 | BundleArtifacts/ 223 | Package.StoreAssociation.xml 224 | _pkginfo.txt 225 | *.appx 226 | *.appxbundle 227 | *.appxupload 228 | 229 | # Visual Studio cache files 230 | # files ending in .cache can be ignored 231 | *.[Cc]ache 232 | # but keep track of directories ending in .cache 233 | !?*.[Cc]ache/ 234 | 235 | # Others 236 | ClientBin/ 237 | ~$* 238 | *~ 239 | *.dbmdl 240 | *.dbproj.schemaview 241 | *.jfm 242 | *.pfx 243 | *.publishsettings 244 | orleans.codegen.cs 245 | 246 | # Including strong name files can present a security risk 247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 248 | #*.snk 249 | 250 | # Since there are multiple workflows, uncomment next line to ignore bower_components 251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 252 | #bower_components/ 253 | 254 | # RIA/Silverlight projects 255 | Generated_Code/ 256 | 257 | # Backup & report files from converting an old project file 258 | # to a newer Visual Studio version. Backup files are not needed, 259 | # because we have git ;-) 260 | _UpgradeReport_Files/ 261 | Backup*/ 262 | UpgradeLog*.XML 263 | UpgradeLog*.htm 264 | ServiceFabricBackup/ 265 | *.rptproj.bak 266 | 267 | # SQL Server files 268 | *.mdf 269 | *.ldf 270 | *.ndf 271 | 272 | # Business Intelligence projects 273 | *.rdl.data 274 | *.bim.layout 275 | *.bim_*.settings 276 | *.rptproj.rsuser 277 | *- [Bb]ackup.rdl 278 | *- [Bb]ackup ([0-9]).rdl 279 | *- [Bb]ackup ([0-9][0-9]).rdl 280 | 281 | # Microsoft Fakes 282 | FakesAssemblies/ 283 | 284 | # GhostDoc plugin setting file 285 | *.GhostDoc.xml 286 | 287 | # Node.js Tools for Visual Studio 288 | .ntvs_analysis.dat 289 | node_modules/ 290 | 291 | # Visual Studio 6 build log 292 | *.plg 293 | 294 | # Visual Studio 6 workspace options file 295 | *.opt 296 | 297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 298 | *.vbw 299 | 300 | # Visual Studio LightSwitch build output 301 | **/*.HTMLClient/GeneratedArtifacts 302 | **/*.DesktopClient/GeneratedArtifacts 303 | **/*.DesktopClient/ModelManifest.xml 304 | **/*.Server/GeneratedArtifacts 305 | **/*.Server/ModelManifest.xml 306 | _Pvt_Extensions 307 | 308 | # Paket dependency manager 309 | .paket/paket.exe 310 | paket-files/ 311 | 312 | # FAKE - F# Make 313 | .fake/ 314 | 315 | # CodeRush personal settings 316 | .cr/personal 317 | 318 | # Python Tools for Visual Studio (PTVS) 319 | __pycache__/ 320 | *.pyc 321 | 322 | # Cake - Uncomment if you are using it 323 | # tools/** 324 | # !tools/packages.config 325 | 326 | # Tabs Studio 327 | *.tss 328 | 329 | # Telerik's JustMock configuration file 330 | *.jmconfig 331 | 332 | # BizTalk build output 333 | *.btp.cs 334 | *.btm.cs 335 | *.odx.cs 336 | *.xsd.cs 337 | 338 | # OpenCover UI analysis results 339 | OpenCover/ 340 | 341 | # Azure Stream Analytics local run output 342 | ASALocalRun/ 343 | 344 | # MSBuild Binary and Structured Log 345 | *.binlog 346 | 347 | # NVidia Nsight GPU debugger configuration file 348 | *.nvuser 349 | 350 | # MFractors (Xamarin productivity tool) working folder 351 | .mfractor/ 352 | 353 | # Local History for Visual Studio 354 | .localhistory/ 355 | 356 | # BeatPulse healthcheck temp database 357 | healthchecksdb 358 | 359 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 360 | MigrationBackup/ 361 | 362 | # Ionide (cross platform F# VS Code tools) working folder 363 | .ionide/ 364 | 365 | # Fody - auto-generated XML schema 366 | FodyWeavers.xsd 367 | 368 | ## 369 | ## Visual studio for Mac 370 | ## 371 | 372 | 373 | # globs 374 | Makefile.in 375 | *.userprefs 376 | *.usertasks 377 | config.make 378 | config.status 379 | aclocal.m4 380 | install-sh 381 | autom4te.cache/ 382 | *.tar.gz 383 | tarballs/ 384 | test-results/ 385 | 386 | # Mac bundle stuff 387 | *.dmg 388 | *.app 389 | 390 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 391 | # General 392 | .DS_Store 393 | .AppleDouble 394 | .LSOverride 395 | 396 | # Icon must end with two \r 397 | Icon 398 | 399 | 400 | # Thumbnails 401 | ._* 402 | 403 | # Files that might appear in the root of a volume 404 | .DocumentRevisions-V100 405 | .fseventsd 406 | .Spotlight-V100 407 | .TemporaryItems 408 | .Trashes 409 | .VolumeIcon.icns 410 | .com.apple.timemachine.donotpresent 411 | 412 | # Directories potentially created on remote AFP share 413 | .AppleDB 414 | .AppleDesktop 415 | Network Trash Folder 416 | Temporary Items 417 | .apdisk 418 | 419 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 420 | # Windows thumbnail cache files 421 | Thumbs.db 422 | ehthumbs.db 423 | ehthumbs_vista.db 424 | 425 | # Dump file 426 | *.stackdump 427 | 428 | # Folder config file 429 | [Dd]esktop.ini 430 | 431 | # Recycle Bin used on file shares 432 | $RECYCLE.BIN/ 433 | 434 | # Windows Installer files 435 | *.cab 436 | *.msi 437 | *.msix 438 | *.msm 439 | *.msp 440 | 441 | # Windows shortcuts 442 | *.lnk 443 | 444 | # JetBrains Rider 445 | .idea/ 446 | *.sln.iml 447 | 448 | ## 449 | ## Visual Studio Code 450 | ## 451 | .vscode/* 452 | !.vscode/settings.json 453 | !.vscode/tasks.json 454 | !.vscode/launch.json 455 | !.vscode/extensions.json 456 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.sourceDirectory": "/home/qian-o/Code/GraphicsHostApp/GraphicsHostApp.OpenGL" 3 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | 11.0.2 5 | 6 | 7 | -------------------------------------------------------------------------------- /GraphicsHostApp.Desktop/GraphicsHostApp.Desktop.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | 6 | net8.0 7 | enable 8 | true 9 | app.manifest 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /GraphicsHostApp.Desktop/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Avalonia; 4 | 5 | namespace GraphicsHostApp.Desktop; 6 | 7 | class Program 8 | { 9 | // Initialization code. Don't use any Avalonia, third-party APIs or any 10 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 11 | // yet and stuff might break. 12 | [STAThread] 13 | public static void Main(string[] args) => BuildAvaloniaApp() 14 | .StartWithClassicDesktopLifetime(args); 15 | 16 | // Avalonia configuration, don't remove; also used by visual designer. 17 | public static AppBuilder BuildAvaloniaApp() 18 | => AppBuilder.Configure() 19 | .UsePlatformDetect() 20 | .WithInterFont() 21 | .LogToTrace(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /GraphicsHostApp.Desktop/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "GraphicsHostApp.Desktop": { 4 | "commandName": "Project", 5 | "nativeDebugging": true 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /GraphicsHostApp.Desktop/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 添加编译选项 2 | add_compile_options(-fPIC) 3 | 4 | # 包含OpenGL图形库的头文件和源文件 5 | file(GLOB GRAPHICS_HEADERS "Graphics/*.h") 6 | file(GLOB GRAPHICS_SOURCES "Graphics/*.cpp") 7 | 8 | # 将头文件和源文件分组 9 | source_group("Graphics/Header Files" FILES ${GRAPHICS_HEADERS}) 10 | source_group("Graphics/Source Files" FILES ${GRAPHICS_SOURCES}) 11 | 12 | # 设置接口源文件和头文件 13 | set(INTERFACE_SOURCES "GraphicsHostApp.OpenGL.cpp") 14 | set(INTERFACE_HEADERS "GraphicsHostApp.OpenGL.h") 15 | 16 | # 创建名为GraphicsHostApp.OpenGL的动态链接库项目 17 | add_library(GraphicsHostApp.OpenGL SHARED ${GRAPHICS_SOURCES} ${GRAPHICS_HEADERS} ${INTERFACE_SOURCES} ${INTERFACE_HEADERS}) 18 | 19 | # 添加glad库的源文件 20 | add_library(glad glad/src/glad.c) 21 | 22 | # 将glad库的包含目录添加到项目中 23 | target_include_directories(glad PUBLIC glad/include) 24 | 25 | # 将glad库链接到GraphicsHostApp.OpenGL项目中 26 | target_link_libraries(GraphicsHostApp.OpenGL glad) 27 | 28 | # 检查CMake版本并设置C++标准为C++20 29 | if (CMAKE_VERSION VERSION_GREATER 3.12) 30 | set_property(TARGET GraphicsHostApp.OpenGL PROPERTY CXX_STANDARD 20) 31 | endif() -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/Camera.cpp: -------------------------------------------------------------------------------- 1 | #include "Camera.h" 2 | 3 | namespace GraphicsHostApp 4 | { 5 | void Camera::SetWidth(int width) 6 | { 7 | this->width = width; 8 | } 9 | 10 | void Camera::SetHeight(int height) 11 | { 12 | this->height = height; 13 | } 14 | 15 | void Camera::SetPosition(glm::vec3 position) 16 | { 17 | this->position = position; 18 | } 19 | 20 | void Camera::SetPitch(float pitch) 21 | { 22 | this->pitch = glm::radians(pitch); 23 | UpdateVectors(); 24 | } 25 | 26 | void Camera::SetYaw(float yaw) 27 | { 28 | this->yaw = glm::radians(yaw); 29 | UpdateVectors(); 30 | } 31 | 32 | void Camera::SetFov(float fov) 33 | { 34 | this->fov = glm::radians(fov); 35 | } 36 | 37 | void Camera::UpdateVectors() 38 | { 39 | front.x = cos(yaw) * cos(pitch); 40 | front.y = sin(pitch); 41 | front.z = sin(yaw) * cos(pitch); 42 | 43 | front = glm::normalize(front); 44 | 45 | right = glm::normalize(glm::cross(front, up)); 46 | up = glm::normalize(glm::cross(right, front)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/Camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace GraphicsHostApp 8 | { 9 | class Camera 10 | { 11 | public: 12 | int Width() const { return width; } 13 | int Height() const { return height; } 14 | glm::vec3 Position() const { return position; } 15 | glm::vec3 Front() const { return front; } 16 | glm::vec3 Up() const { return up; } 17 | glm::vec3 Right() const { return right; } 18 | float Pitch() const { return glm::degrees(pitch); } 19 | float Yaw() const { return glm::degrees(yaw); } 20 | float Fov() const { return glm::degrees(fov); } 21 | glm::mat4 View() const { return glm::lookAt(position, position + front, up); } 22 | glm::mat4 Projection() const { return glm::perspective(fov, (float)width / (float)height, 0.1f, 1000.0f); } 23 | 24 | void SetWidth(int width); 25 | void SetHeight(int height); 26 | void SetPosition(glm::vec3 position); 27 | void SetPitch(float pitch); 28 | void SetYaw(float yaw); 29 | void SetFov(float fov); 30 | 31 | private: 32 | int width = 800; 33 | int height = 600; 34 | glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f); 35 | glm::vec3 front = -glm::vec3(0.0f, 0.0f, 1.0f); 36 | glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 37 | glm::vec3 right = glm::vec3(1.0f, 0.0f, 0.0f); 38 | float pitch = 0.0f; 39 | float yaw = -glm::radians(90.0f); 40 | float fov = glm::radians(90.0f); 41 | 42 | void UpdateVectors(); 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/GLObject.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace GraphicsHostApp 9 | { 10 | class GLObject 11 | { 12 | public: 13 | GLuint Handle() const { return m_handle; } 14 | 15 | protected: 16 | GLuint m_handle; 17 | }; 18 | } -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/Mesh.cpp: -------------------------------------------------------------------------------- 1 | #include "Mesh.h" 2 | 3 | namespace GraphicsHostApp 4 | { 5 | static std::map offsetMap; 6 | 7 | Mesh::Mesh(std::vector vertices, std::vector indices) 8 | { 9 | if (offsetMap.empty()) 10 | { 11 | offsetMap["Position"] = offsetof(Vertex, Position); 12 | offsetMap["Normal"] = offsetof(Vertex, Normal); 13 | offsetMap["Tangent"] = offsetof(Vertex, Tangent); 14 | offsetMap["Bitangent"] = offsetof(Vertex, Bitangent); 15 | offsetMap["Color"] = offsetof(Vertex, Color); 16 | offsetMap["TexCoord"] = offsetof(Vertex, TexCoord); 17 | } 18 | 19 | glGenVertexArrays(1, &m_handle); 20 | glBindVertexArray(m_handle); 21 | 22 | glGenBuffers(1, &m_vbo); 23 | glBindBuffer(GL_ARRAY_BUFFER, m_vbo); 24 | glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW); 25 | 26 | glGenBuffers(1, &m_ebo); 27 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo); 28 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), indices.data(), GL_STATIC_DRAW); 29 | 30 | m_indexLength = (GLuint)indices.size(); 31 | 32 | glBindVertexArray(0); 33 | } 34 | 35 | Mesh::~Mesh() 36 | { 37 | glDeleteBuffers(1, &m_vbo); 38 | glDeleteBuffers(1, &m_ebo); 39 | glDeleteVertexArrays(1, &m_handle); 40 | } 41 | 42 | void Mesh::VertexAttributePointer(GLuint index, GLint size, std::string fieldName) 43 | { 44 | if (offsetMap.find(fieldName) == offsetMap.end()) 45 | { 46 | throw std::runtime_error("Field name not found in vertex struct."); 47 | } 48 | 49 | glBindVertexArray(m_handle); 50 | 51 | glBindBuffer(GL_ARRAY_BUFFER, m_vbo); 52 | glVertexAttribPointer(index, size, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetMap[fieldName]); 53 | glEnableVertexAttribArray(index); 54 | glBindBuffer(GL_ARRAY_BUFFER, 0); 55 | 56 | glBindVertexArray(0); 57 | } 58 | 59 | void Mesh::Draw() 60 | { 61 | glBindVertexArray(m_handle); 62 | glDrawElements(GL_TRIANGLES, m_indexLength, GL_UNSIGNED_INT, nullptr); 63 | glBindVertexArray(0); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/Mesh.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GLObject.h" 4 | #include "Vertex.h" 5 | 6 | namespace GraphicsHostApp 7 | { 8 | class Mesh : public GLObject 9 | { 10 | public: 11 | Mesh(std::vector vertices, std::vector indices); 12 | ~Mesh(); 13 | 14 | GLuint ArrayBuffer() const { return m_vbo; } 15 | GLuint ElementBuffer() const { return m_ebo; } 16 | GLuint IndexLength() const { return m_indexLength; } 17 | 18 | void VertexAttributePointer(GLuint index, GLint size, std::string fieldName); 19 | void Draw(); 20 | 21 | private: 22 | GLuint m_vbo; 23 | GLuint m_ebo; 24 | GLuint m_indexLength; 25 | }; 26 | } -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/MeshFactory.cpp: -------------------------------------------------------------------------------- 1 | #include "MeshFactory.h" 2 | 3 | namespace GraphicsHostApp 4 | { 5 | Mesh* MeshFactory::CreateTriangle(float size) 6 | { 7 | std::vector vertices = { 8 | Vertex(glm::vec3(-size, -size, 0.0f), glm::vec3(1.0f, 0.0f, 0.0f)), 9 | Vertex(glm::vec3(size, -size, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)), 10 | Vertex(glm::vec3(0.0f, size, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)) 11 | }; 12 | 13 | std::vector indices = { 14 | 0, 1, 2 15 | }; 16 | 17 | return new Mesh(vertices, indices); 18 | } 19 | 20 | Mesh* MeshFactory::CreateCube(float size) 21 | { 22 | std::vector vertices = { 23 | // Front face 24 | Vertex(glm::vec3(-size, -size, size), glm::vec3(0.0f, 0.0f, 1.0f)), 25 | Vertex(glm::vec3(size, -size, size), glm::vec3(0.0f, 0.0f, 1.0f)), 26 | Vertex(glm::vec3(size, size, size), glm::vec3(0.0f, 0.0f, 1.0f)), 27 | Vertex(glm::vec3(size, size, size), glm::vec3(0.0f, 0.0f, 1.0f)), 28 | Vertex(glm::vec3(-size, size, size), glm::vec3(0.0f, 0.0f, 1.0f)), 29 | Vertex(glm::vec3(-size, -size, size), glm::vec3(0.0f, 0.0f, 1.0f)), 30 | 31 | // Back face 32 | Vertex(glm::vec3(-size, -size, -size), glm::vec3(0.0f, 0.0f, -1.0f)), 33 | Vertex(glm::vec3(-size, size, -size), glm::vec3(0.0f, 0.0f, -1.0f)), 34 | Vertex(glm::vec3(size, size, -size), glm::vec3(0.0f, 0.0f, -1.0f)), 35 | Vertex(glm::vec3(size, size, -size), glm::vec3(0.0f, 0.0f, -1.0f)), 36 | Vertex(glm::vec3(size, -size, -size), glm::vec3(0.0f, 0.0f, -1.0f)), 37 | Vertex(glm::vec3(-size, -size, -size), glm::vec3(0.0f, 0.0f, -1.0f)), 38 | 39 | // Top face 40 | Vertex(glm::vec3(-size, size, -size), glm::vec3(0.0f, 1.0f, 0.0f)), 41 | Vertex(glm::vec3(-size, size, size), glm::vec3(0.0f, 1.0f, 0.0f)), 42 | Vertex(glm::vec3(size, size, size), glm::vec3(0.0f, 1.0f, 0.0f)), 43 | Vertex(glm::vec3(size, size, size), glm::vec3(0.0f, 1.0f, 0.0f)), 44 | Vertex(glm::vec3(size, size, -size), glm::vec3(0.0f, 1.0f, 0.0f)), 45 | Vertex(glm::vec3(-size, size, -size), glm::vec3(0.0f, 1.0f, 0.0f)), 46 | 47 | // Bottom face 48 | Vertex(glm::vec3(-size, -size, -size), glm::vec3(0.0f, -1.0f, 0.0f)), 49 | Vertex(glm::vec3(size, -size, -size), glm::vec3(0.0f, -1.0f, 0.0f)), 50 | Vertex(glm::vec3(size, -size, size), glm::vec3(0.0f, -1.0f, 0.0f)), 51 | Vertex(glm::vec3(size, -size, size), glm::vec3(0.0f, -1.0f, 0.0f)), 52 | Vertex(glm::vec3(-size, -size, size), glm::vec3(0.0f, -1.0f, 0.0f)), 53 | Vertex(glm::vec3(-size, -size, -size), glm::vec3(0.0f, -1.0f, 0.0f)), 54 | 55 | // Right face 56 | Vertex(glm::vec3(size, -size, -size), glm::vec3(1.0f, 0.0f, 0.0f)), 57 | Vertex(glm::vec3(size, size, -size), glm::vec3(1.0f, 0.0f, 0.0f)), 58 | Vertex(glm::vec3(size, size, size), glm::vec3(1.0f, 0.0f, 0.0f)), 59 | Vertex(glm::vec3(size, size, size), glm::vec3(1.0f, 0.0f, 0.0f)), 60 | Vertex(glm::vec3(size, -size, size), glm::vec3(1.0f, 0.0f, 0.0f)), 61 | Vertex(glm::vec3(size, -size, -size), glm::vec3(1.0f, 0.0f, 0.0f)), 62 | 63 | // Left face 64 | Vertex(glm::vec3(-size, -size, -size), glm::vec3(-1.0f, 0.0f, 0.0f)), 65 | Vertex(glm::vec3(-size, -size, size), glm::vec3(-1.0f, 0.0f, 0.0f)), 66 | Vertex(glm::vec3(-size, size, size), glm::vec3(-1.0f, 0.0f, 0.0f)), 67 | Vertex(glm::vec3(-size, size, size), glm::vec3(-1.0f, 0.0f, 0.0f)), 68 | Vertex(glm::vec3(-size, size, -size), glm::vec3(-1.0f, 0.0f, 0.0f)), 69 | Vertex(glm::vec3(-size, -size, -size), glm::vec3(-1.0f, 0.0f, 0.0f)) 70 | }; 71 | 72 | std::vector indices = { 73 | 0, 1, 2, 3, 4, 5, // Front face 74 | 6, 7, 8, 9, 10, 11, // Back face 75 | 12, 13, 14, 15, 16, 17, // Top face 76 | 18, 19, 20, 21, 22, 23, // Bottom face 77 | 24, 25, 26, 27, 28, 29, // Right face 78 | 30, 31, 32, 33, 34, 35 // Left face 79 | }; 80 | 81 | return new Mesh(vertices, indices); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/MeshFactory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Mesh.h" 4 | 5 | namespace GraphicsHostApp 6 | { 7 | class MeshFactory 8 | { 9 | public: 10 | static Mesh* CreateTriangle(float size = 0.5f); 11 | 12 | static Mesh* CreateCube(float size = 0.5f); 13 | }; 14 | } -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/RenderPipeline.cpp: -------------------------------------------------------------------------------- 1 | #include "RenderPipeline.h" 2 | 3 | namespace GraphicsHostApp 4 | { 5 | RenderPipeline::RenderPipeline(Shader vs, Shader fs) 6 | { 7 | m_handle = glCreateProgram(); 8 | glAttachShader(m_handle, vs.Handle()); 9 | glAttachShader(m_handle, fs.Handle()); 10 | glLinkProgram(m_handle); 11 | 12 | // Check for linking errors. 13 | GLint length; 14 | glGetProgramiv(m_handle, GL_INFO_LOG_LENGTH, &length); 15 | GLchar* m_infoLog = new GLchar[length]; 16 | glGetProgramInfoLog(m_handle, length * 2, nullptr, m_infoLog); 17 | 18 | if (length > 0) 19 | { 20 | glDeleteProgram(m_handle); 21 | 22 | throw std::runtime_error(m_infoLog); 23 | } 24 | } 25 | 26 | RenderPipeline::~RenderPipeline() 27 | { 28 | glDeleteProgram(m_handle); 29 | } 30 | 31 | GLint RenderPipeline::GetAttribLocation(std::string name) 32 | { 33 | return glGetAttribLocation(m_handle, name.c_str()); 34 | } 35 | 36 | GLint RenderPipeline::GetUniformLocation(std::string name) 37 | { 38 | return glGetUniformLocation(m_handle, name.c_str()); 39 | } 40 | 41 | void RenderPipeline::SetUniform(std::string name, GLint value) 42 | { 43 | glUniform1i(GetUniformLocation(name), value); 44 | } 45 | 46 | void RenderPipeline::SetUniform(std::string name, GLfloat value) 47 | { 48 | glUniform1f(GetUniformLocation(name), value); 49 | } 50 | 51 | void RenderPipeline::SetUniform(std::string name, glm::vec2 value) 52 | { 53 | glUniform2fv(GetUniformLocation(name), 1, &value[0]); 54 | } 55 | 56 | void RenderPipeline::SetUniform(std::string name, glm::vec3 value) 57 | { 58 | glUniform3fv(GetUniformLocation(name), 1, &value[0]); 59 | } 60 | 61 | void RenderPipeline::SetUniform(std::string name, glm::vec4 value) 62 | { 63 | glUniform4fv(GetUniformLocation(name), 1, &value[0]); 64 | } 65 | 66 | void RenderPipeline::SetUniform(std::string name, glm::mat2 value) 67 | { 68 | glUniformMatrix2fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]); 69 | } 70 | 71 | void RenderPipeline::SetUniform(std::string name, glm::mat3 value) 72 | { 73 | glUniformMatrix3fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]); 74 | } 75 | 76 | void RenderPipeline::SetUniform(std::string name, glm::mat4 value) 77 | { 78 | glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]); 79 | } 80 | 81 | void RenderPipeline::Bind() 82 | { 83 | glUseProgram(m_handle); 84 | 85 | glEnable(GL_DEPTH_TEST); 86 | } 87 | 88 | void RenderPipeline::Unbind() 89 | { 90 | glUseProgram(0); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/RenderPipeline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "GLObject.h" 6 | #include "Shader.h" 7 | 8 | namespace GraphicsHostApp 9 | { 10 | class RenderPipeline : public GLObject 11 | { 12 | public: 13 | RenderPipeline(Shader vs, Shader fs); 14 | ~RenderPipeline(); 15 | 16 | GLint GetAttribLocation(std::string name); 17 | GLint GetUniformLocation(std::string name); 18 | void SetUniform(std::string name, GLint value); 19 | void SetUniform(std::string name, GLfloat value); 20 | void SetUniform(std::string name, glm::vec2 value); 21 | void SetUniform(std::string name, glm::vec3 value); 22 | void SetUniform(std::string name, glm::vec4 value); 23 | void SetUniform(std::string name, glm::mat2 value); 24 | void SetUniform(std::string name, glm::mat3 value); 25 | void SetUniform(std::string name, glm::mat4 value); 26 | void Bind(); 27 | void Unbind(); 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "Renderer.h" 2 | 3 | namespace GraphicsHostApp 4 | { 5 | #pragma region Simple Shader 6 | const std::string simpleVert = R"(#version 300 es 7 | 8 | layout(location = 0) in vec3 In_Position; 9 | layout(location = 1) in vec3 In_Normal; 10 | layout(location = 2) in vec3 In_Tangent; 11 | layout(location = 3) in vec3 In_Bitangent; 12 | layout(location = 4) in vec4 In_Color; 13 | layout(location = 5) in vec2 In_TexCoord; 14 | 15 | out vec4 VS_Color; 16 | 17 | uniform mat4 Model; 18 | uniform mat4 View; 19 | uniform mat4 Projection; 20 | uniform mat4 ObjectToWorld; 21 | uniform mat4 ObjectToClip; 22 | uniform mat4 WorldToObject; 23 | 24 | void main() 25 | { 26 | VS_Color = vec4(In_Normal * 0.5 + vec3(0.5), 1.0); 27 | 28 | gl_Position = ObjectToClip * vec4(In_Position, 1.0); 29 | } 30 | )"; 31 | const std::string simpleFrag = R"(#version 300 es 32 | 33 | precision highp float; 34 | 35 | in vec4 VS_Color; 36 | 37 | layout(location = 0) out vec4 Out_Color; 38 | 39 | uniform vec4 Color; 40 | 41 | void main() 42 | { 43 | Out_Color = VS_Color * Color; 44 | } 45 | )"; 46 | #pragma endregion 47 | 48 | #pragma region Solid Color Shader 49 | const std::string solidColorVert = R"(#version 300 es 50 | 51 | layout(location = 0) in vec3 In_Position; 52 | layout(location = 1) in vec3 In_Normal; 53 | layout(location = 2) in vec3 In_Tangent; 54 | layout(location = 3) in vec3 In_Bitangent; 55 | layout(location = 4) in vec4 In_Color; 56 | layout(location = 5) in vec2 In_TexCoord; 57 | 58 | uniform mat4 Model; 59 | uniform mat4 View; 60 | uniform mat4 Projection; 61 | uniform mat4 ObjectToWorld; 62 | uniform mat4 ObjectToClip; 63 | uniform mat4 WorldToObject; 64 | 65 | void main() 66 | { 67 | gl_Position = ObjectToClip * vec4(In_Position, 1.0); 68 | } 69 | )"; 70 | const std::string solidColorFrag = R"(#version 300 es 71 | 72 | precision highp float; 73 | 74 | layout(location = 0) out vec4 Out_Color; 75 | 76 | uniform vec4 Color; 77 | 78 | void main() 79 | { 80 | Out_Color = Color; 81 | } 82 | )"; 83 | #pragma endregion 84 | 85 | static std::map renderers; 86 | 87 | Renderer::Renderer(long id) 88 | { 89 | this->id = id; 90 | 91 | camera = new Camera(); 92 | camera->SetPosition(glm::vec3(0.0f, 2.0f, 8.0f)); 93 | camera->SetFov(45.0f); 94 | 95 | simplePipeline = nullptr; 96 | solidColorPipeline = nullptr; 97 | } 98 | 99 | void Renderer::MakeContext(GLADloadproc getProcAddress) 100 | { 101 | gladLoadGLES2Loader(getProcAddress); 102 | } 103 | 104 | void Renderer::LoadScene() 105 | { 106 | Shader vs1 = Shader(GL_VERTEX_SHADER, simpleVert); 107 | Shader fs1 = Shader(GL_FRAGMENT_SHADER, simpleFrag); 108 | simplePipeline = new RenderPipeline(vs1, fs1); 109 | 110 | Shader vs2 = Shader(GL_VERTEX_SHADER, solidColorVert); 111 | Shader fs2 = Shader(GL_FRAGMENT_SHADER, solidColorFrag); 112 | solidColorPipeline = new RenderPipeline(vs2, fs2); 113 | 114 | cubeMeshes = { MeshFactory::CreateCube() }; 115 | } 116 | 117 | void Renderer::UpdateScene(double deltaSeconds, glm::vec2 size) 118 | { 119 | model = glm::rotate(glm::mat4(1.0f), (float)deltaSeconds, glm::vec3(0.0f, 1.0f, 0.0f)); 120 | color = glm::mix(glm::vec4(1.0f), glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), (float)sin(deltaSeconds)); 121 | 122 | camera->SetWidth((int)size.x); 123 | camera->SetHeight((int)size.y); 124 | } 125 | 126 | void Renderer::DrawScene(double deltaSeconds) 127 | { 128 | glClearColor(0.2f, 0.2f, 0.2f, 1.0f); 129 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 130 | 131 | // Cube 132 | { 133 | glm::mat4 m = model; 134 | 135 | for (Mesh* mesh : cubeMeshes) 136 | { 137 | simplePipeline->Bind(); 138 | 139 | simplePipeline->SetUniform("Model", m); 140 | simplePipeline->SetUniform("View", camera->View()); 141 | simplePipeline->SetUniform("Projection", camera->Projection()); 142 | simplePipeline->SetUniform("ObjectToWorld", m); 143 | simplePipeline->SetUniform("ObjectToClip", camera->Projection() * camera->View() * m); 144 | simplePipeline->SetUniform("WorldToObject", glm::inverse(m)); 145 | simplePipeline->SetUniform("Color", glm::vec4(1.0f)); 146 | 147 | mesh->VertexAttributePointer(simplePipeline->GetAttribLocation("In_Position"), 3, "Position"); 148 | mesh->VertexAttributePointer(simplePipeline->GetAttribLocation("In_Normal"), 3, "Normal"); 149 | mesh->VertexAttributePointer(simplePipeline->GetAttribLocation("In_Tangent"), 3, "Tangent"); 150 | mesh->VertexAttributePointer(simplePipeline->GetAttribLocation("In_Bitangent"), 3, "Bitangent"); 151 | mesh->VertexAttributePointer(simplePipeline->GetAttribLocation("In_Color"), 4, "Color"); 152 | mesh->VertexAttributePointer(simplePipeline->GetAttribLocation("In_TexCoord"), 2, "TexCoord"); 153 | 154 | mesh->Draw(); 155 | 156 | simplePipeline->Unbind(); 157 | } 158 | } 159 | } 160 | 161 | Renderer* Renderer::GetInstance(long id) 162 | { 163 | if (id < 0) 164 | { 165 | id = (long)renderers.size(); 166 | } 167 | 168 | if (renderers.find(id) == renderers.end()) 169 | { 170 | renderers[id] = new Renderer(id); 171 | } 172 | 173 | return renderers[id]; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/Renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Camera.h" 9 | #include "RenderPipeline.h" 10 | #include "Shader.h" 11 | #include "Mesh.h" 12 | #include "MeshFactory.h" 13 | 14 | namespace GraphicsHostApp 15 | { 16 | class Renderer 17 | { 18 | public: 19 | Renderer(long id); 20 | 21 | long Id() const { return id; } 22 | 23 | void MakeContext(GLADloadproc getProcAddress); 24 | void LoadScene(); 25 | void UpdateScene(double deltaSeconds, glm::vec2 size); 26 | void DrawScene(double deltaSeconds); 27 | 28 | static Renderer* GetInstance(long id = -1); 29 | 30 | private: 31 | long id; 32 | Camera* camera; 33 | RenderPipeline* simplePipeline; 34 | RenderPipeline* solidColorPipeline; 35 | std::vector cubeMeshes = {}; 36 | glm::mat4 model = glm::mat4(1.0f); 37 | glm::vec4 color = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f); 38 | }; 39 | } -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/Shader.cpp: -------------------------------------------------------------------------------- 1 | #include "Shader.h" 2 | 3 | namespace GraphicsHostApp 4 | { 5 | Shader::Shader(GLenum shaderType, std::string source) 6 | { 7 | const char* c_source = source.c_str(); 8 | 9 | m_handle = glCreateShader(shaderType); 10 | glShaderSource(m_handle, 1, &c_source, nullptr); 11 | glCompileShader(m_handle); 12 | 13 | // Check for shader compile errors. 14 | GLint length; 15 | glGetShaderiv(m_handle, GL_INFO_LOG_LENGTH, &length); 16 | GLchar* m_infoLog = new GLchar[length]; 17 | glGetShaderInfoLog(m_handle, length * 2, nullptr, m_infoLog); 18 | 19 | if (length > 0) 20 | { 21 | glDeleteShader(m_handle); 22 | 23 | throw std::runtime_error(m_infoLog); 24 | } 25 | } 26 | 27 | Shader::~Shader() 28 | { 29 | glDeleteShader(m_handle); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/Shader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GLObject.h" 4 | 5 | namespace GraphicsHostApp 6 | { 7 | class Shader : public GLObject 8 | { 9 | public: 10 | Shader(GLenum shaderType, std::string source); 11 | ~Shader(); 12 | }; 13 | } -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/Graphics/Vertex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace GraphicsHostApp 6 | { 7 | struct Vertex 8 | { 9 | glm::vec3 Position; 10 | glm::vec3 Normal; 11 | glm::vec3 Tangent; 12 | glm::vec3 Bitangent; 13 | glm::vec4 Color; 14 | glm::vec2 TexCoord; 15 | 16 | Vertex() = default; 17 | 18 | Vertex(const glm::vec3& position, const glm::vec3& normal = glm::vec3(0.0f), const glm::vec3& tangent = glm::vec3(0.0f), const glm::vec3& bitangent = glm::vec3(0.0f), const glm::vec4& color = glm::vec4(1.f), const glm::vec2& texCoord = glm::vec2(0.0f)) : Position(position), Normal(normal), Tangent(tangent), Bitangent(bitangent), Color(color), TexCoord(texCoord) 19 | { 20 | } 21 | }; 22 | } -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/GraphicsHostApp.OpenGL.cpp: -------------------------------------------------------------------------------- 1 | #include "GraphicsHostApp.OpenGL.h" 2 | 3 | void MakeContext(GLADloadproc getProcAddress, long* id) 4 | { 5 | Renderer* renderer = Renderer::GetInstance(); 6 | renderer->MakeContext(getProcAddress); 7 | 8 | *id = renderer->Id(); 9 | } 10 | 11 | void LoadScene(long id) 12 | { 13 | Renderer::GetInstance(id)->LoadScene(); 14 | } 15 | 16 | void UpdateScene(long id, double deltaSeconds, const glm::vec2* size) 17 | { 18 | Renderer::GetInstance(id)->UpdateScene(deltaSeconds, *size); 19 | } 20 | 21 | void DrawScene(long id, double deltaSeconds) 22 | { 23 | Renderer::GetInstance(id)->DrawScene(deltaSeconds); 24 | } 25 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/GraphicsHostApp.OpenGL.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Graphics/Renderer.h" 8 | 9 | using namespace GraphicsHostApp; 10 | 11 | #if WIN32 12 | #define EXPORT extern "C" __declspec(dllexport) 13 | #else 14 | #define EXPORT extern "C" 15 | #endif 16 | 17 | EXPORT void MakeContext(GLADloadproc getProcAddress, long* id); 18 | 19 | EXPORT void LoadScene(long id); 20 | 21 | EXPORT void UpdateScene(long id, double deltaSeconds, const glm::vec2* size); 22 | 23 | EXPORT void DrawScene(long id, double deltaSeconds); 24 | -------------------------------------------------------------------------------- /GraphicsHostApp.OpenGL/glad/include/KHR/khrplatform.h: -------------------------------------------------------------------------------- 1 | #ifndef __khrplatform_h_ 2 | #define __khrplatform_h_ 3 | 4 | /* 5 | ** Copyright (c) 2008-2018 The Khronos Group Inc. 6 | ** 7 | ** Permission is hereby granted, free of charge, to any person obtaining a 8 | ** copy of this software and/or associated documentation files (the 9 | ** "Materials"), to deal in the Materials without restriction, including 10 | ** without limitation the rights to use, copy, modify, merge, publish, 11 | ** distribute, sublicense, and/or sell copies of the Materials, and to 12 | ** permit persons to whom the Materials are furnished to do so, subject to 13 | ** the following conditions: 14 | ** 15 | ** The above copyright notice and this permission notice shall be included 16 | ** in all copies or substantial portions of the Materials. 17 | ** 18 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 25 | */ 26 | 27 | /* Khronos platform-specific types and definitions. 28 | * 29 | * The master copy of khrplatform.h is maintained in the Khronos EGL 30 | * Registry repository at https://github.com/KhronosGroup/EGL-Registry 31 | * The last semantic modification to khrplatform.h was at commit ID: 32 | * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 33 | * 34 | * Adopters may modify this file to suit their platform. Adopters are 35 | * encouraged to submit platform specific modifications to the Khronos 36 | * group so that they can be included in future versions of this file. 37 | * Please submit changes by filing pull requests or issues on 38 | * the EGL Registry repository linked above. 39 | * 40 | * 41 | * See the Implementer's Guidelines for information about where this file 42 | * should be located on your system and for more details of its use: 43 | * http://www.khronos.org/registry/implementers_guide.pdf 44 | * 45 | * This file should be included as 46 | * #include 47 | * by Khronos client API header files that use its types and defines. 48 | * 49 | * The types in khrplatform.h should only be used to define API-specific types. 50 | * 51 | * Types defined in khrplatform.h: 52 | * khronos_int8_t signed 8 bit 53 | * khronos_uint8_t unsigned 8 bit 54 | * khronos_int16_t signed 16 bit 55 | * khronos_uint16_t unsigned 16 bit 56 | * khronos_int32_t signed 32 bit 57 | * khronos_uint32_t unsigned 32 bit 58 | * khronos_int64_t signed 64 bit 59 | * khronos_uint64_t unsigned 64 bit 60 | * khronos_intptr_t signed same number of bits as a pointer 61 | * khronos_uintptr_t unsigned same number of bits as a pointer 62 | * khronos_ssize_t signed size 63 | * khronos_usize_t unsigned size 64 | * khronos_float_t signed 32 bit floating point 65 | * khronos_time_ns_t unsigned 64 bit time in nanoseconds 66 | * khronos_utime_nanoseconds_t unsigned time interval or absolute time in 67 | * nanoseconds 68 | * khronos_stime_nanoseconds_t signed time interval in nanoseconds 69 | * khronos_boolean_enum_t enumerated boolean type. This should 70 | * only be used as a base type when a client API's boolean type is 71 | * an enum. Client APIs which use an integer or other type for 72 | * booleans cannot use this as the base type for their boolean. 73 | * 74 | * Tokens defined in khrplatform.h: 75 | * 76 | * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. 77 | * 78 | * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. 79 | * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. 80 | * 81 | * Calling convention macros defined in this file: 82 | * KHRONOS_APICALL 83 | * KHRONOS_APIENTRY 84 | * KHRONOS_APIATTRIBUTES 85 | * 86 | * These may be used in function prototypes as: 87 | * 88 | * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( 89 | * int arg1, 90 | * int arg2) KHRONOS_APIATTRIBUTES; 91 | */ 92 | 93 | #if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) 94 | # define KHRONOS_STATIC 1 95 | #endif 96 | 97 | /*------------------------------------------------------------------------- 98 | * Definition of KHRONOS_APICALL 99 | *------------------------------------------------------------------------- 100 | * This precedes the return type of the function in the function prototype. 101 | */ 102 | #if defined(KHRONOS_STATIC) 103 | /* If the preprocessor constant KHRONOS_STATIC is defined, make the 104 | * header compatible with static linking. */ 105 | # define KHRONOS_APICALL 106 | #elif defined(_WIN32) 107 | # define KHRONOS_APICALL __declspec(dllimport) 108 | #elif defined (__SYMBIAN32__) 109 | # define KHRONOS_APICALL IMPORT_C 110 | #elif defined(__ANDROID__) 111 | # define KHRONOS_APICALL __attribute__((visibility("default"))) 112 | #else 113 | # define KHRONOS_APICALL 114 | #endif 115 | 116 | /*------------------------------------------------------------------------- 117 | * Definition of KHRONOS_APIENTRY 118 | *------------------------------------------------------------------------- 119 | * This follows the return type of the function and precedes the function 120 | * name in the function prototype. 121 | */ 122 | #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) 123 | /* Win32 but not WinCE */ 124 | # define KHRONOS_APIENTRY __stdcall 125 | #else 126 | # define KHRONOS_APIENTRY 127 | #endif 128 | 129 | /*------------------------------------------------------------------------- 130 | * Definition of KHRONOS_APIATTRIBUTES 131 | *------------------------------------------------------------------------- 132 | * This follows the closing parenthesis of the function prototype arguments. 133 | */ 134 | #if defined (__ARMCC_2__) 135 | #define KHRONOS_APIATTRIBUTES __softfp 136 | #else 137 | #define KHRONOS_APIATTRIBUTES 138 | #endif 139 | 140 | /*------------------------------------------------------------------------- 141 | * basic type definitions 142 | *-----------------------------------------------------------------------*/ 143 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) 144 | 145 | 146 | /* 147 | * Using 148 | */ 149 | #include 150 | typedef int32_t khronos_int32_t; 151 | typedef uint32_t khronos_uint32_t; 152 | typedef int64_t khronos_int64_t; 153 | typedef uint64_t khronos_uint64_t; 154 | #define KHRONOS_SUPPORT_INT64 1 155 | #define KHRONOS_SUPPORT_FLOAT 1 156 | /* 157 | * To support platform where unsigned long cannot be used interchangeably with 158 | * inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t. 159 | * Ideally, we could just use (u)intptr_t everywhere, but this could result in 160 | * ABI breakage if khronos_uintptr_t is changed from unsigned long to 161 | * unsigned long long or similar (this results in different C++ name mangling). 162 | * To avoid changes for existing platforms, we restrict usage of intptr_t to 163 | * platforms where the size of a pointer is larger than the size of long. 164 | */ 165 | #if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__) 166 | #if __SIZEOF_POINTER__ > __SIZEOF_LONG__ 167 | #define KHRONOS_USE_INTPTR_T 168 | #endif 169 | #endif 170 | 171 | #elif defined(__VMS ) || defined(__sgi) 172 | 173 | /* 174 | * Using 175 | */ 176 | #include 177 | typedef int32_t khronos_int32_t; 178 | typedef uint32_t khronos_uint32_t; 179 | typedef int64_t khronos_int64_t; 180 | typedef uint64_t khronos_uint64_t; 181 | #define KHRONOS_SUPPORT_INT64 1 182 | #define KHRONOS_SUPPORT_FLOAT 1 183 | 184 | #elif defined(_WIN32) && !defined(__SCITECH_SNAP__) 185 | 186 | /* 187 | * Win32 188 | */ 189 | typedef __int32 khronos_int32_t; 190 | typedef unsigned __int32 khronos_uint32_t; 191 | typedef __int64 khronos_int64_t; 192 | typedef unsigned __int64 khronos_uint64_t; 193 | #define KHRONOS_SUPPORT_INT64 1 194 | #define KHRONOS_SUPPORT_FLOAT 1 195 | 196 | #elif defined(__sun__) || defined(__digital__) 197 | 198 | /* 199 | * Sun or Digital 200 | */ 201 | typedef int khronos_int32_t; 202 | typedef unsigned int khronos_uint32_t; 203 | #if defined(__arch64__) || defined(_LP64) 204 | typedef long int khronos_int64_t; 205 | typedef unsigned long int khronos_uint64_t; 206 | #else 207 | typedef long long int khronos_int64_t; 208 | typedef unsigned long long int khronos_uint64_t; 209 | #endif /* __arch64__ */ 210 | #define KHRONOS_SUPPORT_INT64 1 211 | #define KHRONOS_SUPPORT_FLOAT 1 212 | 213 | #elif 0 214 | 215 | /* 216 | * Hypothetical platform with no float or int64 support 217 | */ 218 | typedef int khronos_int32_t; 219 | typedef unsigned int khronos_uint32_t; 220 | #define KHRONOS_SUPPORT_INT64 0 221 | #define KHRONOS_SUPPORT_FLOAT 0 222 | 223 | #else 224 | 225 | /* 226 | * Generic fallback 227 | */ 228 | #include 229 | typedef int32_t khronos_int32_t; 230 | typedef uint32_t khronos_uint32_t; 231 | typedef int64_t khronos_int64_t; 232 | typedef uint64_t khronos_uint64_t; 233 | #define KHRONOS_SUPPORT_INT64 1 234 | #define KHRONOS_SUPPORT_FLOAT 1 235 | 236 | #endif 237 | 238 | 239 | /* 240 | * Types that are (so far) the same on all platforms 241 | */ 242 | typedef signed char khronos_int8_t; 243 | typedef unsigned char khronos_uint8_t; 244 | typedef signed short int khronos_int16_t; 245 | typedef unsigned short int khronos_uint16_t; 246 | 247 | /* 248 | * Types that differ between LLP64 and LP64 architectures - in LLP64, 249 | * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears 250 | * to be the only LLP64 architecture in current use. 251 | */ 252 | #ifdef KHRONOS_USE_INTPTR_T 253 | typedef intptr_t khronos_intptr_t; 254 | typedef uintptr_t khronos_uintptr_t; 255 | #elif defined(_WIN64) 256 | typedef signed long long int khronos_intptr_t; 257 | typedef unsigned long long int khronos_uintptr_t; 258 | #else 259 | typedef signed long int khronos_intptr_t; 260 | typedef unsigned long int khronos_uintptr_t; 261 | #endif 262 | 263 | #if defined(_WIN64) 264 | typedef signed long long int khronos_ssize_t; 265 | typedef unsigned long long int khronos_usize_t; 266 | #else 267 | typedef signed long int khronos_ssize_t; 268 | typedef unsigned long int khronos_usize_t; 269 | #endif 270 | 271 | #if KHRONOS_SUPPORT_FLOAT 272 | /* 273 | * Float type 274 | */ 275 | typedef float khronos_float_t; 276 | #endif 277 | 278 | #if KHRONOS_SUPPORT_INT64 279 | /* Time types 280 | * 281 | * These types can be used to represent a time interval in nanoseconds or 282 | * an absolute Unadjusted System Time. Unadjusted System Time is the number 283 | * of nanoseconds since some arbitrary system event (e.g. since the last 284 | * time the system booted). The Unadjusted System Time is an unsigned 285 | * 64 bit value that wraps back to 0 every 584 years. Time intervals 286 | * may be either signed or unsigned. 287 | */ 288 | typedef khronos_uint64_t khronos_utime_nanoseconds_t; 289 | typedef khronos_int64_t khronos_stime_nanoseconds_t; 290 | #endif 291 | 292 | /* 293 | * Dummy value used to pad enum types to 32 bits. 294 | */ 295 | #ifndef KHRONOS_MAX_ENUM 296 | #define KHRONOS_MAX_ENUM 0x7FFFFFFF 297 | #endif 298 | 299 | /* 300 | * Enumerated boolean type 301 | * 302 | * Values other than zero should be considered to be true. Therefore 303 | * comparisons should not be made against KHRONOS_TRUE. 304 | */ 305 | typedef enum { 306 | KHRONOS_FALSE = 0, 307 | KHRONOS_TRUE = 1, 308 | KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM 309 | } khronos_boolean_enum_t; 310 | 311 | #endif /* __khrplatform_h_ */ 312 | -------------------------------------------------------------------------------- /GraphicsHostApp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.9.34607.119 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphicsHostApp", "GraphicsHostApp\GraphicsHostApp.csproj", "{0A288C9D-FDA5-4958-A171-7E2449037958}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphicsHostApp.Desktop", "GraphicsHostApp.Desktop\GraphicsHostApp.Desktop.csproj", "{C54E05B4-3A5A-4552-B112-366E7E38B904}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{9415F1A5-165E-48DE-928A-6291D2E3460A}" 11 | ProjectSection(SolutionItems) = preProject 12 | .gitattributes = .gitattributes 13 | .gitignore = .gitignore 14 | README.md = README.md 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {0A288C9D-FDA5-4958-A171-7E2449037958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {0A288C9D-FDA5-4958-A171-7E2449037958}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {0A288C9D-FDA5-4958-A171-7E2449037958}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {0A288C9D-FDA5-4958-A171-7E2449037958}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {C54E05B4-3A5A-4552-B112-366E7E38B904}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {C54E05B4-3A5A-4552-B112-366E7E38B904}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {C54E05B4-3A5A-4552-B112-366E7E38B904}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {C54E05B4-3A5A-4552-B112-366E7E38B904}.Release|Any CPU.Build.0 = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(SolutionProperties) = preSolution 33 | HideSolutionNode = FALSE 34 | EndGlobalSection 35 | GlobalSection(ExtensibilityGlobals) = postSolution 36 | SolutionGuid = {983D7867-7247-4151-ACC3-2320FD2150D3} 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /GraphicsHostApp/App.axaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /GraphicsHostApp/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Data.Core.Plugins; 4 | using Avalonia.Markup.Xaml; 5 | 6 | using GraphicsHostApp.ViewModels; 7 | using GraphicsHostApp.Views; 8 | 9 | namespace GraphicsHostApp; 10 | 11 | public partial class App : Application 12 | { 13 | public override void Initialize() 14 | { 15 | AvaloniaXamlLoader.Load(this); 16 | } 17 | 18 | public override void OnFrameworkInitializationCompleted() 19 | { 20 | // Line below is needed to remove Avalonia data validation. 21 | // Without this line you will get duplicate validations from both Avalonia and CT 22 | BindingPlugins.DataValidators.RemoveAt(0); 23 | 24 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 25 | { 26 | desktop.MainWindow = new MainWindow 27 | { 28 | DataContext = new MainViewModel() 29 | }; 30 | } 31 | else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) 32 | { 33 | singleViewPlatform.MainView = new MainView 34 | { 35 | DataContext = new MainViewModel() 36 | }; 37 | } 38 | 39 | base.OnFrameworkInitializationCompleted(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /GraphicsHostApp/Assets/avalonia-logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qian-o/GraphicsHostApp/952735d2b3e9030f37cbd98240e12ff09fc333c3/GraphicsHostApp/Assets/avalonia-logo.ico -------------------------------------------------------------------------------- /GraphicsHostApp/Contracts/Services/IDrawingService.cs: -------------------------------------------------------------------------------- 1 | namespace GraphicsHostApp.Contracts.Services; 2 | 3 | public interface IDrawingService 4 | { 5 | void Load(object[] args); 6 | 7 | void Update(double deltaSeconds); 8 | 9 | void Render(double deltaSeconds); 10 | } 11 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/Camera.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphicsHostApp.Helpers; 3 | using Silk.NET.Maths; 4 | 5 | namespace GraphicsHostApp.Graphics; 6 | 7 | public class Camera 8 | { 9 | private Vector3D front = -Vector3D.UnitZ; 10 | private Vector3D up = Vector3D.UnitY; 11 | private Vector3D right = Vector3D.UnitX; 12 | private float pitch; 13 | private float yaw = -MathHelper.PiOver2; 14 | private float fov = MathHelper.PiOver2; 15 | 16 | public int Width { get; set; } 17 | 18 | public int Height { get; set; } 19 | 20 | public Vector3D Position { get; set; } = new(0.0f, 0.0f, 0.0f); 21 | 22 | public Vector3D Front => front; 23 | 24 | public Vector3D Up => up; 25 | 26 | public Vector3D Right => right; 27 | 28 | public float Pitch 29 | { 30 | get => MathHelper.RadiansToDegrees(pitch); 31 | set 32 | { 33 | pitch = MathHelper.DegreesToRadians(MathHelper.Clamp(value, -89f, 89f)); 34 | 35 | UpdateVectors(); 36 | } 37 | } 38 | 39 | public float Yaw 40 | { 41 | get => MathHelper.RadiansToDegrees(yaw); 42 | set 43 | { 44 | yaw = MathHelper.DegreesToRadians(value); 45 | 46 | UpdateVectors(); 47 | } 48 | } 49 | 50 | public float Fov 51 | { 52 | get => MathHelper.RadiansToDegrees(fov); 53 | set 54 | { 55 | fov = MathHelper.DegreesToRadians(MathHelper.Clamp(value, 1f, 90f)); 56 | } 57 | } 58 | 59 | public Matrix4X4 View => Matrix4X4.CreateLookAt(Position, Position + Front, Up); 60 | 61 | public Matrix4X4 Projection => Matrix4X4.CreatePerspectiveFieldOfView(fov, (float)Width / Height, 0.1f, 1000.0f); 62 | 63 | private void UpdateVectors() 64 | { 65 | front.X = MathF.Cos(pitch) * MathF.Cos(yaw); 66 | front.Y = MathF.Sin(pitch); 67 | front.Z = MathF.Cos(pitch) * MathF.Sin(yaw); 68 | 69 | front = Vector3D.Normalize(front); 70 | 71 | right = Vector3D.Normalize(Vector3D.Cross(front, Vector3D.UnitY)); 72 | up = Vector3D.Normalize(Vector3D.Cross(right, front)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/Disposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GraphicsHostApp.Graphics; 4 | 5 | public abstract class Disposable : IDisposable 6 | { 7 | private bool disposedValue; 8 | 9 | ~Disposable() 10 | { 11 | Dispose(disposing: false); 12 | } 13 | 14 | protected abstract void Destroy(bool disposing = false); 15 | 16 | protected virtual void Dispose(bool disposing) 17 | { 18 | if (!disposedValue) 19 | { 20 | Destroy(disposing); 21 | 22 | disposedValue = true; 23 | } 24 | } 25 | 26 | public void Dispose() 27 | { 28 | Dispose(disposing: true); 29 | GC.SuppressFinalize(this); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/IGraphicsHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Silk.NET.Core.Native; 3 | 4 | namespace GraphicsHostApp.Graphics; 5 | 6 | public delegate void DeltaAction(double deltaSeconds); 7 | 8 | public delegate void SizeAction(int width, int height); 9 | 10 | public interface IGraphicsHost where TContext : NativeAPI 11 | { 12 | event Action? OnLoad; 13 | 14 | event Action? OnUnload; 15 | 16 | event DeltaAction? OnUpdate; 17 | 18 | event DeltaAction? OnRender; 19 | 20 | event SizeAction? OnResize; 21 | 22 | TContext GetContext(); 23 | } 24 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/OpenGL/Buffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Silk.NET.OpenGLES; 3 | 4 | namespace GraphicsHostApp.Graphics.OpenGL; 5 | 6 | public class Buffer(IGraphicsHost graphicsHost, BufferTargetARB bufferTarget, BufferUsageARB bufferUsage, uint length = 1) : GraphicsResource(graphicsHost) 7 | { 8 | public uint Length { get; } = length; 9 | 10 | public BufferTargetARB BufferTarget { get; } = bufferTarget; 11 | 12 | public BufferUsageARB BufferUsage { get; } = bufferUsage; 13 | 14 | protected override void Destroy(bool disposing = false) 15 | { 16 | GL.DeleteBuffer(Handle); 17 | } 18 | } 19 | 20 | public unsafe class Buffer : Buffer where TDataType : unmanaged 21 | { 22 | public Buffer(IGraphicsHost graphicsHost, BufferTargetARB bufferTarget, BufferUsageARB bufferUsage, uint length = 1) : base(graphicsHost, bufferTarget, bufferUsage, length) 23 | { 24 | Handle = GL.GenBuffer(); 25 | 26 | GL.BindBuffer(BufferTarget, Handle); 27 | GL.BufferData(BufferTarget, (uint)(Length * sizeof(TDataType)), null, BufferUsage); 28 | GL.BindBuffer(BufferTarget, 0); 29 | } 30 | 31 | public void SetData(TDataType[] data, uint offset = 0) 32 | { 33 | if (data.Length != Length) 34 | { 35 | throw new Exception("数据长度必须等于缓冲区长度。"); 36 | } 37 | 38 | fixed (TDataType* dataPtr = data) 39 | { 40 | SetData(dataPtr, offset); 41 | } 42 | } 43 | 44 | public void SetData(TDataType* data, uint offset = 0) 45 | { 46 | GL.BindBuffer(BufferTarget, Handle); 47 | GL.BufferSubData(BufferTarget, (int)(offset * sizeof(TDataType)), (uint)(Length * sizeof(TDataType)), data); 48 | GL.BindBuffer(BufferTarget, 0); 49 | } 50 | 51 | public void SetData(TDataType data, uint offset = 0) 52 | { 53 | GL.BindBuffer(BufferTarget, Handle); 54 | GL.BufferSubData(BufferTarget, (int)(offset * sizeof(TDataType)), (uint)sizeof(TDataType), &data); 55 | GL.BindBuffer(BufferTarget, 0); 56 | } 57 | 58 | public TDataType[] GetData() 59 | { 60 | TDataType[] result = new TDataType[Length]; 61 | 62 | GL.BindBuffer(BufferTarget, Handle); 63 | void* mapBuffer = GL.MapBufferRange(BufferTarget, 0, (uint)(Length * sizeof(TDataType)), (uint)GLEnum.MapReadBit); 64 | 65 | Span resultSpan = new(mapBuffer, (int)Length); 66 | resultSpan.CopyTo(result); 67 | 68 | GL.UnmapBuffer(BufferTarget); 69 | GL.BindBuffer(BufferTarget, 0); 70 | 71 | return result; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/OpenGL/Frame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Silk.NET.OpenGLES; 3 | 4 | namespace GraphicsHostApp.Graphics.OpenGL; 5 | 6 | public class Frame : GraphicsResource 7 | { 8 | public Frame(IGraphicsHost graphicsHost) : base(graphicsHost) 9 | { 10 | Handle = GL.GenFramebuffer(); 11 | 12 | Framebuffer = GL.GenFramebuffer(); 13 | ColorBuffer = GL.GenRenderbuffer(); 14 | DepthStencilBuffer = GL.GenRenderbuffer(); 15 | 16 | Texture = new Texture(GraphicsHost); 17 | } 18 | 19 | public int Width { get; private set; } 20 | 21 | public int Height { get; private set; } 22 | 23 | public int Samples { get; private set; } 24 | 25 | public Texture Texture { get; } 26 | 27 | public uint Framebuffer { get; } 28 | 29 | public uint ColorBuffer { get; } 30 | 31 | public uint DepthStencilBuffer { get; } 32 | 33 | protected override void Destroy(bool disposing = false) 34 | { 35 | GL.DeleteFramebuffer(Handle); 36 | 37 | GL.DeleteFramebuffer(Framebuffer); 38 | GL.DeleteRenderbuffer(ColorBuffer); 39 | GL.DeleteRenderbuffer(DepthStencilBuffer); 40 | 41 | Texture.Dispose(); 42 | } 43 | 44 | public void Update(int width, int height, int samples = 1) 45 | { 46 | if (samples < 1) 47 | { 48 | throw new Exception("The number of samples must be greater than or equal to 1."); 49 | } 50 | 51 | if (Width == width && Height == height && Samples == samples) 52 | { 53 | return; 54 | } 55 | 56 | Width = width; 57 | Height = height; 58 | Samples = samples; 59 | 60 | if (Handle == 0) 61 | { 62 | return; 63 | } 64 | 65 | Texture.Clear((uint)Width, (uint)Height); 66 | 67 | GL.BindFramebuffer(GLEnum.Framebuffer, Handle); 68 | GL.FramebufferTexture2D(GLEnum.Framebuffer, GLEnum.ColorAttachment0, GLEnum.Texture2D, Texture.Handle, 0); 69 | GL.BindFramebuffer(GLEnum.Framebuffer, 0); 70 | 71 | // 多重采样缓冲区 72 | { 73 | GL.BindRenderbuffer(GLEnum.Renderbuffer, ColorBuffer); 74 | GL.RenderbufferStorageMultisample(GLEnum.Renderbuffer, (uint)Samples, GLEnum.Rgb8, (uint)Width, (uint)Height); 75 | GL.BindRenderbuffer(GLEnum.Renderbuffer, 0); 76 | 77 | GL.BindRenderbuffer(GLEnum.Renderbuffer, DepthStencilBuffer); 78 | GL.RenderbufferStorageMultisample(GLEnum.Renderbuffer, (uint)Samples, GLEnum.Depth32fStencil8, (uint)Width, (uint)Height); 79 | GL.BindRenderbuffer(GLEnum.Renderbuffer, 0); 80 | 81 | GL.BindFramebuffer(GLEnum.Framebuffer, Framebuffer); 82 | GL.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.ColorAttachment0, GLEnum.Renderbuffer, ColorBuffer); 83 | GL.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.DepthStencilAttachment, GLEnum.Renderbuffer, DepthStencilBuffer); 84 | GL.BindFramebuffer(GLEnum.Framebuffer, 0); 85 | } 86 | } 87 | 88 | public void Bind() 89 | { 90 | GL.BindFramebuffer(GLEnum.Framebuffer, Framebuffer); 91 | GL.Viewport(0, 0, (uint)Width, (uint)Height); 92 | } 93 | 94 | public void Unbind() 95 | { 96 | GL.BindFramebuffer(GLEnum.Framebuffer, 0); 97 | 98 | GL.BindFramebuffer(GLEnum.ReadFramebuffer, Framebuffer); 99 | GL.BindFramebuffer(GLEnum.DrawFramebuffer, Handle); 100 | GL.BlitFramebuffer(0, 0, Width, Height, 0, 0, Width, Height, (uint)GLEnum.ColorBufferBit, GLEnum.Nearest); 101 | GL.BindFramebuffer(GLEnum.DrawFramebuffer, 0); 102 | GL.BindFramebuffer(GLEnum.ReadFramebuffer, 0); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/OpenGL/GraphicsResource.cs: -------------------------------------------------------------------------------- 1 | using Silk.NET.OpenGLES; 2 | 3 | namespace GraphicsHostApp.Graphics.OpenGL; 4 | 5 | public abstract class GraphicsResource(IGraphicsHost graphicsHost) : Disposable 6 | { 7 | protected IGraphicsHost GraphicsHost { get; } = graphicsHost; 8 | 9 | protected GL GL => GraphicsHost.GetContext(); 10 | 11 | public uint Handle { get; protected set; } 12 | } 13 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/OpenGL/Mesh.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using Silk.NET.OpenGLES; 3 | 4 | namespace GraphicsHostApp.Graphics.OpenGL; 5 | 6 | public unsafe class Mesh : GraphicsResource 7 | { 8 | public Mesh(IGraphicsHost graphicsHost, Vertex[] vertices, uint[] indices) : base(graphicsHost) 9 | { 10 | Handle = GL.GenVertexArray(); 11 | ArrayBuffer = GL.GenBuffer(); 12 | IndexBuffer = GL.GenBuffer(); 13 | IndexLength = indices.Length; 14 | 15 | GL.BindVertexArray(Handle); 16 | 17 | GL.BindBuffer(GLEnum.ArrayBuffer, ArrayBuffer); 18 | GL.BufferData(GLEnum.ArrayBuffer, (uint)(vertices.Length * sizeof(Vertex)), vertices, GLEnum.StaticDraw); 19 | 20 | GL.BindBuffer(GLEnum.ElementArrayBuffer, IndexBuffer); 21 | GL.BufferData(GLEnum.ElementArrayBuffer, (uint)(indices.Length * sizeof(uint)), indices, GLEnum.StaticDraw); 22 | 23 | GL.BindVertexArray(0); 24 | } 25 | 26 | public uint ArrayBuffer { get; } 27 | 28 | public uint IndexBuffer { get; } 29 | 30 | public int IndexLength { get; } 31 | 32 | protected override void Destroy(bool disposing = false) 33 | { 34 | GL.DeleteVertexArray(Handle); 35 | GL.DeleteBuffer(ArrayBuffer); 36 | GL.DeleteBuffer(IndexBuffer); 37 | } 38 | 39 | public void VertexAttributePointer(uint index, int size, string fieldName) 40 | { 41 | GL.BindVertexArray(Handle); 42 | 43 | GL.BindBuffer(GLEnum.ArrayBuffer, ArrayBuffer); 44 | GL.VertexAttribPointer(index, size, GLEnum.Float, false, (uint)sizeof(Vertex), (void*)Marshal.OffsetOf(fieldName)); 45 | GL.EnableVertexAttribArray(index); 46 | GL.BindBuffer(GLEnum.ArrayBuffer, 0); 47 | 48 | GL.BindVertexArray(0); 49 | } 50 | 51 | public void Draw() 52 | { 53 | GL.BindVertexArray(Handle); 54 | GL.DrawElements(GLEnum.Triangles, (uint)IndexLength, GLEnum.UnsignedInt, null); 55 | GL.BindVertexArray(0); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/OpenGL/RenderPipeline.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Silk.NET.Maths; 4 | using Silk.NET.OpenGLES; 5 | 6 | namespace GraphicsHostApp.Graphics.OpenGL; 7 | 8 | public unsafe class RenderPipeline : GraphicsResource 9 | { 10 | public RenderPipeline(IGraphicsHost graphicsHost, Shader vs, Shader fs) : base(graphicsHost) 11 | { 12 | Handle = GL.CreateProgram(); 13 | 14 | GL.AttachShader(Handle, vs.Handle); 15 | GL.AttachShader(Handle, fs.Handle); 16 | GL.LinkProgram(Handle); 17 | 18 | string error = GL.GetProgramInfoLog(Handle); 19 | 20 | if (!string.IsNullOrEmpty(error)) 21 | { 22 | GL.DeleteProgram(Handle); 23 | 24 | throw new Exception($"Link: {error}"); 25 | } 26 | } 27 | 28 | protected override void Destroy(bool disposing = false) 29 | { 30 | GL.DeleteProgram(Handle); 31 | } 32 | 33 | public int GetAttribLocation(string name) 34 | { 35 | return GL.GetAttribLocation(Handle, name); 36 | } 37 | 38 | public int GetUniformLocation(string name) 39 | { 40 | return GL.GetUniformLocation(Handle, name); 41 | } 42 | 43 | public void SetUniform(string name, int value) 44 | { 45 | GL.Uniform1(GetUniformLocation(name), value); 46 | } 47 | 48 | public void SetUniform(string name, float value) 49 | { 50 | GL.Uniform1(GetUniformLocation(name), value); 51 | } 52 | 53 | public void SetUniform(string name, Vector2D value) 54 | { 55 | GL.Uniform2(GetUniformLocation(name), value.X, value.Y); 56 | } 57 | 58 | public void SetUniform(string name, Vector3D value) 59 | { 60 | GL.Uniform3(GetUniformLocation(name), value.X, value.Y, value.Z); 61 | } 62 | 63 | public void SetUniform(string name, Vector4D value) 64 | { 65 | GL.Uniform4(GetUniformLocation(name), value.X, value.Y, value.Z, value.W); 66 | } 67 | 68 | public void SetUniform(string name, Matrix2X2 value) 69 | { 70 | GL.UniformMatrix2(GetUniformLocation(name), 1, false, (float*)&value); 71 | } 72 | 73 | public void SetUniform(string name, Matrix3X3 value) 74 | { 75 | GL.UniformMatrix3(GetUniformLocation(name), 1, false, (float*)&value); 76 | } 77 | 78 | public void SetUniform(string name, Matrix4X4 value) 79 | { 80 | GL.UniformMatrix4(GetUniformLocation(name), 1, false, (float*)&value); 81 | } 82 | 83 | public void SetUniform(string name, T value) where T : struct 84 | { 85 | if (!string.IsNullOrEmpty(name)) 86 | { 87 | name = $"{name}."; 88 | } 89 | 90 | foreach (FieldInfo field in value.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public)) 91 | { 92 | Type fieldType = field.FieldType; 93 | 94 | if (fieldType == typeof(int)) 95 | { 96 | SetUniform($"{name}{field.Name}", (int)field.GetValue(value)!); 97 | } 98 | else if (fieldType == typeof(float)) 99 | { 100 | SetUniform($"{name}{field.Name}", (float)field.GetValue(value)!); 101 | } 102 | else if (fieldType == typeof(Vector2D)) 103 | { 104 | SetUniform($"{name}{field.Name}", (Vector2D)field.GetValue(value)!); 105 | } 106 | else if (fieldType == typeof(Vector3D)) 107 | { 108 | SetUniform($"{name}{field.Name}", (Vector3D)field.GetValue(value)!); 109 | } 110 | else if (fieldType == typeof(Vector4D)) 111 | { 112 | SetUniform($"{name}{field.Name}", (Vector4D)field.GetValue(value)!); 113 | } 114 | else if (fieldType == typeof(Matrix2X2)) 115 | { 116 | SetUniform($"{name}{field.Name}", (Matrix2X2)field.GetValue(value)!); 117 | } 118 | else if (fieldType == typeof(Matrix3X3)) 119 | { 120 | SetUniform($"{name}{field.Name}", (Matrix3X3)field.GetValue(value)!); 121 | } 122 | else if (fieldType == typeof(Matrix4X4)) 123 | { 124 | SetUniform($"{name}{field.Name}", (Matrix4X4)field.GetValue(value)!); 125 | } 126 | else 127 | { 128 | throw new NotSupportedException($"不支持的类型:{fieldType}。"); 129 | } 130 | } 131 | } 132 | 133 | public void SetUniform(string name, uint index, Texture texture) 134 | { 135 | GL.ActiveTexture(GLEnum.Texture0 + (int)index); 136 | GL.BindTexture(GLEnum.Texture2D, texture.Handle); 137 | 138 | SetUniform(name, (int)index); 139 | } 140 | 141 | public void Bind() 142 | { 143 | GL.UseProgram(Handle); 144 | 145 | GL.Enable(GLEnum.DepthTest); 146 | } 147 | 148 | public void Unbind() 149 | { 150 | GL.UseProgram(0); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/OpenGL/Renderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using Avalonia; 5 | using Avalonia.Controls; 6 | using Avalonia.OpenGL; 7 | using Avalonia.OpenGL.Controls; 8 | using Avalonia.Threading; 9 | using GraphicsHostApp.Helpers; 10 | using Silk.NET.OpenGLES; 11 | 12 | namespace GraphicsHostApp.Graphics.OpenGL; 13 | 14 | public class Renderer : OpenGlControlBase, IGraphicsHost 15 | { 16 | public static readonly StyledProperty SamplesProperty = AvaloniaProperty.Register("Samples", 4); 17 | 18 | private readonly Stopwatch _stopwatch = new(); 19 | 20 | private GL? context; 21 | private Frame? frame; 22 | 23 | #region Pipelines 24 | private RenderPipeline? canvasPipeline; 25 | #endregion 26 | 27 | #region Meshes 28 | private Mesh[]? canvasMeshes; 29 | #endregion 30 | 31 | public event Action? OnLoad; 32 | public event Action? OnUnload; 33 | public event DeltaAction? OnUpdate; 34 | public event DeltaAction? OnRender; 35 | public event SizeAction? OnResize; 36 | 37 | public int Samples 38 | { 39 | get { return GetValue(SamplesProperty); } 40 | set { SetValue(SamplesProperty, value); } 41 | } 42 | 43 | protected override void OnOpenGlInit(GlInterface gl) 44 | { 45 | _stopwatch.Start(); 46 | 47 | // Initialize the OpenGL context 48 | { 49 | context ??= GL.GetApi(gl.GetProcAddress); 50 | frame ??= new Frame(this); 51 | 52 | using Shader vs = new(this, ShaderType.VertexShader, File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Resources", "Shaders", "Canvas.vert"))); 53 | using Shader fs = new(this, ShaderType.FragmentShader, File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Resources", "Shaders", "Canvas.frag"))); 54 | canvasPipeline = new RenderPipeline(this, vs, fs); 55 | 56 | MeshFactory.GetCanvas(out Vertex[] vertices, out uint[] indices); 57 | canvasMeshes = [new(this, vertices, indices)]; 58 | } 59 | 60 | OnLoad?.Invoke(); 61 | 62 | OnResize?.Invoke((int)Bounds.Width, (int)Bounds.Height); 63 | } 64 | 65 | protected override void OnOpenGlDeinit(GlInterface gl) 66 | { 67 | _stopwatch.Stop(); 68 | 69 | OnUnload?.Invoke(); 70 | 71 | foreach (Mesh mesh in canvasMeshes!) 72 | { 73 | mesh.Dispose(); 74 | } 75 | canvasPipeline!.Dispose(); 76 | frame!.Dispose(); 77 | context!.Dispose(); 78 | 79 | frame = null; 80 | context = null; 81 | } 82 | 83 | protected override void OnOpenGlRender(GlInterface gl, int fb) 84 | { 85 | if (context == null || frame == null || canvasPipeline == null || canvasMeshes == null) 86 | { 87 | throw new InvalidOperationException("The OpenGL context has not been initialized yet."); 88 | } 89 | 90 | frame.Update((int)Bounds.Width, (int)Bounds.Height, Samples); 91 | 92 | frame.Bind(); 93 | { 94 | OnUpdate?.Invoke(_stopwatch.Elapsed.TotalSeconds); 95 | 96 | OnRender?.Invoke(_stopwatch.Elapsed.TotalSeconds); 97 | } 98 | frame.Unbind(); 99 | 100 | { 101 | context.BindFramebuffer(GLEnum.Framebuffer, (uint)fb); 102 | context.Viewport(0, 0, (uint)Bounds.Width, (uint)Bounds.Height); 103 | 104 | context.Clear((uint)GLEnum.ColorBufferBit | (uint)GLEnum.DepthBufferBit | (uint)GLEnum.StencilBufferBit); 105 | 106 | foreach (Mesh mesh in canvasMeshes) 107 | { 108 | canvasPipeline.Bind(); 109 | 110 | canvasPipeline.SetUniform("Tex", 0, frame.Texture); 111 | 112 | mesh.VertexAttributePointer((uint)canvasPipeline.GetAttribLocation("In_Position"), 3, nameof(Vertex.Position)); 113 | mesh.VertexAttributePointer((uint)canvasPipeline.GetAttribLocation("In_Normal"), 3, nameof(Vertex.Normal)); 114 | mesh.VertexAttributePointer((uint)canvasPipeline.GetAttribLocation("In_Tangent"), 3, nameof(Vertex.Tangent)); 115 | mesh.VertexAttributePointer((uint)canvasPipeline.GetAttribLocation("In_Bitangent"), 3, nameof(Vertex.Bitangent)); 116 | mesh.VertexAttributePointer((uint)canvasPipeline.GetAttribLocation("In_Color"), 4, nameof(Vertex.Color)); 117 | mesh.VertexAttributePointer((uint)canvasPipeline.GetAttribLocation("In_TexCoord"), 2, nameof(Vertex.TexCoord)); 118 | 119 | mesh.Draw(); 120 | 121 | canvasPipeline.Unbind(); 122 | } 123 | 124 | context.BindFramebuffer(GLEnum.Framebuffer, 0); 125 | } 126 | 127 | Dispatcher.UIThread.Post(RequestNextFrameRendering, DispatcherPriority.Render); 128 | } 129 | 130 | protected override void OnSizeChanged(SizeChangedEventArgs e) 131 | { 132 | if (context != null) 133 | { 134 | OnResize?.Invoke((int)e.NewSize.Width, (int)e.NewSize.Height); 135 | } 136 | } 137 | 138 | public GL GetContext() 139 | { 140 | if (context == null) 141 | { 142 | throw new InvalidOperationException("The OpenGL context has not been initialized yet."); 143 | } 144 | 145 | return context; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/OpenGL/Shader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Silk.NET.OpenGLES; 3 | 4 | namespace GraphicsHostApp.Graphics.OpenGL; 5 | 6 | public class Shader : GraphicsResource 7 | { 8 | public Shader(IGraphicsHost graphicsHost, ShaderType shaderType, string source) : base(graphicsHost) 9 | { 10 | Handle = GL.CreateShader(shaderType); 11 | 12 | GL.ShaderSource(Handle, source); 13 | GL.CompileShader(Handle); 14 | 15 | string error = GL.GetShaderInfoLog(Handle); 16 | 17 | if (!string.IsNullOrEmpty(error)) 18 | { 19 | GL.DeleteShader(Handle); 20 | 21 | throw new Exception($"{shaderType}: {error}"); 22 | } 23 | } 24 | 25 | protected override void Destroy(bool disposing = false) 26 | { 27 | GL.DeleteShader(Handle); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/OpenGL/Texture.cs: -------------------------------------------------------------------------------- 1 | using Silk.NET.OpenGLES; 2 | 3 | namespace GraphicsHostApp.Graphics.OpenGL; 4 | 5 | public unsafe class Texture : GraphicsResource 6 | { 7 | public Texture(IGraphicsHost graphicsHost) : base(graphicsHost) 8 | { 9 | Handle = GL.GenTexture(); 10 | 11 | GL.BindTexture(TextureTarget.Texture2D, Handle); 12 | 13 | GL.TexParameter(GLEnum.Texture2D, GLEnum.TextureMinFilter, (int)GLEnum.Nearest); 14 | GL.TexParameter(GLEnum.Texture2D, GLEnum.TextureMagFilter, (int)GLEnum.Nearest); 15 | GL.TexParameter(GLEnum.Texture2D, GLEnum.TextureWrapS, (int)GLEnum.ClampToEdge); 16 | GL.TexParameter(GLEnum.Texture2D, GLEnum.TextureWrapT, (int)GLEnum.ClampToEdge); 17 | 18 | GL.BindTexture(GLEnum.Texture2D, 0); 19 | } 20 | 21 | protected override void Destroy(bool disposing = false) 22 | { 23 | GL.DeleteTexture(Handle); 24 | } 25 | 26 | public void Write(uint width, uint height, byte* data, bool isAlpha = false) 27 | { 28 | GL.BindTexture(GLEnum.Texture2D, Handle); 29 | 30 | if (isAlpha) 31 | { 32 | GL.TexImage2D(GLEnum.Texture2D, 0, (int)GLEnum.Rgba, width, height, 0, GLEnum.Rgba, GLEnum.UnsignedByte, data); 33 | } 34 | else 35 | { 36 | GL.TexImage2D(GLEnum.Texture2D, 0, (int)GLEnum.Rgb, width, height, 0, GLEnum.Rgb, GLEnum.UnsignedByte, data); 37 | } 38 | 39 | GL.BindTexture(GLEnum.Texture2D, 0); 40 | } 41 | 42 | public void Clear(uint width, uint height, bool isAlpha = false) 43 | { 44 | GL.BindTexture(GLEnum.Texture2D, Handle); 45 | 46 | if (isAlpha) 47 | { 48 | GL.TexImage2D(TextureTarget.Texture2D, 0, (int)InternalFormat.Rgba8, width, height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, null); 49 | } 50 | else 51 | { 52 | GL.TexImage2D(GLEnum.Texture2D, 0, (int)GLEnum.Rgb8, width, height, 0, GLEnum.Rgb, GLEnum.UnsignedByte, null); 53 | } 54 | 55 | GL.BindTexture(GLEnum.Texture2D, 0); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /GraphicsHostApp/Graphics/Vertex.cs: -------------------------------------------------------------------------------- 1 | using Silk.NET.Maths; 2 | 3 | namespace GraphicsHostApp.Graphics; 4 | 5 | public struct Vertex(Vector3D position = default, Vector3D normal = default, Vector3D tangent = default, Vector3D bitangent = default, Vector4D color = default, Vector2D texCoord = default) 6 | { 7 | public Vector3D Position = position; 8 | 9 | public Vector3D Normal = normal; 10 | 11 | public Vector3D Tangent = tangent; 12 | 13 | public Vector3D Bitangent = bitangent; 14 | 15 | public Vector4D Color = color; 16 | 17 | public Vector2D TexCoord = texCoord; 18 | } 19 | -------------------------------------------------------------------------------- /GraphicsHostApp/GraphicsHostApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | enable 5 | latest 6 | True 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /GraphicsHostApp/Helpers/MathExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Silk.NET.Maths; 3 | 4 | namespace GraphicsHostApp.Helpers; 5 | 6 | public static class MathExtensions 7 | { 8 | public static Matrix4X4 Invert(this Matrix4X4 value) where T : unmanaged, IFormattable, IEquatable, IComparable 9 | { 10 | Matrix4X4.Invert(value, out Matrix4X4 result); 11 | 12 | return result; 13 | } 14 | 15 | public static Vector4D ToByte(this Vector4D value) 16 | { 17 | return new Vector4D((byte)(value.X * 255), (byte)(value.Y * 255), (byte)(value.Z * 255), (byte)(value.W * 255)); 18 | } 19 | 20 | public static Vector4D ToSingle(this Vector4D value) 21 | { 22 | return new Vector4D(value.X / 255.0f, value.Y / 255.0f, value.Z / 255.0f, value.W / 255.0f); 23 | } 24 | 25 | public static Vector3D RadianToDegree(this Vector3D value) 26 | { 27 | return value * 180.0f / MathF.PI; 28 | } 29 | 30 | public static Vector3D DegreeToRadian(this Vector3D value) 31 | { 32 | return value * MathF.PI / 180.0f; 33 | } 34 | 35 | public static Vector3D ToEulerAngles(this Quaternion rotation) 36 | { 37 | float yaw = MathF.Atan2(2.0f * (rotation.Y * rotation.W + rotation.X * rotation.Z), 1.0f - 2.0f * (rotation.X * rotation.X + rotation.Y * rotation.Y)); 38 | float pitch = MathF.Asin(2.0f * (rotation.X * rotation.W - rotation.Y * rotation.Z)); 39 | float roll = MathF.Atan2(2.0f * (rotation.X * rotation.Y + rotation.Z * rotation.W), 1.0f - 2.0f * (rotation.X * rotation.X + rotation.Z * rotation.Z)); 40 | 41 | // If any nan or inf, set that value to 0 42 | if (float.IsNaN(yaw) || float.IsInfinity(yaw)) 43 | { 44 | yaw = 0; 45 | } 46 | 47 | if (float.IsNaN(pitch) || float.IsInfinity(pitch)) 48 | { 49 | pitch = 0; 50 | } 51 | 52 | if (float.IsNaN(roll) || float.IsInfinity(roll)) 53 | { 54 | roll = 0; 55 | } 56 | 57 | return new Vector3D(pitch, yaw, roll); 58 | } 59 | 60 | public static Quaternion ToQuaternion(this Vector3D eulerAngles) 61 | { 62 | return Quaternion.CreateFromYawPitchRoll(eulerAngles.Y, eulerAngles.X, eulerAngles.Z); 63 | } 64 | } -------------------------------------------------------------------------------- /GraphicsHostApp/Helpers/MathHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Contracts; 3 | using System.Globalization; 4 | using System.Numerics; 5 | 6 | namespace GraphicsHostApp.Helpers; 7 | 8 | public static class MathHelper 9 | { 10 | /// 11 | /// Defines the value of Pi as a . 12 | /// 13 | public const float Pi = 3.1415927f; 14 | 15 | /// 16 | /// Defines the value of Pi divided by two as a . 17 | /// 18 | public const float PiOver2 = Pi / 2; 19 | 20 | /// 21 | /// Defines the value of Pi divided by three as a . 22 | /// 23 | public const float PiOver3 = Pi / 3; 24 | 25 | /// 26 | /// Defines the value of Pi divided by four as a . 27 | /// 28 | public const float PiOver4 = Pi / 4; 29 | 30 | /// 31 | /// Defines the value of Pi divided by six as a . 32 | /// 33 | public const float PiOver6 = Pi / 6; 34 | 35 | /// 36 | /// Defines the value of Pi multiplied by two as a . 37 | /// 38 | public const float TwoPi = 2 * Pi; 39 | 40 | /// 41 | /// Defines the value of Pi multiplied by 3 and divided by two as a . 42 | /// 43 | public const float ThreePiOver2 = 3 * Pi / 2; 44 | 45 | /// 46 | /// Defines the value of E as a . 47 | /// 48 | public const float E = 2.7182817f; 49 | 50 | /// 51 | /// Defines the base-10 logarithm of E. 52 | /// 53 | public const float Log10E = 0.4342945f; 54 | 55 | /// 56 | /// Defines the base-2 logarithm of E. 57 | /// 58 | public const float Log2E = 1.442695f; 59 | 60 | /// 61 | /// Returns the absolute value of a decimal number. 62 | /// 63 | /// A number that is greater than or equal to MinValue, but less than or equal to MaxValue. 64 | /// A decimal number, x, such that 0 ≤ x ≤ MaxValue. 65 | [Pure] 66 | public static decimal Abs(decimal n) => Math.Abs(n); 67 | 68 | /// 69 | /// Returns the absolute value of a double number. 70 | /// 71 | /// A number that is greater than or equal to MinValue, but less than or equal to MaxValue. 72 | /// A double number, x, such that 0 ≤ x ≤ MaxValue. 73 | [Pure] 74 | public static double Abs(double n) => Math.Abs(n); 75 | 76 | /// 77 | /// Returns the absolute value of a short number. 78 | /// 79 | /// A number that is greater than or equal to MinValue, but less than or equal to MaxValue. 80 | /// A short number, x, such that 0 ≤ x ≤ MaxValue. 81 | [Pure] 82 | public static short Abs(short n) => Math.Abs(n); 83 | 84 | /// 85 | /// Returns the absolute value of a int number. 86 | /// 87 | /// A number that is greater than or equal to MinValue, but less than or equal to MaxValue. 88 | /// A int number, x, such that 0 ≤ x ≤ MaxValue. 89 | [Pure] 90 | public static int Abs(int n) => Math.Abs(n); 91 | 92 | /// 93 | /// Returns the absolute value of a long number. 94 | /// 95 | /// A number that is greater than or equal to MinValue, but less than or equal to MaxValue. 96 | /// A long number, x, such that 0 ≤ x ≤ MaxValue. 97 | [Pure] 98 | public static long Abs(long n) => Math.Abs(n); 99 | 100 | /// 101 | /// Returns the absolute value of a sbyte number. 102 | /// 103 | /// A number that is greater than or equal to MinValue, but less than or equal to MaxValue. 104 | /// A sbyte number, x, such that 0 ≤ x ≤ MaxValue. 105 | [Pure] 106 | public static sbyte Abs(sbyte n) => Math.Abs(n); 107 | 108 | /// 109 | /// Returns the absolute value of a float number. 110 | /// 111 | /// A number that is greater than or equal to MinValue, but less than or equal to MaxValue. 112 | /// A float number, x, such that 0 ≤ x ≤ MaxValue. 113 | [Pure] 114 | public static float Abs(float n) => Math.Abs(n); 115 | 116 | /// 117 | /// Returns the sine of the specified angle. 118 | /// 119 | /// The specified angle. 120 | /// Sine of the angle. If radians is equal to NaN, NegativeInfinity, or PositiveInfinity, this method returns NaN. 121 | [Pure] 122 | public static double Sin(double radians) => Math.Sin(radians); 123 | 124 | /// 125 | /// Returns the hyperbolic sine of the specified angle. 126 | /// 127 | /// The specified angle. 128 | /// Hyperbolic sine of the specified angle. If radians is equal to NaN, NegativeInfinity, or PositiveInfinity, this method returns NaN. 129 | [Pure] 130 | public static double Sinh(double radians) => Math.Sinh(radians); 131 | 132 | /// 133 | /// Returns the arc sine of the specified angle. 134 | /// 135 | /// The specified angle. 136 | /// Arc sine of the specified angle. If radians is equal to NaN, NegativeInfinity, or PositiveInfinity, this method returns NaN. 137 | [Pure] 138 | public static double Asin(double radians) => Math.Asin(radians); 139 | 140 | /// 141 | /// Returns the cosine of the specified angle. 142 | /// 143 | /// The specified angle. 144 | /// Cosine of the angle. If radians is equal to NaN, NegativeInfinity, or PositiveInfinity, this method returns NaN. 145 | [Pure] 146 | public static double Cos(double radians) => Math.Cos(radians); 147 | 148 | /// 149 | /// Returns the hyperbolic cosine of the specified angle. 150 | /// 151 | /// The specified angle. 152 | /// Hyperbolic cosine of the specified angle. If radians is equal to NaN, NegativeInfinity, or PositiveInfinity, this method returns NaN. 153 | [Pure] 154 | public static double Cosh(double radians) => Math.Cosh(radians); 155 | 156 | /// 157 | /// Returns the arc sine of the specified angle. 158 | /// 159 | /// The specified angle. 160 | /// Arc sine of the specified angle. If radians is equal to NaN, NegativeInfinity, or PositiveInfinity, this method returns NaN. 161 | [Pure] 162 | public static double Acos(double radians) => Math.Acos(radians); 163 | 164 | /// 165 | /// Returns the arc sine of the specified angle. 166 | /// 167 | /// The specified angle. 168 | /// Arc sine of the specified angle. If radians is equal to NaN, NegativeInfinity, or PositiveInfinity, this method returns NaN. 169 | [Pure] 170 | public static float Acos(float radians) => MathF.Acos(radians); 171 | 172 | /// 173 | /// Returns the tangent of the specified angle. 174 | /// 175 | /// The specified angle. 176 | /// Tangent of the specified angle. If radians is equal to NaN, NegativeInfinity, or PositiveInfinity, this method returns NaN. 177 | [Pure] 178 | public static double Tan(double radians) => Math.Tan(radians); 179 | 180 | /// 181 | /// Returns the hyperbolic tangent of the specified angle. 182 | /// 183 | /// The specified angle. 184 | /// Hyperbolic tangent of the specified angle. If radians is equal to NaN, NegativeInfinity, or PositiveInfinity, this method returns NaN. 185 | [Pure] 186 | public static double Tanh(double radians) => Math.Tanh(radians); 187 | 188 | /// 189 | /// Returns the arc tangent of the specified angle. 190 | /// 191 | /// The specified angle. 192 | /// Arc tangent of the specified angle. If radians is equal to NaN, NegativeInfinity, or PositiveInfinity, this method returns NaN. 193 | [Pure] 194 | public static double Atan(double radians) => Math.Atan(radians); 195 | 196 | /// 197 | /// Returns the angle whose tangent is the quotient of two specified numbers. 198 | /// 199 | /// The y coordinate of a point. 200 | /// The x coordinate of a point. 201 | /// An angle, θ, measured in radians, such that -π ≤ θ ≤ π, and tan(θ) = y / x, where (x, y) is a point in the Cartesian plane. 202 | [Pure] 203 | public static double Atan2(double y, double x) => Math.Atan2(y, x); 204 | 205 | /// 206 | /// Produces the full product of two 32-bit numbers. 207 | /// 208 | /// The first number to multiply. 209 | /// The second number to multiply. 210 | /// The number containing the product of the specified numbers. 211 | [Pure] 212 | public static long BigMul(int a, int b) => Math.BigMul(a, b); 213 | 214 | /// 215 | /// Returns the square root of a specified number. 216 | /// 217 | /// The number whose square root is to be found. 218 | /// The positive square root of n. 219 | [Pure] 220 | public static double Sqrt(double n) => Math.Sqrt(n); 221 | 222 | /// 223 | /// Returns a specified number raised to the specified power. 224 | /// 225 | /// A double-precision floating-point number to be raised to a power. 226 | /// A double-precision floating-point number that specifies a power. 227 | /// The number x raised to the power y. 228 | [Pure] 229 | public static double Pow(double x, double y) => Math.Pow(x, y); 230 | 231 | /// 232 | /// Returns the smallest integral value greater than or equal to the specified number. 233 | /// 234 | /// A decimal number. 235 | /// The smallest integral value that is greater than or equal to n. Note that this method returns a Decimal instead of an integral type. 236 | [Pure] 237 | public static decimal Ceiling(decimal n) => Math.Ceiling(n); 238 | 239 | /// 240 | /// Returns the smallest integral value greater than or equal to the specified number. 241 | /// 242 | /// A double-precision floating-point number. 243 | /// The smallest integral value that is greater than or equal to n. If n is equal to NaN, NegativeInfinity, or PositiveInfinity, that value is returned. 244 | /// Note that this method returns a Double instead of an integral type. 245 | [Pure] 246 | public static double Ceiling(double n) => Math.Ceiling(n); 247 | 248 | /// 249 | /// Returns the largest integral value less than or equal to the specified number. 250 | /// 251 | /// A decimal number. 252 | /// Returns the largest integral value less than or equal to the specified decimal number. 253 | [Pure] 254 | public static decimal Floor(decimal n) => Math.Floor(n); 255 | 256 | /// 257 | /// Returns the largest integral value less than or equal to the specified number. 258 | /// 259 | /// A double-precision floating-point number. 260 | /// Returns the largest integral value less than or equal to the specified double-precision floating-point number. 261 | [Pure] 262 | public static double Floor(double n) => Math.Floor(n); 263 | 264 | /// 265 | /// Calculates the quotient of two integers and also returns the remainder in an output parameter. 266 | /// 267 | /// The dividend. 268 | /// The divisor. 269 | /// The remainder. 270 | /// The quotient of the specified numbers. 271 | /// b is zero. 272 | [Pure] 273 | public static int DivRem(int a, int b, out int result) => Math.DivRem(a, b, out result); 274 | 275 | /// 276 | /// Calculates the quotient of two longs and also returns the remainder in an output parameter. 277 | /// 278 | /// The dividend. 279 | /// The divisor. 280 | /// The remainder. 281 | /// The quotient of the specified numbers. 282 | /// b is zero. 283 | [Pure] 284 | public static long DivRem(long a, long b, out long result) => Math.DivRem(a, b, out result); 285 | 286 | /// 287 | /// Returns the natural (base e) logarithm of a specified number. 288 | /// 289 | /// A number whose logarithm is to be found. 290 | /// The natural logarithm of n. 291 | [Pure] 292 | public static double Log(double n) => Math.Log(n); 293 | 294 | /// 295 | /// Returns the logarithm of a specified number in a specified base. 296 | /// 297 | /// The specified number. 298 | /// The specified base. 299 | /// The base newBase logarithm of n. 300 | [Pure] 301 | public static double Log(double n, double newBase) => Math.Log(n, newBase); 302 | 303 | /// 304 | /// Returns the base 10 logarithm of a specified number. 305 | /// 306 | /// The specified number. 307 | /// The base 10 log of n. 308 | [Pure] 309 | public static double Log10(double n) => Math.Log10(n); 310 | 311 | /// 312 | /// Returns the base 2 logarithm of a specified number. 313 | /// 314 | /// The specified number. 315 | /// The base 2 log of n. 316 | /// This one will be implemented by System.Math from .netcore 3.0 and onwards. 317 | [Pure] 318 | public static double Log2(double n) => Math.Log(n, 2); 319 | 320 | /// 321 | /// Returns e raised to the specified power. 322 | /// 323 | /// The specified power. 324 | /// The number e raised to the power n. If n equals NaN or PositiveInfinity, that value is returned. If n equals NegativeInfinity, 0 is returned. 325 | [Pure] 326 | public static double Exp(double n) => Math.Exp(n); 327 | 328 | /// 329 | /// Returns the remainder resulting from the division of a specified number by another specified number. 330 | /// 331 | /// A dividend. 332 | /// A divisor. 333 | /// A number equal to a - (b Q), where Q is the quotient of a / b rounded to the nearest integer (if a / b falls halfway between two integers, the even integer is returned). 334 | /// If a - (b Q) is zero, the value +0 is returned if a is positive, or -0 if a is negative. 335 | /// If b = 0, NaN is returned. 336 | [Pure] 337 | public static double IEEERemainder(double a, double b) => Math.IEEERemainder(a, b); 338 | 339 | /// 340 | /// Returns the larger of two bytes. 341 | /// 342 | /// The first of two bytes to compare. 343 | /// The second of two bytes to compare. 344 | /// Parameter a or b, whichever is larger. 345 | [Pure] 346 | public static byte Max(byte a, byte b) => Math.Max(a, b); 347 | 348 | /// 349 | /// Returns the larger of two sbytes. 350 | /// 351 | /// The first of two sbytes to compare. 352 | /// The second of two sbytes to compare. 353 | /// Parameter a or b, whichever is larger. 354 | [Pure] 355 | public static sbyte Max(sbyte a, sbyte b) => Math.Max(a, b); 356 | 357 | /// 358 | /// Returns the larger of two shorts. 359 | /// 360 | /// The first of two shorts to compare. 361 | /// The second of two shorts to compare. 362 | /// Parameter a or b, whichever is larger. 363 | [Pure] 364 | public static short Max(short a, short b) => Math.Max(a, b); 365 | 366 | /// 367 | /// Returns the larger of two ushorts. 368 | /// 369 | /// The first of two ushorts to compare. 370 | /// The second of two ushorts to compare. 371 | /// Parameter a or b, whichever is larger. 372 | [Pure] 373 | public static ushort Max(ushort a, ushort b) => Math.Max(a, b); 374 | 375 | /// 376 | /// Returns the larger of two decimals. 377 | /// 378 | /// The first of two decimals to compare. 379 | /// The second of two decimals to compare. 380 | /// Parameter a or b, whichever is larger. 381 | [Pure] 382 | public static decimal Max(decimal a, decimal b) => Math.Max(a, b); 383 | 384 | /// 385 | /// Returns the larger of two ints. 386 | /// 387 | /// The first of two ints to compare. 388 | /// The second of two ints to compare. 389 | /// Parameter a or b, whichever is larger. 390 | [Pure] 391 | public static int Max(int a, int b) => Math.Max(a, b); 392 | 393 | /// 394 | /// Returns the larger of two uints. 395 | /// 396 | /// The first of two uints to compare. 397 | /// The second of two uints to compare. 398 | /// Parameter a or b, whichever is larger. 399 | [Pure] 400 | public static uint Max(uint a, uint b) => Math.Max(a, b); 401 | 402 | /// 403 | /// Returns the larger of two floats. 404 | /// 405 | /// The first of two floats to compare. 406 | /// The second of two floats to compare. 407 | /// Parameter a or b, whichever is larger. 408 | [Pure] 409 | public static float Max(float a, float b) => Math.Max(a, b); 410 | 411 | /// 412 | /// Returns the larger of two longs. 413 | /// 414 | /// The first of two longs to compare. 415 | /// The second of two longs to compare. 416 | /// Parameter a or b, whichever is larger. 417 | [Pure] 418 | public static long Max(long a, long b) => Math.Max(a, b); 419 | 420 | /// 421 | /// Returns the larger of two ulongs. 422 | /// 423 | /// The first of two ulongs to compare. 424 | /// The second of two ulongs to compare. 425 | /// Parameter a or b, whichever is larger. 426 | [Pure] 427 | public static ulong Max(ulong a, ulong b) => Math.Max(a, b); 428 | 429 | /// 430 | /// Returns the smaller of two bytes. 431 | /// 432 | /// The first of two bytes to compare. 433 | /// The second of two bytes to compare. 434 | /// Parameter a or b, whichever is smaller. 435 | [Pure] 436 | public static byte Min(byte a, byte b) => Math.Min(a, b); 437 | 438 | /// 439 | /// Returns the smaller of two sbytes. 440 | /// 441 | /// The first of two sbytes to compare. 442 | /// The second of two sbytes to compare. 443 | /// Parameter a or b, whichever is smaller. 444 | [Pure] 445 | public static sbyte Min(sbyte a, sbyte b) => Math.Min(a, b); 446 | 447 | /// 448 | /// Returns the smaller of two shorts. 449 | /// 450 | /// The first of two shorts to compare. 451 | /// The second of two shorts to compare. 452 | /// Parameter a or b, whichever is smaller. 453 | [Pure] 454 | public static short Min(short a, short b) => Math.Min(a, b); 455 | 456 | /// 457 | /// Returns the smaller of two ushorts. 458 | /// 459 | /// The first of two ushorts to compare. 460 | /// The second of two ushorts to compare. 461 | /// Parameter a or b, whichever is smaller. 462 | [Pure] 463 | public static ushort Min(ushort a, ushort b) => Math.Min(a, b); 464 | 465 | /// 466 | /// Returns the smaller of two decimals. 467 | /// 468 | /// The first of two decimals to compare. 469 | /// The second of two decimals to compare. 470 | /// Parameter a or b, whichever is smaller. 471 | [Pure] 472 | public static decimal Min(decimal a, decimal b) => Math.Min(a, b); 473 | 474 | /// 475 | /// Returns the smaller of two ints. 476 | /// 477 | /// The first of two ints to compare. 478 | /// The second of two ints to compare. 479 | /// Parameter a or b, whichever is smaller. 480 | [Pure] 481 | public static int Min(int a, int b) => Math.Min(a, b); 482 | 483 | /// 484 | /// Returns the smaller of two uints. 485 | /// 486 | /// The first of two uints to compare. 487 | /// The second of two uints to compare. 488 | /// Parameter a or b, whichever is smaller. 489 | [Pure] 490 | public static uint Min(uint a, uint b) => Math.Min(a, b); 491 | 492 | /// 493 | /// Returns the smaller of two floats. 494 | /// 495 | /// The first of two floats to compare. 496 | /// The second of two floats to compare. 497 | /// Parameter a or b, whichever is smaller. 498 | [Pure] 499 | public static float Min(float a, float b) => Math.Min(a, b); 500 | 501 | /// 502 | /// Returns the smaller of two floats. 503 | /// 504 | /// The first of two floats to compare. 505 | /// The second of two floats to compare. 506 | /// Parameter a or b, whichever is smaller. 507 | [Pure] 508 | public static double Min(double a, double b) => Math.Min(a, b); 509 | 510 | /// 511 | /// Returns the smaller of two longs. 512 | /// 513 | /// The first of two longs to compare. 514 | /// The second of two longs to compare. 515 | /// Parameter a or b, whichever is smaller. 516 | [Pure] 517 | public static long Min(long a, long b) => Math.Min(a, b); 518 | 519 | /// 520 | /// Returns the smaller of two ulongs. 521 | /// 522 | /// The first of two ulongs to compare. 523 | /// The second of two ulongs to compare. 524 | /// Parameter a or b, whichever is smaller. 525 | [Pure] 526 | public static ulong Min(ulong a, ulong b) => Math.Min(a, b); 527 | 528 | /// 529 | /// Rounds a decimal value to a specified number of fractional digits, and uses the specified rounding convention for midpoint values. 530 | /// 531 | /// A decimal number to be rounded. 532 | /// The number of decimal places in the return value. 533 | /// Specification for how to round d if it is midway between two other numbers. 534 | /// The number nearest to d that contains a number of fractional digits equal to digits. If d has fewer fractional digits than digits, d is returned unchanged. 535 | /// digits is less than 0 or greater than 28. 536 | /// mode is not a valid value of MidpointRounding. 537 | /// The result is outside the range of a Decimal. 538 | [Pure] 539 | public static decimal Round(decimal d, int digits, MidpointRounding mode) => Math.Round(d, digits, mode); 540 | 541 | /// 542 | /// Rounds a double-precision floating-point value to a specified number of fractional digits, and uses the specified rounding convention for midpoint values. 543 | /// 544 | /// A double-precision floating-point number to be rounded. 545 | /// The number of fractional digits in the return value. 546 | /// Specification for how to round d if it is midway between two other numbers. 547 | /// The number nearest to d that has a number of fractional digits equal to digits. If d has fewer fractional digits than digits, d is returned unchanged. 548 | /// digits is less than 0 or greater than 15. 549 | /// mode is not a valid value of MidpointRounding. 550 | [Pure] 551 | public static double Round(double d, int digits, MidpointRounding mode) => Math.Round(d, digits, mode); 552 | 553 | /// 554 | /// Rounds a decimal value to the nearest integer, and uses the specified rounding convention for midpoint values. 555 | /// 556 | /// A decimal number to be rounded. 557 | /// Specification for how to round d if it is midway between two other numbers. 558 | /// The integer nearest d. If d is halfway between two numbers, one of which is even and the other odd, then mode determines which of the two is returned. 559 | /// Note that this method returns a Decimal instead of an integral type. 560 | /// mode is not a valid value of MidpointRounding. 561 | /// The result is outside the range of a Decimal. 562 | [Pure] 563 | public static decimal Round(decimal d, MidpointRounding mode) => Math.Round(d, mode); 564 | 565 | /// 566 | /// Rounds a double-precision floating-point value to the nearest integer, and uses the specified rounding convention for midpoint values. 567 | /// 568 | /// A double-precision floating-point number to be rounded. 569 | /// Specification for how to round d if it is midway between two other numbers. 570 | /// The integer nearest d. If d is halfway between two integers, one of which is even and the other odd, then mode determines which of the two is returned. 571 | /// Note that this method returns a Double instead of an integral type. 572 | /// mode is not a valid value of MidpointRounding. 573 | [Pure] 574 | public static double Round(double d, MidpointRounding mode) => Math.Round(d, mode); 575 | 576 | /// 577 | /// Rounds a decimal value to a specified number of fractional digits, and rounds midpoint values to the nearest even number. 578 | /// 579 | /// A decimal number to be rounded. 580 | /// The number of fractional digits in the return value. 581 | /// The number nearest to d that contains a number of fractional digits equal to digits. 582 | /// digits is less than 0 or greater than 15. 583 | /// The result is outside the range of a Decimal. 584 | [Pure] 585 | public static decimal Round(decimal d, int digits) => Math.Round(d, digits); 586 | 587 | /// 588 | /// Rounds a double-precision floating-point value to a specified number of fractional digits, and rounds midpoint values to the nearest even number. 589 | /// 590 | /// A double-precision floating-point number to be rounded. 591 | /// The number of fractional digits in the return value. 592 | /// The number nearest to value that contains a number of fractional digits equal to digits. 593 | /// digits is less than 0 or greater than 15. 594 | [Pure] 595 | public static double Round(double d, int digits) => Math.Round(d, digits); 596 | 597 | /// 598 | /// Rounds a decimal value to the nearest integral value, and rounds midpoint values to the nearest even number. 599 | /// 600 | /// A decimal number to be rounded. 601 | /// The integer nearest the d parameter. If the fractional component of d is halfway between two integers, one of which is even and the other odd, the even number is returned. 602 | /// Note that this method returns a Decimal instead of an integral type. 603 | /// The result is outside the range of a Decimal. 604 | [Pure] 605 | public static decimal Round(decimal d) => Math.Round(d); 606 | 607 | /// 608 | /// Rounds a double-precision floating-point value to the nearest integral value, and rounds midpoint values to the nearest even number. 609 | /// 610 | /// A double-precision floating-point number to be rounded. 611 | /// The integer nearest d. If the fractional component of d is halfway between two integers, one of which is even and the other odd, then the even number is returned. 612 | /// Note that this method returns a Double instead of an integral type. 613 | [Pure] 614 | public static double Round(double d) => Math.Round(d); 615 | 616 | /// 617 | /// Calculates the integral part of a specified decimal number. 618 | /// 619 | /// A number to truncate. 620 | /// The integral part of d; that is, the number that remains after any fractional digits have been discarded. 621 | [Pure] 622 | public static decimal Truncate(decimal d) => Math.Truncate(d); 623 | 624 | /// 625 | /// Calculates the integral part of a specified double-precision floating-point number. 626 | /// 627 | /// A number to truncate. 628 | /// The integral part of d; that is, the number that remains after any fractional digits have been discarded, or one of the values listed in the following table. 629 | [Pure] 630 | public static double Truncate(double d) => Math.Truncate(d); 631 | 632 | /// 633 | /// Returns an integer that indicates the sign of a sbyte. 634 | /// 635 | /// A signed number. 636 | /// If d ≤ -1 returns -1, if 1 ≤ d returns 1 and if d = 0 returns 0. 637 | [Pure] 638 | public static int Sign(sbyte d) => Math.Sign(d); 639 | 640 | /// 641 | /// Returns an integer that indicates the sign of a short. 642 | /// 643 | /// A signed number. 644 | /// If d ≤ -1 returns -1, if 1 ≤ d returns 1 and if d = 0 returns 0. 645 | [Pure] 646 | public static int Sign(short d) => Math.Sign(d); 647 | 648 | /// 649 | /// Returns an integer that indicates the sign of a int. 650 | /// 651 | /// A signed number. 652 | /// If d ≤ -1 returns -1, if 1 ≤ d returns 1 and if d = 0 returns 0. 653 | [Pure] 654 | public static int Sign(int d) => Math.Sign(d); 655 | 656 | /// 657 | /// Returns an integer that indicates the sign of a float. 658 | /// 659 | /// A signed number. 660 | /// If d ≤ -1 returns -1, if 1 ≤ d returns 1 and if d = 0 returns 0. 661 | [Pure] 662 | public static int Sign(float d) => Math.Sign(d); 663 | 664 | /// 665 | /// Returns an integer that indicates the sign of a decimal. 666 | /// 667 | /// A signed number. 668 | /// If d ≤ -1 returns -1, if 1 ≤ d returns 1 and if d = 0 returns 0. 669 | [Pure] 670 | public static int Sign(decimal d) => Math.Sign(d); 671 | 672 | /// 673 | /// Returns an integer that indicates the sign of a double. 674 | /// 675 | /// A signed number. 676 | /// If d ≤ -1 returns -1, if 1 ≤ d returns 1 and if d = 0 returns 0. 677 | [Pure] 678 | public static int Sign(double d) => Math.Sign(d); 679 | 680 | /// 681 | /// Returns an integer that indicates the sign of a long. 682 | /// 683 | /// A signed number. 684 | /// If d ≤ -1 returns -1, if 1 ≤ d returns 1 and if d = 0 returns 0. 685 | [Pure] 686 | public static int Sign(long d) => Math.Sign(d); 687 | 688 | /// 689 | /// Returns the next power of two that is greater than or equal to the specified number. 690 | /// 691 | /// The specified number. 692 | /// The next power of two. 693 | [Pure] 694 | public static long NextPowerOfTwo(long n) 695 | { 696 | if (n < 0) 697 | { 698 | throw new ArgumentOutOfRangeException(nameof(n), "Must be positive."); 699 | } 700 | 701 | return (long)Math.Pow(2, Math.Ceiling(Math.Log(n, 2))); 702 | } 703 | 704 | /// 705 | /// Returns the next power of two that is greater than or equal to the specified number. 706 | /// 707 | /// The specified number. 708 | /// The next power of two. 709 | [Pure] 710 | public static int NextPowerOfTwo(int n) 711 | { 712 | if (n < 0) 713 | { 714 | throw new ArgumentOutOfRangeException(nameof(n), "Must be positive."); 715 | } 716 | 717 | return (int)Math.Pow(2, Math.Ceiling(Math.Log(n, 2))); 718 | } 719 | 720 | /// 721 | /// Returns the next power of two that is greater than or equal to the specified number. 722 | /// 723 | /// The specified number. 724 | /// The next power of two. 725 | [Pure] 726 | public static float NextPowerOfTwo(float n) 727 | { 728 | if (n < 0) 729 | { 730 | throw new ArgumentOutOfRangeException(nameof(n), "Must be positive."); 731 | } 732 | 733 | return MathF.Pow(2, MathF.Ceiling(MathF.Log(n, 2))); 734 | } 735 | 736 | /// 737 | /// Returns the next power of two that is greater than or equal to the specified number. 738 | /// 739 | /// The specified number. 740 | /// The next power of two. 741 | [Pure] 742 | public static double NextPowerOfTwo(double n) 743 | { 744 | if (n < 0) 745 | { 746 | throw new ArgumentOutOfRangeException(nameof(n), "Must be positive."); 747 | } 748 | 749 | return Math.Pow(2, Math.Ceiling(Math.Log(n, 2))); 750 | } 751 | 752 | /// 753 | /// Calculates the factorial of a given natural number. 754 | /// 755 | /// The number. 756 | /// The factorial of . 757 | [Pure] 758 | public static long Factorial(int n) 759 | { 760 | long result = 1; 761 | 762 | for (; n > 1; n--) 763 | { 764 | result *= n; 765 | } 766 | 767 | return result; 768 | } 769 | 770 | /// 771 | /// Calculates the binomial coefficient above . 772 | /// 773 | /// The n. 774 | /// The k. 775 | /// n! / (k! * (n - k)!). 776 | [Pure] 777 | public static long BinomialCoefficient(int n, int k) 778 | { 779 | return Factorial(n) / (Factorial(k) * Factorial(n - k)); 780 | } 781 | 782 | /// 783 | /// Returns an approximation of the inverse square root of left number. 784 | /// 785 | /// A number. 786 | /// An approximation of the inverse square root of the specified number, with an upper error bound of 0.001. 787 | /// 788 | /// This is an improved implementation of the the method known as Carmack's inverse square root 789 | /// which is found in the Quake III source code. This implementation comes from 790 | /// http://www.codemaestro.com/reviews/review00000105.html. For the history of this method, see 791 | /// http://www.beyond3d.com/content/articles/8/. 792 | /// 793 | [Pure] 794 | public static float InverseSqrtFast(float x) 795 | { 796 | unsafe 797 | { 798 | var xhalf = 0.5f * x; 799 | var i = *(int*)&x; // Read bits as integer. 800 | i = 0x5f375a86 - (i >> 1); // Make an initial guess for Newton-Raphson approximation 801 | x = *(float*)&i; // Convert bits back to float 802 | x *= 1.5f - xhalf * x * x; // Perform left single Newton-Raphson step. 803 | return x; 804 | } 805 | } 806 | 807 | /// 808 | /// Returns an approximation of the inverse square root of left number. 809 | /// 810 | /// A number. 811 | /// An approximation of the inverse square root of the specified number, with an upper error bound of 0.001. 812 | /// 813 | /// This is an improved implementation of the the method known as Carmack's inverse square root 814 | /// which is found in the Quake III source code. This implementation comes from 815 | /// http://www.codemaestro.com/reviews/review00000105.html. For the history of this method, see 816 | /// http://www.beyond3d.com/content/articles/8/. 817 | /// double magic number from: https://cs.uwaterloo.ca/~m32rober/rsqrt.pdf 818 | /// chapter 4.8. 819 | /// 820 | [Pure] 821 | public static double InverseSqrtFast(double x) 822 | { 823 | unsafe 824 | { 825 | double xhalf = 0.5 * x; 826 | long i = *(long*)&x; // Read bits as long. 827 | i = 0x5fe6eb50c7b537a9 - (i >> 1); // Make an initial guess for Newton-Raphson approximation 828 | x = *(double*)&i; // Convert bits back to double 829 | x *= 1.5 - xhalf * x * x; // Perform left single Newton-Raphson step. 830 | return x; 831 | } 832 | } 833 | 834 | /// 835 | /// Convert degrees to radians. 836 | /// 837 | /// An angle in degrees. 838 | /// The angle expressed in radians. 839 | [Pure] 840 | public static float DegreesToRadians(float degrees) 841 | { 842 | const float degToRad = MathF.PI / 180.0f; 843 | return degrees * degToRad; 844 | } 845 | 846 | /// 847 | /// Convert radians to degrees. 848 | /// 849 | /// An angle in radians. 850 | /// The angle expressed in degrees. 851 | [Pure] 852 | public static float RadiansToDegrees(float radians) 853 | { 854 | const float radToDeg = 180.0f / MathF.PI; 855 | return radians * radToDeg; 856 | } 857 | 858 | /// 859 | /// Convert degrees to radians. 860 | /// 861 | /// An angle in degrees. 862 | /// The angle expressed in radians. 863 | [Pure] 864 | public static double DegreesToRadians(double degrees) 865 | { 866 | const double degToRad = Math.PI / 180.0; 867 | return degrees * degToRad; 868 | } 869 | 870 | /// 871 | /// Convert radians to degrees. 872 | /// 873 | /// An angle in radians. 874 | /// The angle expressed in degrees. 875 | [Pure] 876 | public static double RadiansToDegrees(double radians) 877 | { 878 | const double radToDeg = 180.0 / Math.PI; 879 | return radians * radToDeg; 880 | } 881 | 882 | /// 883 | /// Swaps two values. 884 | /// 885 | /// The type of the values to swap. 886 | /// The first value. 887 | /// The second value. 888 | public static void Swap(ref T a, ref T b) => (a, b) = (b, a); 889 | 890 | /// 891 | /// Clamps a number between a minimum and a maximum. 892 | /// 893 | /// The number to clamp. 894 | /// The minimum allowed value. 895 | /// The maximum allowed value. 896 | /// min, if n is lower than min; max, if n is higher than max; n otherwise. 897 | [Pure] 898 | public static int Clamp(int n, int min, int max) 899 | { 900 | return Math.Max(Math.Min(n, max), min); 901 | } 902 | 903 | /// 904 | /// Clamps a number between a minimum and a maximum. 905 | /// 906 | /// The number to clamp. 907 | /// The minimum allowed value. 908 | /// The maximum allowed value. 909 | /// min, if n is lower than min; max, if n is higher than max; n otherwise. 910 | [Pure] 911 | public static float Clamp(float n, float min, float max) 912 | { 913 | return Math.Max(Math.Min(n, max), min); 914 | } 915 | 916 | /// 917 | /// Clamps a number between a minimum and a maximum. 918 | /// 919 | /// The number to clamp. 920 | /// The minimum allowed value. 921 | /// The maximum allowed value. 922 | /// min, if n is lower than min; max, if n is higher than max; n otherwise. 923 | [Pure] 924 | public static double Clamp(double n, double min, double max) 925 | { 926 | return Math.Max(Math.Min(n, max), min); 927 | } 928 | 929 | /// 930 | /// Scales the specified number linearly between a minimum and a maximum. 931 | /// 932 | /// If the value range is zero, this function will throw a divide by zero exception. 933 | /// The number to scale. 934 | /// The minimum expected number (inclusive). 935 | /// The maximum expected number (inclusive). 936 | /// The minimum output number (inclusive). 937 | /// The maximum output number (inclusive). 938 | /// The number, scaled linearly between min and max. 939 | [Pure] 940 | public static int MapRange(int value, int valueMin, int valueMax, int resultMin, int resultMax) 941 | { 942 | int inRange = valueMax - valueMin; 943 | int resultRange = resultMax - resultMin; 944 | return resultMin + resultRange * ((value - valueMin) / inRange); 945 | } 946 | 947 | /// 948 | /// Scales the specified number linearly between a minimum and a maximum. 949 | /// 950 | /// If the value range is zero, this function will throw a divide by zero exception. 951 | /// The number to scale. 952 | /// The minimum expected number (inclusive). 953 | /// The maximum expected number (inclusive). 954 | /// The minimum output number (inclusive). 955 | /// The maximum output number (inclusive). 956 | /// The number, scaled linearly between min and max. 957 | [Pure] 958 | public static float MapRange(float value, float valueMin, float valueMax, float resultMin, float resultMax) 959 | { 960 | float inRange = valueMax - valueMin; 961 | float resultRange = resultMax - resultMin; 962 | return resultMin + resultRange * ((value - valueMin) / inRange); 963 | } 964 | 965 | /// 966 | /// Scales the specified number linearly between a minimum and a maximum. 967 | /// 968 | /// If the value range is zero, this function will throw a divide by zero exception. 969 | /// The number to scale. 970 | /// The minimum expected number (inclusive). 971 | /// The maximum expected number (inclusive). 972 | /// The minimum output number (inclusive). 973 | /// The maximum output number (inclusive). 974 | /// The number, scaled linearly between min and max. 975 | [Pure] 976 | public static double MapRange(double value, double valueMin, double valueMax, double resultMin, double resultMax) 977 | { 978 | double inRange = valueMax - valueMin; 979 | double resultRange = resultMax - resultMin; 980 | return resultMin + resultRange * ((value - valueMin) / inRange); 981 | } 982 | 983 | /// 984 | /// Approximates floating point equality with a maximum number of different bits. 985 | /// This is typically used in place of an epsilon comparison. 986 | /// see: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ 987 | /// see: https://stackoverflow.com/questions/3874627/floating-point-comparison-functions-for-c-sharp. 988 | /// 989 | /// the first value to compare. 990 | /// >the second value to compare. 991 | /// the number of floating point bits to check. 992 | /// true if the values are approximately equal; otherwise, false. 993 | [Pure] 994 | public static bool ApproximatelyEqual(float a, float b, int maxDeltaBits) 995 | { 996 | // we use longs here, otherwise we run into a two's complement problem, causing this to fail with -2 and 2.0 997 | long k = BitConverter.SingleToInt32Bits(a); 998 | if (k < 0) 999 | { 1000 | k = int.MinValue - k; 1001 | } 1002 | 1003 | long l = BitConverter.SingleToInt32Bits(b); 1004 | if (l < 0) 1005 | { 1006 | l = int.MinValue - l; 1007 | } 1008 | 1009 | var intDiff = Math.Abs(k - l); 1010 | return intDiff <= 1 << maxDeltaBits; 1011 | } 1012 | 1013 | /// 1014 | /// Approximates double-precision floating point equality by an epsilon (maximum error) value. 1015 | /// This method is designed as a "fits-all" solution and attempts to handle as many cases as possible. 1016 | /// 1017 | /// The first float. 1018 | /// The second float. 1019 | /// The maximum error between the two. 1020 | /// 1021 | /// true if the values are approximately equal within the error margin; otherwise, 1022 | /// false. 1023 | /// 1024 | [Pure] 1025 | public static bool ApproximatelyEqualEpsilon(double a, double b, double epsilon) 1026 | { 1027 | const double doubleNormal = (1L << 52) * double.Epsilon; 1028 | var absA = Math.Abs(a); 1029 | var absB = Math.Abs(b); 1030 | var diff = Math.Abs(a - b); 1031 | 1032 | if (a == b) 1033 | { 1034 | // Shortcut, handles infinities 1035 | return true; 1036 | } 1037 | 1038 | if (a == 0.0f || b == 0.0f || diff < doubleNormal) 1039 | { 1040 | // a or b is zero, or both are extremely close to it. 1041 | // relative error is less meaningful here 1042 | return diff < epsilon * doubleNormal; 1043 | } 1044 | 1045 | // use relative error 1046 | return diff / Math.Min(absA + absB, double.MaxValue) < epsilon; 1047 | } 1048 | 1049 | /// 1050 | /// Approximates single-precision floating point equality by an epsilon (maximum error) value. 1051 | /// This method is designed as a "fits-all" solution and attempts to handle as many cases as possible. 1052 | /// 1053 | /// The first float. 1054 | /// The second float. 1055 | /// The maximum error between the two. 1056 | /// 1057 | /// true if the values are approximately equal within the error margin; otherwise, 1058 | /// false. 1059 | /// 1060 | [Pure] 1061 | public static bool ApproximatelyEqualEpsilon(float a, float b, float epsilon) 1062 | { 1063 | const float floatNormal = (1 << 23) * float.Epsilon; 1064 | var absA = Math.Abs(a); 1065 | var absB = Math.Abs(b); 1066 | var diff = Math.Abs(a - b); 1067 | 1068 | if (a == b) 1069 | { 1070 | // Shortcut, handles infinities 1071 | return true; 1072 | } 1073 | 1074 | if (a == 0.0f || b == 0.0f || diff < floatNormal) 1075 | { 1076 | // a or b is zero, or both are extremely close to it. 1077 | // relative error is less meaningful here 1078 | return diff < epsilon * floatNormal; 1079 | } 1080 | 1081 | // use relative error 1082 | var relativeError = diff / Math.Min(absA + absB, float.MaxValue); 1083 | return relativeError < epsilon; 1084 | } 1085 | 1086 | /// 1087 | /// Approximates equivalence between two single-precision floating-point numbers on a direct human scale. 1088 | /// It is important to note that this does not approximate equality - instead, it merely checks whether or not 1089 | /// two numbers could be considered equivalent to each other within a certain tolerance. The tolerance is 1090 | /// inclusive. 1091 | /// 1092 | /// The first value to compare. 1093 | /// The second value to compare. 1094 | /// The tolerance within which the two values would be considered equivalent. 1095 | /// Whether or not the values can be considered equivalent within the tolerance. 1096 | [Pure] 1097 | public static bool ApproximatelyEquivalent(float a, float b, float tolerance) 1098 | { 1099 | if (a == b) 1100 | { 1101 | // Early bailout, handles infinities 1102 | return true; 1103 | } 1104 | 1105 | var diff = Math.Abs(a - b); 1106 | return diff <= tolerance; 1107 | } 1108 | 1109 | /// 1110 | /// Approximates equivalence between two double-precision floating-point numbers on a direct human scale. 1111 | /// It is important to note that this does not approximate equality - instead, it merely checks whether or not 1112 | /// two numbers could be considered equivalent to each other within a certain tolerance. The tolerance is 1113 | /// inclusive. 1114 | /// 1115 | /// The first value to compare. 1116 | /// The second value to compare. 1117 | /// The tolerance within which the two values would be considered equivalent. 1118 | /// Whether or not the values can be considered equivalent within the tolerance. 1119 | [Pure] 1120 | public static bool ApproximatelyEquivalent(double a, double b, double tolerance) 1121 | { 1122 | if (a == b) 1123 | { 1124 | // Early bailout, handles infinities 1125 | return true; 1126 | } 1127 | 1128 | var diff = Math.Abs(a - b); 1129 | return diff <= tolerance; 1130 | } 1131 | 1132 | /// 1133 | /// Linearly interpolates between a and b by t. 1134 | /// 1135 | /// Start value. 1136 | /// End value. 1137 | /// Value of the interpollation between a and b. Clamped to [0, 1]. 1138 | /// The interpolated result between the a and b values. 1139 | [Pure] 1140 | public static float Lerp(float start, float end, float t) 1141 | { 1142 | t = Math.Clamp(t, 0, 1); 1143 | return start + t * (end - start); 1144 | } 1145 | 1146 | /// 1147 | /// Linearly interpolates between a and b by t. 1148 | /// 1149 | /// Start value. 1150 | /// End value. 1151 | /// Value of the interpollation between a and b. Clamped to [0, 1]. 1152 | /// The interpolated result between the a and b values. 1153 | [Pure] 1154 | public static double Lerp(double start, double end, double t) 1155 | { 1156 | t = Math.Clamp(t, 0, 1); 1157 | return start + t * (end - start); 1158 | } 1159 | 1160 | public static Vector3 Lerp(Vector3 a, Vector3 b, Vector3 c) 1161 | { 1162 | float x = Lerp(a.X, b.X, c.X); 1163 | float y = Lerp(a.Y, b.Y, c.Y); 1164 | float z = Lerp(a.Z, b.Z, c.Z); 1165 | 1166 | return new Vector3(x, y, z); 1167 | } 1168 | 1169 | /// 1170 | /// Normalizes an angle to the range (-180, 180]. 1171 | /// 1172 | /// The angle in degrees to normalize. 1173 | /// The normalized angle in the range (-180, 180]. 1174 | public static float NormalizeAngle(float angle) 1175 | { 1176 | // returns angle in the range [0, 360) 1177 | angle = ClampAngle(angle); 1178 | 1179 | if (angle > 180f) 1180 | { 1181 | // shift angle to range (-180, 180] 1182 | angle -= 360f; 1183 | } 1184 | 1185 | return angle; 1186 | } 1187 | 1188 | /// 1189 | /// Normalizes an angle to the range (-180, 180]. 1190 | /// 1191 | /// The angle in degrees to normalize. 1192 | /// The normalized angle in the range (-180, 180]. 1193 | public static double NormalizeAngle(double angle) 1194 | { 1195 | // returns angle in the range [0, 360) 1196 | angle = ClampAngle(angle); 1197 | 1198 | if (angle > 180f) 1199 | { 1200 | // shift angle to range (-180, 180] 1201 | angle -= 360f; 1202 | } 1203 | 1204 | return angle; 1205 | } 1206 | 1207 | /// 1208 | /// Normalizes an angle to the range (-π, π]. 1209 | /// 1210 | /// The angle in radians to normalize. 1211 | /// The normalized angle in the range (-π, π]. 1212 | public static float NormalizeRadians(float angle) 1213 | { 1214 | // returns angle in the range [0, 2π). 1215 | angle = ClampRadians(angle); 1216 | 1217 | if (angle > Pi) 1218 | { 1219 | // shift angle to range (-π, π] 1220 | angle -= 2 * Pi; 1221 | } 1222 | 1223 | return angle; 1224 | } 1225 | 1226 | /// 1227 | /// Normalizes an angle to the range (-π, π]. 1228 | /// 1229 | /// The angle in radians to normalize. 1230 | /// The normalized angle in the range (-π, π]. 1231 | public static double NormalizeRadians(double angle) 1232 | { 1233 | // returns angle in the range [0, 2π). 1234 | angle = ClampRadians(angle); 1235 | 1236 | if (angle > Pi) 1237 | { 1238 | // shift angle to range (-π, π] 1239 | angle -= 2 * Pi; 1240 | } 1241 | 1242 | return angle; 1243 | } 1244 | 1245 | /// 1246 | /// Clamps an angle to the range [0, 360). 1247 | /// 1248 | /// The angle to clamp in degrees. 1249 | /// The clamped angle in the range [0, 360). 1250 | public static float ClampAngle(float angle) 1251 | { 1252 | // mod angle so it's in the range (-360, 360) 1253 | angle %= 360f; 1254 | 1255 | if (angle < 0.0f) 1256 | { 1257 | // shift angle to the range [0, 360) 1258 | angle += 360f; 1259 | } 1260 | 1261 | return angle; 1262 | } 1263 | 1264 | /// 1265 | /// Clamps an angle to the range [0, 360). 1266 | /// 1267 | /// The angle to clamp in degrees. 1268 | /// The clamped angle in the range [0, 360). 1269 | public static double ClampAngle(double angle) 1270 | { 1271 | // mod angle so it's in the range (-360, 360) 1272 | angle %= 360d; 1273 | 1274 | if (angle < 0.0d) 1275 | { 1276 | // shift angle to the range [0, 360) 1277 | angle += 360d; 1278 | } 1279 | 1280 | return angle; 1281 | } 1282 | 1283 | /// 1284 | /// Clamps an angle to the range [0, 2π). 1285 | /// 1286 | /// The angle to clamp in radians. 1287 | /// The clamped angle in the range [0, 2π). 1288 | public static float ClampRadians(float angle) 1289 | { 1290 | // mod angle so it's in the range (-2π,2π) 1291 | angle %= TwoPi; 1292 | 1293 | if (angle < 0.0f) 1294 | { 1295 | // shift angle to the range [0,2π) 1296 | angle += TwoPi; 1297 | } 1298 | 1299 | return angle; 1300 | } 1301 | 1302 | /// 1303 | /// Clamps an angle to the range [0, 2π). 1304 | /// 1305 | /// The angle to clamp in radians. 1306 | /// The clamped angle in the range [0, 2π). 1307 | public static double ClampRadians(double angle) 1308 | { 1309 | // mod angle so it's in the range (-2π,2π) 1310 | angle %= 2d * Math.PI; 1311 | 1312 | if (angle < 0.0d) 1313 | { 1314 | // shift angle to the range [0,2π) 1315 | angle += 2d * Math.PI; 1316 | } 1317 | 1318 | return angle; 1319 | } 1320 | 1321 | internal static string GetListSeparator(IFormatProvider formatProvider) 1322 | { 1323 | if (formatProvider is CultureInfo cultureInfo) 1324 | { 1325 | return cultureInfo.TextInfo.ListSeparator; 1326 | } 1327 | 1328 | if (formatProvider?.GetFormat(typeof(TextInfo)) is TextInfo textInfo) 1329 | { 1330 | return textInfo.ListSeparator; 1331 | } 1332 | 1333 | return CultureInfo.CurrentCulture.TextInfo.ListSeparator; 1334 | } 1335 | } 1336 | -------------------------------------------------------------------------------- /GraphicsHostApp/Helpers/MeshFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using GraphicsHostApp.Graphics; 3 | 4 | namespace GraphicsHostApp.Helpers; 5 | 6 | public static unsafe class MeshFactory 7 | { 8 | public static void GetCube(out Vertex[] vertices, out uint[] indices, float size = 0.5f) 9 | { 10 | vertices = 11 | [ 12 | // Front face 13 | new(new(-size, -size, size), new(0.0f, 0.0f, 1.0f), texCoord: new(0.0f, 0.0f)), 14 | new(new(size, -size, size), new(0.0f, 0.0f, 1.0f), texCoord: new(1.0f, 0.0f)), 15 | new(new(size, size, size), new(0.0f, 0.0f, 1.0f), texCoord: new(1.0f, 1.0f)), 16 | new(new(size, size, size), new(0.0f, 0.0f, 1.0f), texCoord: new(1.0f, 1.0f)), 17 | new(new(-size, size, size), new(0.0f, 0.0f, 1.0f), texCoord: new(0.0f, 1.0f)), 18 | new(new(-size, -size, size), new(0.0f, 0.0f, 1.0f), texCoord: new(0.0f, 0.0f)), 19 | 20 | // Back face 21 | new(new(-size, -size, -size), new(0.0f, 0.0f, -1.0f), texCoord: new(1.0f, 0.0f)), 22 | new(new(-size, size, -size), new(0.0f, 0.0f, -1.0f), texCoord: new(1.0f, 1.0f)), 23 | new(new(size, size, -size), new(0.0f, 0.0f, -1.0f), texCoord: new(0.0f, 1.0f)), 24 | new(new(size, size, -size), new(0.0f, 0.0f, -1.0f), texCoord: new(0.0f, 1.0f)), 25 | new(new(size, -size, -size), new(0.0f, 0.0f, -1.0f), texCoord: new(0.0f, 0.0f)), 26 | new(new(-size, -size, -size), new(0.0f, 0.0f, -1.0f), texCoord: new(1.0f, 0.0f)), 27 | 28 | // Top face 29 | new(new(-size, size, -size), new(0.0f, 1.0f, 0.0f), texCoord: new(0.0f, 1.0f)), 30 | new(new(-size, size, size), new(0.0f, 1.0f, 0.0f), texCoord: new(0.0f, 0.0f)), 31 | new(new(size, size, size), new(0.0f, 1.0f, 0.0f), texCoord: new(1.0f, 0.0f)), 32 | new(new(size, size, size), new(0.0f, 1.0f, 0.0f), texCoord: new(1.0f, 0.0f)), 33 | new(new(size, size, -size), new(0.0f, 1.0f, 0.0f), texCoord: new(1.0f, 1.0f)), 34 | new(new(-size, size, -size), new(0.0f, 1.0f, 0.0f), texCoord: new(0.0f, 1.0f)), 35 | 36 | // Bottom face 37 | new(new(-size, -size, -size), new(0.0f, -1.0f, 0.0f), texCoord: new(0.0f, 0.0f)), 38 | new(new(size, -size, -size), new(0.0f, -1.0f, 0.0f), texCoord: new(1.0f, 0.0f)), 39 | new(new(size, -size, size), new(0.0f, -1.0f, 0.0f), texCoord: new(1.0f, 1.0f)), 40 | new(new(size, -size, size), new(0.0f, -1.0f, 0.0f), texCoord: new(1.0f, 1.0f)), 41 | new(new(-size, -size, size), new(0.0f, -1.0f, 0.0f), texCoord: new(0.0f, 1.0f)), 42 | new(new(-size, -size, -size), new(0.0f, -1.0f, 0.0f), texCoord: new(0.0f, 0.0f)), 43 | 44 | // Right face 45 | new(new(size, -size, -size), new(1.0f, 0.0f, 0.0f), texCoord: new(1.0f, 0.0f)), 46 | new(new(size, size, -size), new(1.0f, 0.0f, 0.0f), texCoord: new(1.0f, 1.0f)), 47 | new(new(size, size, size), new(1.0f, 0.0f, 0.0f), texCoord: new(0.0f, 1.0f)), 48 | new(new(size, size, size), new(1.0f, 0.0f, 0.0f), texCoord: new(0.0f, 1.0f)), 49 | new(new(size, -size, size), new(1.0f, 0.0f, 0.0f), texCoord: new(0.0f, 0.0f)), 50 | new(new(size, -size, -size), new(1.0f, 0.0f, 0.0f), texCoord: new(1.0f, 0.0f)), 51 | 52 | // Left face 53 | new(new(-size, -size, -size), new(-1.0f, 0.0f, 0.0f), texCoord: new(0.0f, 0.0f)), 54 | new(new(-size, -size, size), new(-1.0f, 0.0f, 0.0f), texCoord: new(1.0f, 0.0f)), 55 | new(new(-size, size, size), new(-1.0f, 0.0f, 0.0f), texCoord: new(1.0f, 1.0f)), 56 | new(new(-size, size, size), new(-1.0f, 0.0f, 0.0f), texCoord: new(1.0f, 1.0f)), 57 | new(new(-size, size, -size), new(-1.0f, 0.0f, 0.0f), texCoord: new(0.0f, 1.0f)), 58 | new(new(-size, -size, -size), new(-1.0f, 0.0f, 0.0f), texCoord: new(0.0f, 0.0f)) 59 | ]; 60 | 61 | indices = vertices.Select((a, b) => (uint)b).ToArray(); 62 | } 63 | 64 | public static void GetCanvas(out Vertex[] vertices, out uint[] indices) 65 | { 66 | vertices = 67 | [ 68 | new(new(-1.0f, 1.0f, 0.0f), new(0.0f, 0.0f, 0.0f), texCoord: new(0.0f, 1.0f)), 69 | new(new(-1.0f, -1.0f, 0.0f), new(0.0f, 0.0f, 0.0f), texCoord: new(0.0f, 0.0f)), 70 | new(new(1.0f, -1.0f, 0.0f), new(0.0f, 0.0f, 0.0f), texCoord: new(1.0f, 0.0f)), 71 | new(new(1.0f, -1.0f, 0.0f), new(0.0f, 0.0f, 0.0f), texCoord: new(1.0f, 0.0f)), 72 | new(new(1.0f, 1.0f, 0.0f), new(0.0f, 0.0f, 0.0f), texCoord: new(1.0f, 1.0f)), 73 | new(new(-1.0f, 1.0f, 0.0f), new(0.0f, 0.0f, 0.0f), texCoord: new(0.0f, 1.0f)) 74 | ]; 75 | 76 | indices = vertices.Select((a, b) => (uint)b).ToArray(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /GraphicsHostApp/Resources/Dependencies/GraphicsHostApp.OpenGL.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qian-o/GraphicsHostApp/952735d2b3e9030f37cbd98240e12ff09fc333c3/GraphicsHostApp/Resources/Dependencies/GraphicsHostApp.OpenGL.dll -------------------------------------------------------------------------------- /GraphicsHostApp/Resources/Dependencies/libGraphicsHostApp.OpenGL.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qian-o/GraphicsHostApp/952735d2b3e9030f37cbd98240e12ff09fc333c3/GraphicsHostApp/Resources/Dependencies/libGraphicsHostApp.OpenGL.so -------------------------------------------------------------------------------- /GraphicsHostApp/Resources/Models/Sphere.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qian-o/GraphicsHostApp/952735d2b3e9030f37cbd98240e12ff09fc333c3/GraphicsHostApp/Resources/Models/Sphere.glb -------------------------------------------------------------------------------- /GraphicsHostApp/Resources/Shaders/Canvas.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | 5 | in vec2 VS_UV; 6 | 7 | layout(location = 0) out vec4 Out_Color; 8 | 9 | uniform sampler2D Tex; 10 | 11 | void main() 12 | { 13 | Out_Color = texture(Tex, VS_UV); 14 | } -------------------------------------------------------------------------------- /GraphicsHostApp/Resources/Shaders/Canvas.vert: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | layout(location = 0) in vec3 In_Position; 4 | layout(location = 1) in vec3 In_Normal; 5 | layout(location = 2) in vec3 In_Tangent; 6 | layout(location = 3) in vec3 In_Bitangent; 7 | layout(location = 4) in vec4 In_Color; 8 | layout(location = 5) in vec2 In_TexCoord; 9 | 10 | out vec2 VS_UV; 11 | 12 | void main() 13 | { 14 | VS_UV = In_TexCoord; 15 | 16 | gl_Position = vec4(In_Position, 1.0); 17 | } -------------------------------------------------------------------------------- /GraphicsHostApp/Resources/Shaders/Simple.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | 5 | in vec4 VS_Color; 6 | 7 | layout(location = 0) out vec4 Out_Color; 8 | 9 | uniform vec4 Color; 10 | 11 | void main() 12 | { 13 | Out_Color = VS_Color * Color; 14 | } -------------------------------------------------------------------------------- /GraphicsHostApp/Resources/Shaders/Simple.vert: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | layout(location = 0) in vec3 In_Position; 4 | layout(location = 1) in vec3 In_Normal; 5 | layout(location = 2) in vec3 In_Tangent; 6 | layout(location = 3) in vec3 In_Bitangent; 7 | layout(location = 4) in vec4 In_Color; 8 | layout(location = 5) in vec2 In_TexCoord; 9 | 10 | out vec4 VS_Color; 11 | 12 | uniform mat4 Model; 13 | uniform mat4 View; 14 | uniform mat4 Projection; 15 | uniform mat4 ObjectToWorld; 16 | uniform mat4 ObjectToClip; 17 | uniform mat4 WorldToObject; 18 | 19 | void main() 20 | { 21 | VS_Color = vec4(In_Normal * 0.5 + vec3(0.5), 1.0); 22 | 23 | gl_Position = ObjectToClip * vec4(In_Position, 1.0); 24 | } -------------------------------------------------------------------------------- /GraphicsHostApp/Resources/Shaders/SolidColor.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | 5 | layout(location = 0) out vec4 Out_Color; 6 | 7 | uniform vec4 Color; 8 | 9 | void main() 10 | { 11 | Out_Color = Color; 12 | } -------------------------------------------------------------------------------- /GraphicsHostApp/Resources/Shaders/SolidColor.vert: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | layout(location = 0) in vec3 In_Position; 4 | layout(location = 1) in vec3 In_Normal; 5 | layout(location = 2) in vec3 In_Tangent; 6 | layout(location = 3) in vec3 In_Bitangent; 7 | layout(location = 4) in vec4 In_Color; 8 | layout(location = 5) in vec2 In_TexCoord; 9 | 10 | uniform mat4 Model; 11 | uniform mat4 View; 12 | uniform mat4 Projection; 13 | uniform mat4 ObjectToWorld; 14 | uniform mat4 ObjectToClip; 15 | uniform mat4 WorldToObject; 16 | 17 | void main() 18 | { 19 | gl_Position = ObjectToClip * vec4(In_Position, 1.0); 20 | } -------------------------------------------------------------------------------- /GraphicsHostApp/Services/ExternalDrawingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using GraphicsHostApp.Contracts.Services; 6 | using GraphicsHostApp.Graphics.OpenGL; 7 | using Silk.NET.Maths; 8 | 9 | namespace GraphicsHostApp.Services; 10 | 11 | public unsafe partial class ExternalDrawingService : IDrawingService 12 | { 13 | static ExternalDrawingService() 14 | { 15 | NativeLibrary.SetDllImportResolver(typeof(ExternalDrawingService).Assembly, (libraryName, assembly, searchPath) => 16 | { 17 | libraryName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? $"{libraryName}.dll" : $"lib{libraryName}.so"; 18 | 19 | string libPath = Path.Combine(AppContext.BaseDirectory, "Resources", "Dependencies", libraryName); 20 | 21 | NativeLibrary.TryLoad(libPath, out nint handle); 22 | 23 | return handle; 24 | }); 25 | } 26 | 27 | #region External Drawing Service 28 | public delegate void* GetProcAddress(string proc); 29 | 30 | [LibraryImport("GraphicsHostApp.OpenGL")] 31 | [UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvCdecl) })] 32 | private static partial void MakeContext(GetProcAddress getProcAddress, out long id); 33 | 34 | [LibraryImport("GraphicsHostApp.OpenGL")] 35 | private static partial void LoadScene(long id); 36 | 37 | [LibraryImport("GraphicsHostApp.OpenGL")] 38 | private static partial void UpdateScene(long id, double deltaSeconds, Vector2D* size); 39 | 40 | [LibraryImport("GraphicsHostApp.OpenGL")] 41 | private static partial void DrawScene(long id, double deltaSeconds); 42 | #endregion 43 | 44 | private Renderer renderer = null!; 45 | private long rendererId; 46 | 47 | public void Load(object[] args) 48 | { 49 | renderer = (Renderer)args[0]; 50 | 51 | MakeContext((proc) => 52 | { 53 | if (renderer.GetContext().Context.TryGetProcAddress(proc, out nint addr)) 54 | { 55 | return (void*)addr; 56 | } 57 | 58 | return (void*)0; 59 | 60 | }, out rendererId); 61 | 62 | LoadScene(rendererId); 63 | } 64 | 65 | public void Update(double deltaSeconds) 66 | { 67 | Vector2D size = new((float)renderer.Bounds.Width, (float)renderer.Bounds.Height); 68 | 69 | UpdateScene(rendererId, deltaSeconds, &size); 70 | } 71 | 72 | public void Render(double deltaSeconds) 73 | { 74 | DrawScene(rendererId, deltaSeconds); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /GraphicsHostApp/Services/SimpleDrawingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using GraphicsHostApp.Contracts.Services; 4 | using GraphicsHostApp.Graphics; 5 | using GraphicsHostApp.Graphics.OpenGL; 6 | using GraphicsHostApp.Helpers; 7 | using Silk.NET.Maths; 8 | using Silk.NET.OpenGLES; 9 | using Shader = GraphicsHostApp.Graphics.OpenGL.Shader; 10 | 11 | namespace GraphicsHostApp.Services; 12 | 13 | public class SimpleDrawingService : IDrawingService 14 | { 15 | #region Uniforms 16 | private struct UniTransforms 17 | { 18 | public Matrix4X4 Model; 19 | 20 | public Matrix4X4 View; 21 | 22 | public Matrix4X4 Projection; 23 | 24 | public Matrix4X4 ObjectToWorld; 25 | 26 | public Matrix4X4 ObjectToClip; 27 | 28 | public Matrix4X4 WorldToObject; 29 | } 30 | 31 | private struct UniParameters 32 | { 33 | public Vector4D Color; 34 | } 35 | #endregion 36 | 37 | private Renderer renderer = null!; 38 | private Camera camera = null!; 39 | 40 | #region Pipelines 41 | private RenderPipeline simplePipeline = null!; 42 | private RenderPipeline solidColorPipeline = null!; 43 | #endregion 44 | 45 | #region Meshes 46 | private Mesh[] cubeMeshes = null!; 47 | #endregion 48 | 49 | private Matrix4X4 model = Matrix4X4.Identity; 50 | private Vector4D color = new(1.0f, 0.0f, 0.0f, 1.0f); 51 | 52 | public void Load(object[] args) 53 | { 54 | renderer = (Renderer)args[0]; 55 | camera = new Camera() 56 | { 57 | Position = new Vector3D(0.0f, 2.0f, 8.0f), 58 | Fov = 45.0f 59 | }; 60 | 61 | using Shader vs1 = new(renderer, ShaderType.VertexShader, File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Resources", "Shaders", "Simple.vert"))); 62 | using Shader fs1 = new(renderer, ShaderType.FragmentShader, File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Resources", "Shaders", "Simple.frag"))); 63 | simplePipeline = new RenderPipeline(renderer, vs1, fs1); 64 | 65 | using Shader vs2 = new(renderer, ShaderType.VertexShader, File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Resources", "Shaders", "SolidColor.vert"))); 66 | using Shader fs2 = new(renderer, ShaderType.FragmentShader, File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Resources", "Shaders", "SolidColor.frag"))); 67 | 68 | solidColorPipeline = new RenderPipeline(renderer, vs2, fs2); 69 | 70 | MeshFactory.GetCube(out Vertex[] vertices, out uint[] indices); 71 | cubeMeshes = [new(renderer, vertices, indices)]; 72 | } 73 | 74 | public void Update(double deltaSeconds) 75 | { 76 | model = Matrix4X4.CreateFromAxisAngle(new Vector3D(0.0f, 1.0f, 0.0f), (float)deltaSeconds); 77 | color = Vector4D.Lerp(Vector4D.One, new Vector4D(1.0f, 0.0f, 0.0f, 1.0f), (float)Math.Sin(deltaSeconds)); 78 | 79 | camera.Width = (int)renderer.Bounds.Width; 80 | camera.Height = (int)renderer.Bounds.Height; 81 | } 82 | 83 | public void Render(double deltaSeconds) 84 | { 85 | GL gl = renderer.GetContext(); 86 | 87 | gl.ClearColor(0.2f, 0.2f, 0.2f, 1.0f); 88 | gl.Clear((uint)GLEnum.ColorBufferBit | (uint)GLEnum.DepthBufferBit | (uint)GLEnum.StencilBufferBit); 89 | 90 | // Cube 91 | { 92 | Matrix4X4 m = model * Matrix4X4.CreateTranslation(new Vector3D(0.0f, 0.0f, 0.0f)); 93 | 94 | foreach (Mesh mesh in cubeMeshes) 95 | { 96 | solidColorPipeline.Bind(); 97 | 98 | solidColorPipeline.SetUniform(string.Empty, new UniTransforms() 99 | { 100 | Model = m, 101 | View = camera.View, 102 | Projection = camera.Projection, 103 | ObjectToWorld = m, 104 | ObjectToClip = m * camera.View * camera.Projection, 105 | WorldToObject = m.Invert() 106 | }); 107 | 108 | solidColorPipeline.SetUniform(string.Empty, new UniParameters() 109 | { 110 | Color = color 111 | }); 112 | 113 | mesh.VertexAttributePointer((uint)solidColorPipeline.GetAttribLocation("In_Position"), 3, nameof(Vertex.Position)); 114 | mesh.VertexAttributePointer((uint)solidColorPipeline.GetAttribLocation("In_Normal"), 3, nameof(Vertex.Normal)); 115 | mesh.VertexAttributePointer((uint)solidColorPipeline.GetAttribLocation("In_Tangent"), 3, nameof(Vertex.Tangent)); 116 | mesh.VertexAttributePointer((uint)solidColorPipeline.GetAttribLocation("In_Bitangent"), 3, nameof(Vertex.Bitangent)); 117 | mesh.VertexAttributePointer((uint)solidColorPipeline.GetAttribLocation("In_Color"), 4, nameof(Vertex.Color)); 118 | mesh.VertexAttributePointer((uint)solidColorPipeline.GetAttribLocation("In_TexCoord"), 2, nameof(Vertex.TexCoord)); 119 | 120 | mesh.Draw(); 121 | 122 | solidColorPipeline.Unbind(); 123 | } 124 | } 125 | 126 | // Cube 1 127 | { 128 | Matrix4X4 m = model * Matrix4X4.CreateTranslation(new Vector3D(-10.0f, 4.0f, -16.0f)); 129 | 130 | foreach (Mesh mesh in cubeMeshes) 131 | { 132 | simplePipeline.Bind(); 133 | 134 | simplePipeline.SetUniform(string.Empty, new UniTransforms() 135 | { 136 | Model = m, 137 | View = camera.View, 138 | Projection = camera.Projection, 139 | ObjectToWorld = m, 140 | ObjectToClip = m * camera.View * camera.Projection, 141 | WorldToObject = m.Invert() 142 | }); 143 | 144 | simplePipeline.SetUniform(string.Empty, new UniParameters() 145 | { 146 | Color = Vector4D.One 147 | }); 148 | 149 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_Position"), 3, nameof(Vertex.Position)); 150 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_Normal"), 3, nameof(Vertex.Normal)); 151 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_Tangent"), 3, nameof(Vertex.Tangent)); 152 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_Bitangent"), 3, nameof(Vertex.Bitangent)); 153 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_Color"), 4, nameof(Vertex.Color)); 154 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_TexCoord"), 2, nameof(Vertex.TexCoord)); 155 | 156 | mesh.Draw(); 157 | 158 | simplePipeline.Unbind(); 159 | } 160 | } 161 | 162 | // Cube 2 163 | { 164 | Matrix4X4 m = model * Matrix4X4.CreateTranslation(new Vector3D(8.0f, 0.0f, -10.0f)); 165 | 166 | foreach (Mesh mesh in cubeMeshes) 167 | { 168 | simplePipeline.Bind(); 169 | 170 | simplePipeline.SetUniform(string.Empty, new UniTransforms() 171 | { 172 | Model = m, 173 | View = camera.View, 174 | Projection = camera.Projection, 175 | ObjectToWorld = m, 176 | ObjectToClip = m * camera.View * camera.Projection, 177 | WorldToObject = m.Invert() 178 | }); 179 | 180 | simplePipeline.SetUniform(string.Empty, new UniParameters() 181 | { 182 | Color = Vector4D.One 183 | }); 184 | 185 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_Position"), 3, nameof(Vertex.Position)); 186 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_Normal"), 3, nameof(Vertex.Normal)); 187 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_Tangent"), 3, nameof(Vertex.Tangent)); 188 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_Bitangent"), 3, nameof(Vertex.Bitangent)); 189 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_Color"), 4, nameof(Vertex.Color)); 190 | mesh.VertexAttributePointer((uint)simplePipeline.GetAttribLocation("In_TexCoord"), 2, nameof(Vertex.TexCoord)); 191 | 192 | mesh.Draw(); 193 | 194 | simplePipeline.Unbind(); 195 | } 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /GraphicsHostApp/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace GraphicsHostApp.ViewModels; 2 | 3 | public partial class MainViewModel : ViewModelBase 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /GraphicsHostApp/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace GraphicsHostApp.ViewModels; 4 | 5 | public class ViewModelBase : ObservableObject 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /GraphicsHostApp/Views/MainView.axaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |