├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ ├── cmake.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CMakeSettings.json ├── Folder.DotSettings ├── LICENSE ├── Poltergeist.Core.Tests ├── AssertUtils.cs ├── CoTaskMemoryAllocatorTests.cs ├── EnumUtilsTests.cs ├── EnumerableUtilsTests.cs ├── NativeArrayPoolTests.cs ├── NativeArrayTests.cs ├── NativeListTests.cs ├── NativeStructTests.cs ├── PointerUtilsTests.cs ├── Poltergeist.Core.Tests.csproj └── StringExtensionsTests.cs ├── Poltergeist.Core ├── Collections │ └── EnumerableUtils.cs ├── ExitHandler.cs ├── Extensions │ └── StringExtensions.cs ├── IO │ └── BetterConsole.cs ├── Math │ ├── EnumUtils.cs │ ├── MathUtils.cs │ ├── Vector2.cs │ ├── Vector2Int.cs │ ├── Vector3.cs │ ├── Vector3Int.cs │ ├── Vector4.cs │ └── Vector4Int.cs ├── Memory │ ├── CoTaskMemAllocator.cs │ ├── INativeAllocator.cs │ ├── NativeArray.cs │ ├── NativeArrayPool.cs │ ├── NativeList.cs │ ├── NativeStruct.cs │ └── PointerUtils.cs └── Poltergeist.Core.csproj ├── Poltergeist.GameKit.sln ├── PoltergeistEditor ├── CMakeLists.txt ├── include │ ├── .gitkeep │ └── ImGUIContent.hpp └── src │ ├── ImGUIContent.cpp │ └── PoltergeistEditor.cpp ├── PoltergeistEngine ├── CMakeLists.txt ├── include │ ├── .gitkeep │ └── PoltergeistEngine │ │ ├── Encoding │ │ └── EncodingUtilities.hpp │ │ ├── IO │ │ └── FileUtilities.hpp │ │ ├── Image │ │ └── Image.hpp │ │ ├── Macros.hpp │ │ └── Rendering │ │ ├── FrameBuffer.hpp │ │ ├── IndexBuffer.hpp │ │ ├── OpenGlUtilities.hpp │ │ ├── Renderer.hpp │ │ ├── Shader.hpp │ │ ├── ShaderStage.hpp │ │ ├── ShaderStageType.hpp │ │ ├── Texture.hpp │ │ ├── Vertex.hpp │ │ ├── VertexArray.hpp │ │ ├── VertexBuffer.hpp │ │ ├── VertexBufferLayout.hpp │ │ └── VertexBufferLayoutElement.hpp ├── resources │ ├── texture.png │ └── white.png ├── shaders │ ├── core.frag │ └── core.vert └── src │ └── PoltergeistEngine │ ├── Encoding │ └── EncodingUtilities.cpp │ ├── IO │ └── FileUtilities.cpp │ ├── Image │ └── Image.cpp │ └── Rendering │ ├── FrameBuffer.cpp │ ├── IndexBuffer.cpp │ ├── Renderer.cpp │ ├── Shader.cpp │ ├── ShaderStage.cpp │ ├── Texture.cpp │ ├── VertexArray.cpp │ └── VertexBuffer.cpp ├── PoltergeistSandbox ├── CMakeLists.txt ├── include │ └── .gitkeep └── src │ └── PoltergeistSandbox.cpp ├── README.md ├── nuget.config └── vcpkg.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{cs,csx,vb,vbx,cpp,hpp,c,h}] 4 | indent_style = tab 5 | charset = utf-8-bom 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | max_line_length = 120 9 | 10 | [CMakeLists.txt] 11 | indent_style = tab 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | max_line_length = 120 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | reviewers: 9 | - KernelErr0r 10 | assignees: 11 | - KernelErr0r 12 | - package-ecosystem: github-actions 13 | directory: "/" 14 | schedule: 15 | interval: daily 16 | open-pull-requests-limit: 10 17 | reviewers: 18 | - KernelErr0r 19 | assignees: 20 | - KernelErr0r 21 | - package-ecosystem: gitsubmodule 22 | directory: "/" 23 | schedule: 24 | interval: weekly 25 | open-pull-requests-limit: 10 26 | reviewers: 27 | - KernelErr0r 28 | assignees: 29 | - KernelErr0r 30 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build GameKit 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Poltergeist GameKit build (${{matrix.runtime}} ${{matrix.configuration}}) 8 | runs-on: ${{matrix.os}} 9 | 10 | strategy: 11 | matrix: 12 | os: [windows-latest] 13 | configuration: ['debug', 'release'] 14 | include: 15 | - os: windows-latest 16 | runtime: 'win-x64' 17 | 18 | env: 19 | DOTNET_NOLOGO: true 20 | DOTNET_CLI_TELEMETRY_OPTOUT: true 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Setup .NET 25 | uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: '5.0' 28 | 29 | - name: Restore dependencies 30 | run: dotnet restore -r ${{matrix.runtime}} 31 | 32 | - name: Build ${{matrix.project}} 33 | run: dotnet publish -c ${{matrix.configuration}} -r ${{matrix.runtime}} --no-restore 34 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Poltergeist CMake 8 | runs-on: ${{matrix.os}} 9 | 10 | env: 11 | buildDir: '${{github.workspace}}/out' 12 | llvmLocation: '${{github.workspace}}/llvm' 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [windows-latest,ubuntu-20.04] 18 | compiler: ['msvc','clangcl','gcc','clang'] 19 | generator: ['Visual Studio 16 2019', 'MSYS Makefiles', 'Unix Makefiles'] 20 | configuration: ['Debug', 'Release'] 21 | exclude: 22 | - os: windows-latest 23 | generator: 'Unix Makefiles' 24 | - os: windows-latest 25 | compiler: 'gcc' 26 | - os: windows-latest 27 | compiler: 'clang' 28 | - os: ubuntu-20.04 29 | compiler: 'clangcl' 30 | - os: ubuntu-20.04 31 | compiler: 'msvc' 32 | - os: ubuntu-20.04 33 | generator: 'Visual Studio 16 2019' 34 | - os: ubuntu-20.04 35 | generator: 'MSYS Makefiles' 36 | - compiler: 'msvc' 37 | generator: 'MSYS Makefiles' 38 | - compiler: 'clangcl' 39 | generator: 'MSYS Makefiles' 40 | - compiler: 'gcc' 41 | generator: 'Visual Studio 16 2019' 42 | - compiler: 'clang' 43 | generator: 'Visual Studio 16 2019' 44 | include: 45 | - compiler: 'clangcl' 46 | toolkit: ' -T ClangCL' 47 | # - generator: 'Visual Studio 16 2019' 48 | # cmakewrapper: '' 49 | # - generator: 'MSYS Makefiles' 50 | # cmakewrapper: 'msys2 -c' 51 | # - generator: 'Visual Studio 16 2019' 52 | # cmakewrapper: '' 53 | 54 | steps: 55 | - uses: actions/checkout@v2.3.4 56 | with: 57 | submodules: 'recursive' 58 | 59 | - if: matrix.os == 'ubuntu-20.04' 60 | name: Install dependencies 61 | run: | 62 | sudo apt update 63 | sudo apt install build-essential gcc-10 g++-10 clang llvm cmake libxinerama-dev libxcursor-dev xorg-dev libglu1-mesa-dev libtinfo6 64 | 65 | - if: matrix.generator == 'MSYS Makefiles' 66 | name: Setup MSYS2 67 | uses: msys2/setup-msys2@v2 68 | 69 | - if: matrix.compiler == 'msvc' 70 | name: Setup MSBuild 71 | uses: microsoft/setup-msbuild@v1 72 | 73 | - if: matrix.compiler == 'gcc' && matrix.os != 'windows-latest' 74 | shell: 'bash' 75 | name: Set up GCC 76 | run: | 77 | echo "CC=gcc-10" >> $GITHUB_ENV 78 | echo "CXX=g++-10" >> $GITHUB_ENV 79 | 80 | # - if: matrix.compiler == 'gcc' && matrix.os == 'windows-latest' 81 | # shell: 'msys2 {0}' 82 | # name: Set up GCC 83 | # run: | 84 | # echo "CC=gcc" >> $GITHUB_ENV 85 | # echo "CXX=g++" >> $GITHUB_ENV 86 | 87 | - if: matrix.compiler == 'clang' && matrix.os != 'windows-latest' 88 | name: Cache LLVM and Clang 89 | id: cache-llvm 90 | uses: actions/cache@v2 91 | with: 92 | path: '${{env.llvmLocation}}' 93 | key: llvm-11 94 | 95 | - if: matrix.compiler == 'clang' && matrix.os != 'windows-latest' 96 | name: Install LLVM and Clang 97 | uses: KyleMayes/install-llvm-action@v1 98 | with: 99 | version: "11" 100 | directory: '${{env.llvmLocation}}' 101 | cached: ${{ steps.cache-llvm.outputs.cache-hit }} 102 | 103 | - if: matrix.compiler == 'clang' && matrix.os != 'windows-latest' 104 | shell: 'bash' 105 | name: Enable CLANG 106 | run: | 107 | echo "CC=${{env.llvmLocation}}/bin/clang" >> $GITHUB_ENV 108 | echo "CXX=${{env.llvmLocation}}/bin/clang++" >> $GITHUB_ENV 109 | 110 | # - if: matrix.compiler == 'clang' && matrix.os == 'windows-latest' 111 | # shell: 'msys2 {0}' 112 | # name: Enable CLANG 113 | # run: | 114 | # echo "CC=clang" >> $GITHUB_ENV 115 | # echo "CXX=clang++" >> $GITHUB_ENV 116 | 117 | - name: Get latest CMake and ninja 118 | uses: lukka/get-cmake@latest 119 | 120 | - name: Run vcpkg 121 | uses: lukka/run-vcpkg@v7.4 122 | with: 123 | setupOnly: true 124 | doNotUpdateVcpkg: true 125 | vcpkgDirectory: '${{github.workspace}}/vcpkg' 126 | appendedCacheKey: ${{matrix.os}}-${{matrix.generator}}-${{matrix.compiler}}-${{matrix.toolkit}}-${{matrix.configuration}}-${{hashFiles('vcpkg.json')}} 127 | additionalCachedPaths: '${{env.buildDir}}/vcpkg_installed' 128 | 129 | - name: Build 130 | uses: lukka/run-cmake@v3.4 131 | with: 132 | # cmakeWrapperCommand: '${{matrix.cmakewrapper}}' 133 | cmakeListsOrSettingsJson: CMakeListsTxtAdvanced 134 | useVcpkgToolchainFile: true 135 | buildWithCMake: true 136 | buildWithCMakeArgs: '--config ${{matrix.configuration}}' 137 | buildDirectory: ${{env.buildDir}} 138 | cmakeListsTxtPath: '${{github.workspace}}/CMakeLists.txt' 139 | cmakeAppendedArgs: '-G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE=${{matrix.configuration}}${{matrix.toolkit}}' 140 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Poltergeist test (${{matrix.runtime}} ${{matrix.configuration}}) 8 | runs-on: ${{matrix.os}} 9 | 10 | strategy: 11 | matrix: 12 | os: [windows-latest] 13 | configuration: ['debug', 'release'] 14 | include: 15 | - os: windows-latest 16 | runtime: 'win-x64' 17 | 18 | env: 19 | DOTNET_NOLOGO: true 20 | DOTNET_CLI_TELEMETRY_OPTOUT: true 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Setup .NET 25 | uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: '5.0' 28 | 29 | - name: Restore dependencies 30 | run: dotnet restore -r ${{matrix.runtime}} 31 | 32 | - name: Test 33 | run: dotnet test -c ${{matrix.configuration}} -r ${{matrix.runtime}} 34 | -------------------------------------------------------------------------------- /.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 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | # Rider 353 | .idea/ 354 | 355 | # CMake 356 | cmake-build-debug/ 357 | cmake-build-release/ 358 | cmake-build-debug-visual-studio/ 359 | cmake-build-release-visual-studio/ 360 | 361 | out/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vcpkg"] 2 | path = vcpkg 3 | url = https://github.com/microsoft/vcpkg.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | cmake_policy(SET CMP0092 NEW) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_C_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_C_STANDARD_REQUIRED ON) 8 | 9 | set(VCPKG_MANIFEST_MODE ON) 10 | set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file") 11 | 12 | if (NOT CMAKE_BUILD_TYPE) 13 | set(CMAKE_BUILD_TYPE Debug) 14 | endif () 15 | 16 | # Force configuration to the specified one 17 | get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 18 | if(isMultiConfig) 19 | set(CMAKE_CONFIGURATION_TYPES "${CMAKE_BUILD_TYPE}" CACHE STRING "" FORCE) 20 | endif () 21 | 22 | enable_language(C) 23 | enable_language(CXX) 24 | 25 | # Warning settings 26 | if (MSVC) 27 | message("Enabling MSVC warnings") 28 | add_compile_options(/W4) 29 | else () 30 | message("Enabling non-MSVC warnings") 31 | add_compile_options(-Wall -Wextra -pedantic) 32 | endif () 33 | 34 | # MSVC settings 35 | if (MSVC) 36 | message("Enabling MSVC UTF-8") 37 | add_compile_options(/utf-8) 38 | endif () 39 | 40 | # Windows settings 41 | if (WIN32) 42 | message("Enabling Windows configuration") 43 | add_definitions(-DUNICODE -D_UNICODE) 44 | add_definitions(-DWIN32_LEAN_AND_MEAN) 45 | add_definitions(-DNTDDI_VERSION=NTDDI_WIN7 -DWINVER=_WIN32_WINNT_WIN7 -D_WIN32_WINNT=_WIN32_WINNT_WIN7) 46 | endif () 47 | 48 | # Optimization settings 49 | if (MSVC) 50 | message("Enabling MSVC optimizations") 51 | SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /O2 /Oi /Ot /Oy /GL") 52 | SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2 /Oi /Ot /Oy /GL") 53 | if (CMAKE_SIZEOF_VOID_P LESS 8) 54 | message("Enabling MSVC non 64bit optimizations") 55 | SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /arch:SSE2") 56 | SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:SSE2") 57 | endif () 58 | SET(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") 59 | else () 60 | message("Enabling non-MSVC optimizations") 61 | SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2 -mfpmath=sse -mmmx -msse -msse2") 62 | SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -mfpmath=sse -mmmx -msse -msse2") 63 | endif () 64 | 65 | project(Poltergeist) 66 | 67 | add_subdirectory("PoltergeistEngine") 68 | add_subdirectory("PoltergeistEditor") 69 | add_subdirectory("PoltergeistSandbox") 70 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\out\\build\\${name}", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "", 12 | "ctestCommandArgs": "" 13 | }, 14 | { 15 | "name": "x64-Release", 16 | "generator": "Ninja", 17 | "configurationType": "Release", 18 | "inheritEnvironments": [ "msvc_x64_x64" ], 19 | "buildRoot": "${projectDir}\\out\\build\\${name}", 20 | "installRoot": "${projectDir}\\out\\install\\${name}", 21 | "cmakeCommandArgs": "", 22 | "buildCommandArgs": "", 23 | "ctestCommandArgs": "" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /Folder.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | ExplicitlyExcluded 3 | ExplicitlyExcluded -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/AssertUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | 6 | namespace Poltergeist.Core.Tests 7 | { 8 | public static class AssertUtils 9 | { 10 | public static void SequenceEqual(IEnumerable expected, IEnumerable actual) 11 | { 12 | if (expected is IReadOnlyList list1 && actual is IReadOnlyList list2) 13 | Assert.Equal(list1.Count, list2.Count); 14 | // ReSharper disable PossibleMultipleEnumeration 15 | Assert.Equal(expected.Count(), actual.Count()); 16 | Assert.True(expected.SequenceEqual(actual)); 17 | // ReSharper restore PossibleMultipleEnumeration 18 | } 19 | 20 | public static void SequenceEqual(ReadOnlySpan expected, ReadOnlySpan actual) where T : IEquatable 21 | { 22 | Assert.Equal(expected.Length, actual.Length); 23 | Assert.True(expected.SequenceEqual(actual)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/CoTaskMemoryAllocatorTests.cs: -------------------------------------------------------------------------------- 1 | using Poltergeist.Core.Memory; 2 | using System; 3 | using Xunit; 4 | 5 | namespace Poltergeist.Core.Tests 6 | { 7 | public class CoTaskMemoryAllocatorTests 8 | { 9 | [Theory] 10 | [InlineData(0)] 11 | [InlineData(1)] 12 | [InlineData(16)] 13 | [InlineData(1024)] 14 | public unsafe void AllocationTest(int size) 15 | { 16 | CoTaskMemAllocator allocator = new(); 17 | IntPtr data = allocator.Allocate(size); 18 | try 19 | { 20 | Assert.NotEqual(IntPtr.Zero, data); 21 | byte* bytes = (byte*)data.ToPointer(); 22 | // check if writing to memory doesn't crash 23 | for (int i = 0; i < size; i++) 24 | { 25 | bytes[i] = byte.MaxValue; 26 | } 27 | // check if reading memory yields correct values 28 | for (int i = 0; i < size; i++) 29 | { 30 | Assert.Equal(byte.MaxValue, bytes[i]); 31 | } 32 | } 33 | finally 34 | { 35 | allocator.Free(data); 36 | } 37 | } 38 | 39 | [Theory] 40 | [InlineData(-1)] 41 | [InlineData(int.MinValue)] 42 | public void NegativeSizeTest(int size) 43 | { 44 | Assert.Throws(() => { new CoTaskMemAllocator().Allocate(size); }); 45 | } 46 | 47 | [Theory] 48 | [InlineData(0)] 49 | [InlineData(-1)] 50 | [InlineData(1)] 51 | [InlineData(int.MinValue)] 52 | [InlineData(int.MaxValue)] 53 | public void InvalidFreeTest(int data) 54 | { 55 | Assert.Throws(() => { new CoTaskMemAllocator().Free(new IntPtr(data)); }); 56 | } 57 | 58 | [Theory] 59 | [InlineData(0)] 60 | [InlineData(1)] 61 | [InlineData(16)] 62 | [InlineData(1024)] 63 | public void DoubleFreeTest(int size) 64 | { 65 | CoTaskMemAllocator allocator = new(); 66 | IntPtr data = allocator.Allocate(size); 67 | allocator.Free(data); 68 | Assert.Throws(() => { allocator.Free(data); }); 69 | } 70 | 71 | [Theory] 72 | [InlineData(0)] 73 | [InlineData(1)] 74 | [InlineData(16)] 75 | [InlineData(1024)] 76 | public void WrongAllocatorFreeTest(int size) 77 | { 78 | CoTaskMemAllocator allocator = new(); 79 | IntPtr data = allocator.Allocate(size); 80 | try 81 | { 82 | Assert.Throws(() => { new CoTaskMemAllocator().Free(data); }); 83 | } 84 | finally 85 | { 86 | allocator.Free(data); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/EnumUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using Poltergeist.Core.Math; 2 | using System; 3 | using System.Linq; 4 | using Xunit; 5 | 6 | namespace Poltergeist.Core.Tests 7 | { 8 | public static class EnumUtilsTests 9 | { 10 | // ReSharper disable UnusedMember.Local 11 | private enum TestEnum 12 | { 13 | A = 1, 14 | B = 2, 15 | C = 4, 16 | D = A, 17 | E = B, 18 | F = 3 19 | } 20 | 21 | [Flags] 22 | public enum TestFlags 23 | { 24 | A = 1 << 0, 25 | B = 1 << 1, 26 | C = 1 << 2, 27 | D = 1 << 3, 28 | E = 1 << 4, 29 | F = 1 << 5 30 | } 31 | // ReSharper restore UnusedMember.Local 32 | 33 | [Fact] 34 | public static void ValuesTest() 35 | { 36 | Assert.True(EnumUtils.Values.ToArray().SequenceEqual(Enum.GetValues())); 37 | } 38 | 39 | [Fact] 40 | public static void NamesTest() 41 | { 42 | Assert.True(EnumUtils.Names.ToArray().SequenceEqual(Enum.GetNames())); 43 | } 44 | 45 | [Fact] 46 | public static void TypeTest() 47 | { 48 | Assert.Equal(Enum.GetUnderlyingType(typeof(TestEnum)), EnumUtils.UnderlyingType); 49 | } 50 | 51 | [Theory] 52 | [InlineData((TestFlags)0, TestFlags.C)] 53 | [InlineData(TestFlags.A, TestFlags.C)] 54 | [InlineData(TestFlags.C, TestFlags.C)] 55 | [InlineData(TestFlags.A | TestFlags.B, TestFlags.C)] 56 | [InlineData(TestFlags.A | TestFlags.C, TestFlags.C)] 57 | public static void HasFlagTest(TestFlags value, TestFlags flag) 58 | { 59 | // ReSharper disable HeapView.BoxingAllocation 60 | Assert.Equal(value.HasFlag(flag), EnumUtils.HasFlag(value, flag)); 61 | Assert.Equal(value.HasFlag(flag), value.HasFlagF(flag)); 62 | // ReSharper restore HeapView.BoxingAllocation 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/EnumerableUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using Poltergeist.Core.Collections; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | namespace Poltergeist.Core.Tests 6 | { 7 | public class EnumerableUtilsTests 8 | { 9 | [Fact] 10 | public void JoinTest() 11 | { 12 | Assert.True(EnumerableUtils.Join(new[] { 1, 2, 3 }, new[] { 4, 5 }, new[] { 6, 7, 8 }, new[] { 9 }).SequenceEqual(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })); 13 | Assert.True(EnumerableUtils.Join(new[] { "a", "b", "c" }, new[] { "d", "e" }, new[] { "f", "g", "h" }, new[] { "i" }).SequenceEqual(new[] { "a", "b", "c", "d", "e", "f", "g", "h", "i" })); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/NativeArrayPoolTests.cs: -------------------------------------------------------------------------------- 1 | using Poltergeist.Core.Memory; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | 7 | namespace Poltergeist.Core.Tests 8 | { 9 | public class NativeArrayPoolTests 10 | { 11 | [Theory] 12 | [InlineData(0)] 13 | [InlineData(1)] 14 | [InlineData(64)] 15 | [InlineData(1024)] 16 | public void RentTest(int size) 17 | { 18 | NativeArray nativeArray = NativeArrayPool.Rent(size); 19 | try 20 | { 21 | Assert.NotNull(nativeArray); 22 | Assert.True(nativeArray.Count >= size); 23 | } 24 | finally 25 | { 26 | NativeArrayPool.Return(nativeArray); 27 | } 28 | } 29 | 30 | [Theory] 31 | [InlineData(0, 10)] 32 | [InlineData(1, 10)] 33 | [InlineData(64, 10)] 34 | [InlineData(1024, 10)] 35 | public void RentMultipleTest(int size, int count) 36 | { 37 | NativeArray[] nativeArrays = new NativeArray[count]; 38 | for (int i = 0; i < count; i++) 39 | nativeArrays[i] = NativeArrayPool.Rent(size); 40 | 41 | try 42 | { 43 | foreach (NativeArray nativeArray in nativeArrays) 44 | { 45 | Assert.NotNull(nativeArray); 46 | Assert.True(nativeArray.Count >= size); 47 | } 48 | } 49 | finally 50 | { 51 | foreach (NativeArray nativeArray in nativeArrays) 52 | NativeArrayPool.Return(nativeArray); 53 | } 54 | } 55 | 56 | [Theory] 57 | [InlineData(0)] 58 | [InlineData(1)] 59 | [InlineData(64)] 60 | [InlineData(1024)] 61 | public void ParallelRentTest(int size) 62 | { 63 | Parallel.For(0, Environment.ProcessorCount * 2, (_, _) => 64 | { 65 | NativeArray nativeArray = NativeArrayPool.Rent(size); 66 | try 67 | { 68 | Assert.NotNull(nativeArray); 69 | Assert.True(nativeArray.Count >= size); 70 | } 71 | finally 72 | { 73 | NativeArrayPool.Return(nativeArray); 74 | } 75 | }); 76 | } 77 | 78 | [Theory] 79 | [InlineData(5)] 80 | [InlineData(10)] 81 | public unsafe void ZeroCacheTest(int count) 82 | { 83 | NativeArray[] nativeArrays = new NativeArray[count]; 84 | for (int i = 0; i < count; i++) 85 | nativeArrays[i] = NativeArrayPool.Rent(0); 86 | 87 | try 88 | { 89 | foreach (NativeArray nativeArray in nativeArrays) 90 | { 91 | Assert.NotNull(nativeArray); 92 | Assert.Equal(0, nativeArray.Count); 93 | Assert.True(nativeArrays[0].Data == nativeArray.Data); 94 | Assert.Equal(nativeArrays[0], nativeArray); 95 | } 96 | } 97 | finally 98 | { 99 | foreach (NativeArray nativeArray in nativeArrays) 100 | NativeArrayPool.Return(nativeArray); 101 | } 102 | } 103 | 104 | [Theory] 105 | [InlineData(1, 10)] 106 | [InlineData(64, 10)] 107 | [InlineData(1024, 10)] 108 | public unsafe void UniqueTest(int size, int count) 109 | { 110 | NativeArray[] nativeArrays = new NativeArray[count]; 111 | for (int i = 0; i < count; i++) 112 | nativeArrays[i] = NativeArrayPool.Rent(size); 113 | 114 | HashSet> arrays = new(); 115 | HashSet datas = new(); 116 | 117 | try 118 | { 119 | foreach (NativeArray nativeArray in nativeArrays) 120 | { 121 | Assert.True(arrays.Add(nativeArray)); 122 | Assert.True(datas.Add(new IntPtr(nativeArray.Data))); 123 | } 124 | } 125 | finally 126 | { 127 | foreach (NativeArray nativeArray in nativeArrays) 128 | NativeArrayPool.Return(nativeArray); 129 | } 130 | } 131 | 132 | [Theory] 133 | [InlineData(-1)] 134 | [InlineData(int.MinValue)] 135 | public void NegativeTest(int size) 136 | { 137 | Assert.Throws(() => NativeArrayPool.Rent(size)); 138 | } 139 | 140 | [Fact] 141 | public void ReturnNullTest() 142 | { 143 | Assert.Throws(() => NativeArrayPool.Return(null)); 144 | } 145 | 146 | [Theory] 147 | [InlineData(0)] 148 | [InlineData(1)] 149 | [InlineData(10)] 150 | public void ReturnNotFromPoolTest(int size) 151 | { 152 | NativeArray array = new(size); 153 | try 154 | { 155 | Assert.Throws(() => NativeArrayPool.Return(array)); 156 | } 157 | finally 158 | { 159 | array.Dispose(); 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/NativeArrayTests.cs: -------------------------------------------------------------------------------- 1 | using Poltergeist.Core.Memory; 2 | using System; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using Xunit; 6 | 7 | namespace Poltergeist.Core.Tests 8 | { 9 | public unsafe class NativeArrayTests 10 | { 11 | [Theory] 12 | [InlineData(0)] 13 | [InlineData(1)] 14 | [InlineData(127)] 15 | [InlineData(1024)] 16 | public void CreateTest(int size) 17 | { 18 | using (NativeArray nativeArray = new(size)) 19 | { 20 | Assert.True(nativeArray.Data != null); 21 | Assert.NotNull(nativeArray.Allocator); 22 | Assert.Equal(size, nativeArray.Count); 23 | Assert.Equal(size * sizeof(int), nativeArray.ByteSize); 24 | Assert.True(nativeArray.AlignedSize >= nativeArray.ByteSize); 25 | // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local 26 | Assert.True(nativeArray.All(v => v == 0)); 27 | } 28 | } 29 | 30 | [Theory] 31 | [InlineData(0)] 32 | [InlineData(1)] 33 | [InlineData(127)] 34 | [InlineData(1024)] 35 | public void CreateWithDataTest(int size) 36 | { 37 | int[] array = new int[size]; 38 | new Random().NextBytes(MemoryMarshal.AsBytes(array.AsSpan())); 39 | using (NativeArray nativeArray = new(array)) 40 | { 41 | Assert.True(nativeArray.Data != null); 42 | Assert.NotNull(nativeArray.Allocator); 43 | Assert.Equal(array.Length, nativeArray.Count); 44 | Assert.Equal(array.Length * sizeof(int), nativeArray.ByteSize); 45 | Assert.True(nativeArray.AlignedSize >= nativeArray.ByteSize); 46 | AssertUtils.SequenceEqual(array, nativeArray); 47 | } 48 | } 49 | 50 | [Theory] 51 | [InlineData(-1)] 52 | [InlineData(int.MinValue)] 53 | [InlineData(int.MaxValue)] 54 | public void InvalidLengthTest(int length) 55 | { 56 | Assert.Throws(() => 57 | { 58 | using (NativeArray _ = new(length)) { } 59 | }); 60 | } 61 | 62 | [Fact] 63 | public void SpanTest() 64 | { 65 | using (NativeArray nativeArray = new(1024)) 66 | { 67 | Assert.Equal(nativeArray.Count, nativeArray.AsSpan().Length); 68 | Assert.Equal(nativeArray.Count, nativeArray.AsReadOnlySpan().Length); 69 | AssertUtils.SequenceEqual(nativeArray.AsSpan(), new ReadOnlySpan(nativeArray.Data, nativeArray.Count)); 70 | AssertUtils.SequenceEqual(nativeArray.AsReadOnlySpan(), new ReadOnlySpan(nativeArray.Data, nativeArray.Count)); 71 | } 72 | } 73 | 74 | [Theory] 75 | [InlineData(0)] 76 | [InlineData(-1)] 77 | [InlineData(int.MinValue)] 78 | [InlineData(int.MaxValue)] 79 | public void FillTest(int value) 80 | { 81 | using (NativeArray nativeArray = new(1024, false)) 82 | { 83 | nativeArray.Fill(value); 84 | // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local 85 | Assert.True(nativeArray.All(v => v == value)); 86 | } 87 | } 88 | 89 | [Fact] 90 | public void ClearTest() 91 | { 92 | using (NativeArray nativeArray = new(1024, false)) 93 | { 94 | nativeArray.Fill(uint.MaxValue); 95 | nativeArray.Clear(); 96 | // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local 97 | Assert.True(nativeArray.All(v => v == 0)); 98 | } 99 | } 100 | 101 | [Theory] 102 | [InlineData(0)] 103 | [InlineData(1)] 104 | [InlineData(128)] 105 | public void EnumerableTest(int length) 106 | { 107 | using (NativeArray nativeArray = new(length)) 108 | { 109 | nativeArray.Fill(uint.MaxValue); 110 | // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local 111 | Assert.True(nativeArray.All(v => v == uint.MaxValue)); 112 | #pragma warning disable CA1826 113 | Assert.Equal(nativeArray.Count, nativeArray.Count()); 114 | #pragma warning restore CA1826 115 | } 116 | } 117 | 118 | [Theory] 119 | [InlineData(0)] 120 | [InlineData(1)] 121 | [InlineData(128)] 122 | public void ToArrayTest(int length) 123 | { 124 | using (NativeArray nativeArray = new(length)) 125 | { 126 | nativeArray.Fill(uint.MaxValue); 127 | AssertUtils.SequenceEqual(nativeArray, nativeArray.ToArray()); 128 | } 129 | } 130 | 131 | [Theory] 132 | [InlineData(0)] 133 | [InlineData(1)] 134 | [InlineData(128)] 135 | public void IterationTest(int length) 136 | { 137 | using (NativeArray nativeArray = new(length)) 138 | { 139 | for (int i = 0; i < nativeArray.Count; i++) 140 | nativeArray[i] = uint.MaxValue; 141 | for (int i = 0; i < nativeArray.Count; i++) 142 | Assert.Equal(uint.MaxValue, nativeArray[i]); 143 | for (uint i = 0; i < nativeArray.Count; i++) 144 | Assert.Equal(uint.MaxValue, nativeArray[i]); 145 | foreach (uint u in nativeArray) 146 | Assert.Equal(uint.MaxValue, u); 147 | } 148 | } 149 | 150 | [Theory] 151 | [InlineData(0)] 152 | [InlineData(1)] 153 | [InlineData(128)] 154 | public void IndexerTest(int length) 155 | { 156 | using (NativeArray nativeArray = new(length)) 157 | { 158 | Assert.Throws(() => { _ = nativeArray[-1]; }); 159 | Assert.Throws(() => { _ = nativeArray[nativeArray.Count]; }); 160 | if (nativeArray.Count != 0) 161 | { 162 | _ = nativeArray[0]; 163 | _ = nativeArray[nativeArray.Count - 1]; 164 | } 165 | Assert.Throws(() => { _ = nativeArray[(uint)nativeArray.Count]; }); 166 | if (nativeArray.Count != 0) 167 | { 168 | _ = nativeArray[0u]; 169 | _ = nativeArray[(uint)(nativeArray.Count - 1)]; 170 | } 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/NativeListTests.cs: -------------------------------------------------------------------------------- 1 | using Poltergeist.Core.Collections; 2 | using Poltergeist.Core.Memory; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Security.Cryptography; 9 | using Xunit; 10 | 11 | // ReSharper disable HeapView.BoxingAllocation 12 | // ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local 13 | // ReSharper disable UseCollectionCountProperty 14 | 15 | namespace Poltergeist.Core.Tests 16 | { 17 | public unsafe class NativeListTests 18 | { 19 | [Fact] 20 | public void CreateTest() 21 | { 22 | using (NativeList nativeList = new()) 23 | { 24 | Assert.NotNull(nativeList.Allocator); 25 | Assert.Equal(NativeList.DefaultAllocator, nativeList.Allocator); 26 | Assert.True(nativeList.Data != null); 27 | Assert.Empty(nativeList); 28 | Assert.True(nativeList.Capacity > 0); 29 | Assert.False(nativeList.IsFixedSize); 30 | Assert.False(nativeList.IsReadOnly); 31 | Assert.False(nativeList.IsSynchronized); 32 | } 33 | } 34 | 35 | [Theory] 36 | [InlineData(0)] 37 | [InlineData(1)] 38 | [InlineData(1024)] 39 | public void CreateDataTest(int size) 40 | { 41 | int[] data = new int[size]; 42 | RandomNumberGenerator.Fill(MemoryMarshal.AsBytes(data.AsSpan())); 43 | using (NativeList nativeList = new(data)) 44 | { 45 | Assert.NotNull(nativeList.Allocator); 46 | Assert.Equal(NativeList.DefaultAllocator, nativeList.Allocator); 47 | Assert.True(nativeList.Data != null); 48 | AssertUtils.SequenceEqual(data, nativeList); 49 | Assert.True(nativeList.Capacity >= data.Length); 50 | Assert.False(nativeList.IsFixedSize); 51 | Assert.False(nativeList.IsReadOnly); 52 | Assert.False(nativeList.IsSynchronized); 53 | } 54 | } 55 | 56 | [Theory] 57 | [InlineData(1, 0)] 58 | [InlineData(128, 1024)] 59 | [InlineData(1024, 128)] 60 | public void EnsureCapacityTest(int initial, int requested) 61 | { 62 | using (NativeList nativeList = new(initial)) 63 | { 64 | Assert.True(nativeList.Capacity >= initial); 65 | nativeList.EnsureCapacity(requested); 66 | Assert.True(nativeList.Capacity >= requested); 67 | } 68 | } 69 | 70 | [Fact] 71 | public void EnsureCapacityDataTest() 72 | { 73 | int[] data = new int[1024]; 74 | RandomNumberGenerator.Fill(MemoryMarshal.AsBytes(data.AsSpan())); 75 | using (NativeList nativeList = new(data)) 76 | { 77 | AssertUtils.SequenceEqual(data, nativeList); 78 | nativeList.EnsureCapacity(nativeList.Count * 3); 79 | AssertUtils.SequenceEqual(data, nativeList); 80 | } 81 | } 82 | 83 | [Theory] 84 | [InlineData(0)] 85 | [InlineData(1)] 86 | [InlineData(128)] 87 | public void IterationTest(int length) 88 | { 89 | uint[] array = new uint[length]; 90 | array.AsSpan().Fill(uint.MaxValue); 91 | using (NativeList nativeList = new(array)) 92 | { 93 | for (int i = 0; i < nativeList.Count; i++) 94 | Assert.Equal(uint.MaxValue, nativeList[i]); 95 | for (uint i = 0; i < nativeList.Count; i++) 96 | Assert.Equal(uint.MaxValue, nativeList[i]); 97 | foreach (uint u in nativeList) 98 | Assert.Equal(uint.MaxValue, u); 99 | } 100 | } 101 | 102 | [Theory] 103 | [InlineData(0)] 104 | [InlineData(1)] 105 | [InlineData(128)] 106 | public void IndexerTest(int length) 107 | { 108 | using (NativeList nativeList = new(new uint[length])) 109 | { 110 | Assert.Throws(() => { _ = nativeList[-1]; }); 111 | Assert.Throws(() => { _ = nativeList[nativeList.Count]; }); 112 | if (nativeList.Count != 0) 113 | { 114 | _ = nativeList[0]; 115 | // ReSharper disable once UseIndexFromEndExpression 116 | _ = nativeList[nativeList.Count - 1]; 117 | _ = nativeList[^1]; 118 | } 119 | Assert.Throws(() => { _ = nativeList[(uint)nativeList.Count]; }); 120 | if (nativeList.Count != 0) 121 | { 122 | _ = nativeList[0u]; 123 | _ = nativeList[(uint)(nativeList.Count - 1)]; 124 | } 125 | } 126 | } 127 | 128 | [Theory] 129 | [InlineData(new int[0], 512)] 130 | [InlineData(new[] { 256 }, 512)] 131 | [InlineData(new[] { 256, 512, 512 }, 512)] 132 | public void AddTest(int[] initial, int value) 133 | { 134 | using (NativeList nativeList = new(initial)) 135 | { 136 | List expected = new(initial); 137 | AssertUtils.SequenceEqual(initial, nativeList); 138 | nativeList.Add(value); 139 | expected.Add(value); 140 | AssertUtils.SequenceEqual(expected, nativeList); 141 | } 142 | using (NativeList nativeList = new(initial)) 143 | { 144 | List expected = new(initial); 145 | AssertUtils.SequenceEqual(initial, nativeList); 146 | nativeList.Add((object)value); 147 | ((IList)expected).Add(value); 148 | AssertUtils.SequenceEqual(expected, nativeList); 149 | } 150 | } 151 | 152 | [Theory] 153 | [InlineData(new int[0], new[] { 512 })] 154 | [InlineData(new[] { 256 }, new[] { 512 })] 155 | [InlineData(new[] { 256, 512, 512 }, new[] { 512 })] 156 | [InlineData(new int[0], new[] { 128, 512 })] 157 | [InlineData(new[] { 256 }, new[] { 128, 512 })] 158 | [InlineData(new[] { 256, 512, 512 }, new[] { 128, 512 })] 159 | [InlineData(new[] { 256, 512, 512, 64, 32, 1024, 16384, 32768, 2, 4, 16 }, new[] { 128, 512, 2048, 4096, 8 })] 160 | public void AddRangeTest(int[] initial, int[] values) 161 | { 162 | using (NativeList nativeList = new(initial)) 163 | { 164 | List expected = new(initial); 165 | AssertUtils.SequenceEqual(initial, nativeList); 166 | nativeList.AddRange(values); 167 | expected.AddRange(values); 168 | AssertUtils.SequenceEqual(expected, nativeList); 169 | } 170 | } 171 | 172 | [Theory] 173 | [InlineData(new int[0], 512, 0)] 174 | [InlineData(new[] { 256 }, 512, 0)] 175 | [InlineData(new[] { 256, 512, 512 }, 512, 0)] 176 | public void InsertTest(int[] initial, int value, int index) 177 | { 178 | using (NativeList nativeList = new(initial)) 179 | { 180 | List expected = new(initial); 181 | AssertUtils.SequenceEqual(initial, nativeList); 182 | nativeList.Insert(index, value); 183 | expected.Insert(index, value); 184 | AssertUtils.SequenceEqual(expected, nativeList); 185 | } 186 | using (NativeList nativeList = new(initial)) 187 | { 188 | List expected = new(initial); 189 | AssertUtils.SequenceEqual(initial, nativeList); 190 | nativeList.Insert(index, (object)value); 191 | ((IList)expected).Insert(index, value); 192 | AssertUtils.SequenceEqual(expected, nativeList); 193 | } 194 | } 195 | 196 | [Theory] 197 | [InlineData(new int[0], new[] { 512 }, 0)] 198 | [InlineData(new[] { 256 }, new[] { 512 }, 0)] 199 | [InlineData(new[] { 256, 512, 512 }, new[] { 512 }, 0)] 200 | [InlineData(new int[0], new[] { 128, 512 }, 0)] 201 | [InlineData(new[] { 256 }, new[] { 128, 512 }, 0)] 202 | [InlineData(new[] { 256, 512, 512 }, new[] { 128, 512 }, 0)] 203 | [InlineData(new[] { 256, 512, 512, 64, 32, 1024, 16384, 32768, 2, 4, 16 }, new[] { 128, 512, 2048, 4096, 8 }, 0)] 204 | public void InsertRangeTest(int[] initial, int[] values, int index) 205 | { 206 | using (NativeList nativeList = new(initial)) 207 | { 208 | List expected = new(initial); 209 | AssertUtils.SequenceEqual(initial, nativeList); 210 | nativeList.InsertRange(index, values); 211 | expected.InsertRange(index, values); 212 | AssertUtils.SequenceEqual(expected, nativeList); 213 | } 214 | } 215 | 216 | [Theory] 217 | [InlineData(new[] { 256 }, 0)] 218 | [InlineData(new[] { 256, 512, 1024 }, 0)] 219 | [InlineData(new[] { 256, 512, 1024 }, 1)] 220 | [InlineData(new[] { 256, 512, 1024 }, 2)] 221 | public void RemoveAtTest(int[] initial, int index) 222 | { 223 | using (NativeList nativeList = new(initial)) 224 | { 225 | List expected = new(initial); 226 | AssertUtils.SequenceEqual(initial, nativeList); 227 | nativeList.RemoveAt(index); 228 | expected.RemoveAt(index); 229 | AssertUtils.SequenceEqual(expected, nativeList); 230 | } 231 | } 232 | 233 | [Theory] 234 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 3, 1)] 235 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 0, 1)] 236 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 3, 2)] 237 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 0, 2)] 238 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 3, 0)] 239 | [InlineData(new[] { 256, 512, 512, 128, 512 }, 0, 0)] 240 | [InlineData(new[] { 256, 512, 512, 64, 32, 1024, 16384, 32768, 2, 4, 16, 128, 512, 2048, 4096, 8 }, 5, 5)] 241 | public void RemoveRangeTest(int[] initial, int index, int count) 242 | { 243 | using (NativeList nativeList = new(initial)) 244 | { 245 | List expected = new(initial); 246 | AssertUtils.SequenceEqual(initial, nativeList); 247 | nativeList.RemoveRange(index, count); 248 | expected.RemoveRange(index, count); 249 | AssertUtils.SequenceEqual(expected, nativeList); 250 | } 251 | } 252 | 253 | [Theory] 254 | [InlineData(new[] { 256 }, 256)] 255 | [InlineData(new[] { 256, 512, 1024 }, 256)] 256 | [InlineData(new[] { 256, 512, 1024 }, 512)] 257 | [InlineData(new[] { 256, 512, 1024 }, 1024)] 258 | public void RemoveTest(int[] initial, int value) 259 | { 260 | using (NativeList nativeList = new(initial)) 261 | { 262 | List expected = new(initial); 263 | AssertUtils.SequenceEqual(initial, nativeList); 264 | nativeList.Remove(value); 265 | expected.Remove(value); 266 | AssertUtils.SequenceEqual(expected, nativeList); 267 | } 268 | using (NativeList nativeList = new(initial)) 269 | { 270 | List expected = new(initial); 271 | AssertUtils.SequenceEqual(initial, nativeList); 272 | nativeList.Remove((object)value); 273 | ((IList)expected).Remove(value); 274 | AssertUtils.SequenceEqual(expected, nativeList); 275 | } 276 | } 277 | 278 | [Theory] 279 | [InlineData(new int[0])] 280 | [InlineData(new[] { 256 })] 281 | [InlineData(new[] { 256, 512 })] 282 | [InlineData(new[] { 256, 512, 1024 })] 283 | public void ClearTest(int[] initial) 284 | { 285 | using (NativeList nativeList = new(initial)) 286 | { 287 | AssertUtils.SequenceEqual(initial, nativeList); 288 | nativeList.Clear(); 289 | AssertUtils.SequenceEqual(Array.Empty(), nativeList); 290 | } 291 | } 292 | 293 | [Theory] 294 | [InlineData(new int[0], 512)] 295 | [InlineData(new[] { 256 }, 256)] 296 | [InlineData(new[] { 256, 512 }, 256)] 297 | [InlineData(new[] { 256, 512 }, 512)] 298 | [InlineData(new[] { 256, 512, 1024 }, 512)] 299 | [InlineData(new[] { 256, 512, 1024 }, 1024)] 300 | [InlineData(new[] { 256, 512, 1024 }, 128)] 301 | [InlineData(new[] { 256, 512, 512, 512, 1024 }, 512)] 302 | [InlineData(new[] { 256, 1024, 512, 1024 }, 1024)] 303 | [InlineData(new[] { 128, 256, 512, 1024, 128 }, 128)] 304 | public void IndefOfContainsTest(int[] initial, int value) 305 | { 306 | using (NativeList nativeList = new(initial)) 307 | { 308 | List expected = new(initial); 309 | AssertUtils.SequenceEqual(initial, nativeList); 310 | Assert.Equal(expected.IndexOf(value), nativeList.IndexOf(value)); 311 | Assert.Equal(((IList)expected).IndexOf(value), nativeList.IndexOf((object)value)); 312 | Assert.Equal(expected.LastIndexOf(value), nativeList.LastIndexOf(value)); 313 | Assert.Equal(expected.Contains(value), nativeList.Contains(value)); 314 | Assert.Equal(((IList)expected).Contains(value), nativeList.Contains((object)value)); 315 | } 316 | } 317 | 318 | [Fact] 319 | public void SpanTest() 320 | { 321 | uint[] array = new uint[1024]; 322 | array.AsSpan().Fill(uint.MaxValue); 323 | using (NativeList nativeList = new(array)) 324 | { 325 | Assert.Equal(nativeList.Count, nativeList.AsSpan().Length); 326 | Assert.Equal(nativeList.Count, nativeList.AsReadOnlySpan().Length); 327 | AssertUtils.SequenceEqual(nativeList.AsSpan(), new ReadOnlySpan(nativeList.Data, nativeList.Count)); 328 | AssertUtils.SequenceEqual(nativeList.AsReadOnlySpan(), new ReadOnlySpan(nativeList.Data, nativeList.Count)); 329 | } 330 | } 331 | 332 | [Theory] 333 | [InlineData(0)] 334 | [InlineData(1)] 335 | [InlineData(128)] 336 | public void ToArrayListTest(int length) 337 | { 338 | uint[] array = new uint[length]; 339 | array.AsSpan().Fill(uint.MaxValue); 340 | using (NativeList nativeList = new(array)) 341 | { 342 | AssertUtils.SequenceEqual(array, nativeList); 343 | AssertUtils.SequenceEqual(nativeList, nativeList.ToArray()); 344 | AssertUtils.SequenceEqual(nativeList, nativeList.ToList()); 345 | } 346 | } 347 | 348 | [Theory] 349 | [InlineData(0)] 350 | [InlineData(1)] 351 | [InlineData(128)] 352 | public void EnumerableTest(int length) 353 | { 354 | uint[] array = new uint[length]; 355 | array.AsSpan().Fill(uint.MaxValue); 356 | using (NativeList nativeList = new(array)) 357 | { 358 | Assert.True(nativeList.All(v => v == uint.MaxValue)); 359 | Assert.Equal(nativeList.Count, nativeList.Count()); 360 | } 361 | } 362 | 363 | [Theory] 364 | [InlineData(0)] 365 | [InlineData(1)] 366 | [InlineData(128)] 367 | public void CopyToTest(int length) 368 | { 369 | uint[] array = new uint[length]; 370 | array.AsSpan().Fill(uint.MaxValue); 371 | using (NativeList nativeList = new(array)) 372 | { 373 | uint[] array1 = new uint[nativeList.Count]; 374 | nativeList.CopyTo(array1); 375 | AssertUtils.SequenceEqual(nativeList, array1); 376 | uint[] array2 = new uint[nativeList.Count]; 377 | nativeList.CopyTo(array2, 0); 378 | AssertUtils.SequenceEqual(nativeList, array2); 379 | Array array3 = new uint[nativeList.Count]; 380 | nativeList.CopyTo(array3, 0); 381 | AssertUtils.SequenceEqual(nativeList, array3.ToArray()); 382 | // ReSharper disable once UseArrayCreationExpression.1 383 | Array array4 = Array.CreateInstance(typeof(uint), nativeList.Count); 384 | nativeList.CopyTo(array4, 0); 385 | AssertUtils.SequenceEqual(nativeList, array4.ToArray()); 386 | Array array5 = Array.CreateInstance(typeof(uint), new[] { nativeList.Count }, new[] { 0 }); 387 | nativeList.CopyTo(array5, 0); 388 | AssertUtils.SequenceEqual(nativeList, array5.ToArray()); 389 | uint[] array6 = new uint[nativeList.Count]; 390 | Assert.True(nativeList.TryCopyTo(array6)); 391 | AssertUtils.SequenceEqual(nativeList, array6); 392 | uint[] array7 = new uint[nativeList.Count / 2]; 393 | Assert.Equal(nativeList.Count <= array7.Length, nativeList.TryCopyTo(array7)); 394 | } 395 | } 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/NativeStructTests.cs: -------------------------------------------------------------------------------- 1 | using Poltergeist.Core.Memory; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | using Xunit; 5 | 6 | namespace Poltergeist.Core.Tests 7 | { 8 | public class NativeStructTests 9 | { 10 | [Theory] 11 | [InlineData(0, null, null, 0)] 12 | [InlineData(int.MaxValue, "abcde", "fghij", int.MaxValue)] 13 | [InlineData(-1, "abc\tde", "fgh\tij", -1)] 14 | public unsafe void DataTest(int integer, string utf8String, string wideString, int ptr) 15 | { 16 | TestStruct testStruct = new() { integer = integer, utf8String = utf8String, wideString = wideString, ptr = new IntPtr(ptr) }; 17 | using (NativeStruct nativeStruct = new(testStruct)) 18 | { 19 | Assert.True(nativeStruct.Data != null); 20 | Assert.NotNull(nativeStruct.Allocator); 21 | Assert.Equal(NativeStruct.DefaultAllocator, nativeStruct.Allocator); 22 | Assert.Equal(Marshal.SizeOf(), nativeStruct.Size); 23 | Assert.Equal(testStruct.integer, *(int*)nativeStruct.Data); 24 | Assert.Equal(testStruct, Marshal.PtrToStructure(new IntPtr(nativeStruct.Data))); 25 | } 26 | } 27 | 28 | [StructLayout(LayoutKind.Sequential)] 29 | private struct TestStruct : IEquatable 30 | { 31 | public int integer; 32 | [MarshalAs(UnmanagedType.LPUTF8Str)] 33 | public string utf8String; 34 | [MarshalAs(UnmanagedType.LPWStr)] 35 | public string wideString; 36 | public IntPtr ptr; 37 | 38 | public bool Equals(TestStruct other) 39 | { 40 | return integer == other.integer && utf8String == other.utf8String && wideString == other.wideString && ptr == other.ptr; 41 | } 42 | 43 | public override bool Equals(object obj) 44 | { 45 | return obj is TestStruct other && Equals(other); 46 | } 47 | 48 | public override int GetHashCode() 49 | { 50 | // ReSharper disable NonReadonlyMemberInGetHashCode 51 | return HashCode.Combine(integer, utf8String, wideString, ptr); 52 | // ReSharper restore NonReadonlyMemberInGetHashCode 53 | } 54 | 55 | public static bool operator ==(TestStruct left, TestStruct right) 56 | { 57 | return left.Equals(right); 58 | } 59 | 60 | public static bool operator !=(TestStruct left, TestStruct right) 61 | { 62 | return !left.Equals(right); 63 | } 64 | 65 | public override string ToString() 66 | { 67 | // ReSharper disable HeapView.BoxingAllocation 68 | return $"({integer}) ({utf8String}) ({wideString}) ({ptr})"; 69 | // ReSharper restore HeapView.BoxingAllocation 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/PointerUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using Poltergeist.Core.Memory; 2 | using System; 3 | using Xunit; 4 | 5 | namespace Poltergeist.Core.Tests 6 | { 7 | public unsafe class PointerUtilsTests 8 | { 9 | [Fact] 10 | public void NullTest() 11 | { 12 | Assert.False(PointerUtils.IsReadable((byte*)null)); 13 | } 14 | 15 | [Fact] 16 | public void NullDoubleTest() 17 | { 18 | Assert.False(PointerUtils.IsReadable((byte**)null)); 19 | } 20 | 21 | [Fact] 22 | public void ValidTest() 23 | { 24 | using (NativeArray nativeArray = new(1)) 25 | Assert.True(PointerUtils.IsReadable(nativeArray.Data)); 26 | } 27 | 28 | [Fact] 29 | public void ValidDoubleTest() 30 | { 31 | using (NativeArray nativeArray = new(1)) 32 | Assert.True(PointerUtils.IsReadable((byte**)nativeArray.Data)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/Poltergeist.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 9 6 | true 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Poltergeist.Core.Tests/StringExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Poltergeist.Core.Extensions; 2 | using System; 3 | using Xunit; 4 | using Xunit.Abstractions; 5 | 6 | namespace Poltergeist.Core.Tests 7 | { 8 | public class StringExtensionsTests 9 | { 10 | [Theory] 11 | [InlineData("", new[] { "" })] 12 | [InlineData("ab", new[] { "ab" })] 13 | [InlineData("a\rb", new[] { "a", "b" })] 14 | [InlineData("a\nb", new[] { "a", "b" })] 15 | [InlineData("a\r\nb", new[] { "a", "b" })] 16 | [InlineData("\r", new[] { "", "" })] 17 | [InlineData("ab\r", new[] { "ab", "" })] 18 | [InlineData("\n", new[] { "", "" })] 19 | [InlineData("ab\n", new[] { "ab", "" })] 20 | [InlineData("\r\n", new[] { "", "" })] 21 | [InlineData("ab\r\n", new[] { "ab", "" })] 22 | [InlineData("a\rb\nc", new[] { "a", "b", "c" })] 23 | [InlineData("a\nb\rc", new[] { "a", "b", "c" })] 24 | [InlineData("a\r\nb\rc\nd", new[] { "a", "b", "c", "d" })] 25 | [InlineData("a\rb\nc\r\n", new[] { "a", "b", "c", "" })] 26 | [InlineData("a\nb\rc\r\n", new[] { "a", "b", "c", "" })] 27 | [InlineData("a\r\nb\rc\nd\r\n", new[] { "a", "b", "c", "d", "" })] 28 | public void SplitTest(string value, string[] expected) 29 | { 30 | int i = 0; 31 | foreach (ReadOnlySpan line in value.AsSpan().SplitLines()) 32 | { 33 | Assert.True(line.Equals(expected[i], StringComparison.Ordinal), $"\"{expected[i]}\" == \"{line.ToString()}\""); 34 | i++; 35 | } 36 | Assert.Equal(expected.Length, i); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Poltergeist.Core/Collections/EnumerableUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Poltergeist.Core.Collections 5 | { 6 | public static class EnumerableUtils 7 | { 8 | public static IEnumerable Join(params IEnumerable[] enumerables) 9 | { 10 | foreach (IEnumerable enumerable in enumerables) 11 | foreach (T element in enumerable) 12 | yield return element; 13 | } 14 | 15 | public static T[] ToArray(this Array array) 16 | { 17 | T[] result = new T[array.Length]; 18 | array.CopyTo(result, 0); 19 | return result; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Poltergeist.Core/ExitHandler.cs: -------------------------------------------------------------------------------- 1 | using Mono.Unix; 2 | using Mono.Unix.Native; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Runtime.InteropServices; 6 | using System.Threading; 7 | 8 | namespace Poltergeist.Core 9 | { 10 | public static class ExitHandler 11 | { 12 | public static event Action Exit; 13 | 14 | private static readonly object ExitLock = new(); 15 | private static readonly HandlerRoutine Routine = OnNativeSignal; 16 | 17 | private static Process _currentProcess; 18 | private static bool _called; 19 | 20 | static ExitHandler() 21 | { 22 | _currentProcess = Process.GetCurrentProcess(); 23 | _currentProcess.EnableRaisingEvents = true; 24 | _currentProcess.Exited += (_, _) => CallExit(); 25 | AppDomain.CurrentDomain.ProcessExit += (_, _) => CallExit(); 26 | AppDomain.CurrentDomain.DomainUnload += (_, _) => CallExit(); 27 | 28 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 29 | { 30 | SetConsoleCtrlHandler(Routine, true); 31 | } 32 | else 33 | { 34 | new Thread(() => 35 | { 36 | UnixSignal.WaitAny(new UnixSignal[]{ 37 | new(Signum.SIGINT), 38 | new(Signum.SIGTERM), 39 | new(Signum.SIGKILL), 40 | new(Signum.SIGUSR1), 41 | new(Signum.SIGUSR2), 42 | new(Signum.SIGHUP) 43 | }, -1); 44 | CallExit(); 45 | }) 46 | { IsBackground = true }.Start(); 47 | } 48 | } 49 | 50 | private static void CallExit() 51 | { 52 | lock (ExitLock) 53 | { 54 | if (_called) 55 | return; 56 | 57 | _called = true; 58 | _currentProcess?.Dispose(); 59 | _currentProcess = null; 60 | Exit?.Invoke(); 61 | } 62 | } 63 | 64 | public static void ExitProcess(int code = 0) 65 | { 66 | lock (ExitLock) 67 | { 68 | CallExit(); 69 | Environment.Exit(code); 70 | } 71 | } 72 | 73 | private static bool OnNativeSignal(CtrlTypes ctrl) 74 | { 75 | CallExit(); 76 | return true; 77 | } 78 | 79 | [DllImport("Kernel32", EntryPoint = "SetConsoleCtrlHandler")] 80 | private static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add); 81 | 82 | [UnmanagedFunctionPointer(CallingConvention.Winapi)] 83 | private delegate bool HandlerRoutine(CtrlTypes ctrlType); 84 | 85 | private enum CtrlTypes 86 | { 87 | // ReSharper disable UnusedMember.Local 88 | CtrlCEvent, 89 | CtrlBreakEvent, 90 | CtrlCloseEvent, 91 | CtrlLogoffEvent, 92 | CtrlShutdownEvent 93 | // ReSharper restore UnusedMember.Local 94 | } 95 | 96 | public sealed class LifetimeHandle : IDisposable 97 | { 98 | public void Dispose() 99 | { 100 | CallExit(); 101 | GC.SuppressFinalize(this); 102 | } 103 | 104 | ~LifetimeHandle() 105 | { 106 | CallExit(); 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Poltergeist.Core/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Poltergeist.Core.Extensions 4 | { 5 | public static class StringExtensions 6 | { 7 | public static LineSplitEnumerator SplitLines(this ReadOnlySpan str) 8 | { 9 | return new(str); 10 | } 11 | 12 | public ref struct LineSplitEnumerator 13 | { 14 | private ReadOnlySpan _data; 15 | private bool _endOnEmpty; 16 | 17 | public ReadOnlySpan Current { get; private set; } 18 | 19 | public LineSplitEnumerator(ReadOnlySpan data) 20 | { 21 | _data = data; 22 | _endOnEmpty = false; 23 | Current = default; 24 | } 25 | 26 | public bool MoveNext() 27 | { 28 | if (_data.Length == 0) 29 | { 30 | if (_endOnEmpty) 31 | return false; 32 | Current = ReadOnlySpan.Empty; 33 | _endOnEmpty = true; 34 | return true; 35 | } 36 | 37 | int index = _data.IndexOfAny('\r', '\n'); 38 | if (index == -1) 39 | { 40 | Current = _data; 41 | _data = ReadOnlySpan.Empty; 42 | _endOnEmpty = true; 43 | return true; 44 | } 45 | 46 | Current = _data.Slice(0, index); 47 | _data = _data.Slice(index + (_data[index] == '\r' && index < _data.Length - 1 && _data[index + 1] == '\n' ? 2 : 1)); 48 | _endOnEmpty = false; 49 | return true; 50 | } 51 | 52 | public LineSplitEnumerator GetEnumerator() => this; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Poltergeist.Core/IO/BetterConsole.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace Poltergeist.Core.IO 8 | { 9 | public static unsafe class BetterConsole 10 | { 11 | private static readonly ConcurrentQueue> Lines = new(); 12 | private static readonly StringBuilder Buffer = new(16384); 13 | private static readonly AutoResetEvent Wait = new(false); 14 | private static readonly Thread WriteThread; 15 | private static readonly IntPtr OutHandle; 16 | 17 | private static bool _exit; 18 | private static bool _paused; 19 | 20 | static BetterConsole() 21 | { 22 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !Console.IsOutputRedirected) 23 | OutHandle = GetStdHandle(-11); 24 | ExitHandler.Exit += () => 25 | { 26 | _exit = true; 27 | if (WriteThread == null || !WriteThread.IsAlive) 28 | return; 29 | Wait.Set(); 30 | WriteThread.Join(); 31 | }; 32 | WriteThread = new Thread(WriteLoop) 33 | { 34 | IsBackground = true 35 | }; 36 | WriteThread.Start(); 37 | } 38 | 39 | public static void WriteLine(string line) => Lines.Enqueue(line.AsMemory()); 40 | 41 | public static void WriteLine(ReadOnlyMemory line) => Lines.Enqueue(line); 42 | 43 | public static void Flush() => Wait.Set(); 44 | 45 | public static void BeginConsoleUpdate() => _paused = true; 46 | 47 | public static void EndConsoleUpdate() => _paused = false; 48 | 49 | private static void WriteLoop() 50 | { 51 | while (true) 52 | { 53 | Wait.WaitOne(100); 54 | 55 | if (_paused && !_exit) 56 | continue; 57 | 58 | while (Lines.TryDequeue(out ReadOnlyMemory line)) 59 | { 60 | Buffer.Append(line); 61 | Buffer.AppendLine(); 62 | } 63 | 64 | if (Buffer.Length == 0) 65 | { 66 | if (_exit) 67 | return; 68 | continue; 69 | } 70 | 71 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && OutHandle != IntPtr.Zero) 72 | { 73 | foreach (ReadOnlyMemory chunk in Buffer.GetChunks()) 74 | { 75 | ReadOnlySpan span = chunk.Span; 76 | fixed (char* ptr = span) 77 | WriteConsole(OutHandle, ptr, (uint)span.Length, out _); 78 | } 79 | } 80 | else 81 | { 82 | Console.Write(Buffer.ToString()); 83 | } 84 | 85 | Buffer.Clear(); 86 | 87 | if (_exit) 88 | return; 89 | } 90 | } 91 | 92 | [DllImport("Kernel32", EntryPoint = "GetStdHandle", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] 93 | private static extern IntPtr GetStdHandle(int handle); 94 | 95 | [DllImport("Kernel32", EntryPoint = "WriteConsoleW", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)] 96 | private static extern bool WriteConsole(IntPtr handle, char* buffer, uint count, out uint written, 97 | void* reserved = null); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Poltergeist.Core/Math/EnumUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace Poltergeist.Core.Math 5 | { 6 | public static class EnumUtils where T : unmanaged, Enum 7 | { 8 | private static readonly T[] ValuesCache = Enum.GetValues(); 9 | private static readonly string[] NamesCache = Enum.GetNames(); 10 | 11 | public static ReadOnlySpan Values => ValuesCache; 12 | public static ReadOnlySpan Names => NamesCache; 13 | public static Type UnderlyingType { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } = Enum.GetUnderlyingType(typeof(T)); 14 | 15 | #pragma warning disable CA2248 16 | // ReSharper disable once HeapView.PossibleBoxingAllocation 17 | // ReSharper disable once HeapView.BoxingAllocation 18 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 19 | public static bool HasFlag(T value, T flag) => value.HasFlag(flag); 20 | #pragma warning restore CA2248 21 | } 22 | 23 | public static class EnumUtils 24 | { 25 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 26 | public static bool HasFlagF(this T value, T flag) where T : unmanaged, Enum => EnumUtils.HasFlag(value, flag); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Poltergeist.Core/Math/MathUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Poltergeist.Core.Math 4 | { 5 | public static class MathUtils 6 | { 7 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 8 | public static int Align(int value, int alignment) => (value + alignment - 1) / alignment * alignment; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Poltergeist.Core/Math/Vector2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Poltergeist.Core.Math 6 | { 7 | [StructLayout(LayoutKind.Sequential)] 8 | public readonly struct Vector2 : IEquatable, IFormattable 9 | { 10 | private const float Epsilon = 0.001f; 11 | 12 | public static Vector2 Zero => new(0f, 0f); 13 | public static Vector2 One => new(1f, 1f); 14 | public static Vector2 Up => new(0f, 1f); 15 | public static Vector2 Down => new(0f, -1f); 16 | public static Vector2 Left => new(-1f, 0f); 17 | public static Vector2 Right => new(1f, 0f); 18 | public static Vector2 PositiveInfinity => new(float.PositiveInfinity, float.PositiveInfinity); 19 | public static Vector2 NegativeInfinity => new(float.NegativeInfinity, float.NegativeInfinity); 20 | 21 | public readonly float X; 22 | public readonly float Y; 23 | 24 | public Vector2(float x, float y) 25 | { 26 | X = x; 27 | Y = y; 28 | } 29 | 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public static Vector2 operator -(Vector2 a) => new(-a.X, -a.Y); 32 | 33 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 34 | public static Vector2 operator +(Vector2 a, Vector2 b) => new(a.X + b.X, a.Y + b.Y); 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public static Vector2 operator -(Vector2 a, Vector2 b) => new(a.X - b.X, a.Y - b.Y); 38 | 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | public static Vector2 operator *(Vector2 a, Vector2 b) => new(a.X * b.X, a.Y * b.Y); 41 | 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | public static Vector2 operator /(Vector2 a, Vector2 b) => new(a.X / b.X, a.Y / b.Y); 44 | 45 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 46 | public static Vector2 operator *(Vector2 a, float d) => new(a.X * d, a.Y * d); 47 | 48 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 49 | public static Vector2 operator *(float d, Vector2 a) => new(a.X * d, a.Y * d); 50 | 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public static Vector2 operator /(Vector2 a, float d) => new(a.X / d, a.Y / d); 53 | 54 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 55 | public bool Equals(Vector2 other) 56 | { 57 | return MathF.Abs(X - other.X) <= Epsilon && MathF.Abs(Y - other.Y) <= Epsilon; 58 | } 59 | 60 | public override bool Equals(object obj) 61 | { 62 | return obj is Vector2 other && Equals(other); 63 | } 64 | 65 | public override int GetHashCode() 66 | { 67 | return HashCode.Combine(X, Y); 68 | } 69 | 70 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 71 | public static bool operator ==(Vector2 left, Vector2 right) 72 | { 73 | return left.Equals(right); 74 | } 75 | 76 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 77 | public static bool operator !=(Vector2 left, Vector2 right) 78 | { 79 | return !left.Equals(right); 80 | } 81 | 82 | public override string ToString() 83 | { 84 | return $"({X}, {Y})"; 85 | } 86 | 87 | public string ToString(string format, IFormatProvider formatProvider) 88 | { 89 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)})"; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Poltergeist.Core/Math/Vector2Int.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Poltergeist.Core.Math 6 | { 7 | [StructLayout(LayoutKind.Sequential)] 8 | public readonly struct Vector2Int : IEquatable, IFormattable 9 | { 10 | public static Vector2Int Zero => new(0, 0); 11 | public static Vector2Int One => new(1, 1); 12 | public static Vector2Int Up => new(0, 1); 13 | public static Vector2Int Down => new(0, -1); 14 | public static Vector2Int Left => new(-1, 0); 15 | public static Vector2Int Right => new(1, 0); 16 | 17 | public readonly int X; 18 | public readonly int Y; 19 | 20 | public Vector2Int(int x, int y) 21 | { 22 | X = x; 23 | Y = y; 24 | } 25 | 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | public static Vector2Int operator -(Vector2Int a) => new(-a.X, -a.Y); 28 | 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | public static Vector2Int operator +(Vector2Int a, Vector2Int b) => new(a.X + b.X, a.Y + b.Y); 31 | 32 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 | public static Vector2Int operator -(Vector2Int a, Vector2Int b) => new(a.X - b.X, a.Y - b.Y); 34 | 35 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 36 | public static Vector2Int operator *(Vector2Int a, Vector2Int b) => new(a.X * b.X, a.Y * b.Y); 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | public static Vector2Int operator /(Vector2Int a, Vector2Int b) => new(a.X / b.X, a.Y / b.Y); 40 | 41 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 42 | public static Vector2Int operator *(Vector2Int a, int d) => new(a.X * d, a.Y * d); 43 | 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | public static Vector2Int operator *(int d, Vector2Int a) => new(a.X * d, a.Y * d); 46 | 47 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 48 | public static Vector2Int operator /(Vector2Int a, int d) => new(a.X / d, a.Y / d); 49 | 50 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 51 | public bool Equals(Vector2Int other) 52 | { 53 | return X == other.X && Y == other.Y; 54 | } 55 | 56 | public override bool Equals(object obj) 57 | { 58 | return obj is Vector2Int other && Equals(other); 59 | } 60 | 61 | public override int GetHashCode() 62 | { 63 | return HashCode.Combine(X, Y); 64 | } 65 | 66 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 67 | public static bool operator ==(Vector2Int left, Vector2Int right) 68 | { 69 | return left.Equals(right); 70 | } 71 | 72 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 73 | public static bool operator !=(Vector2Int left, Vector2Int right) 74 | { 75 | return !left.Equals(right); 76 | } 77 | 78 | public override string ToString() 79 | { 80 | return $"({X}, {Y})"; 81 | } 82 | 83 | public string ToString(string format, IFormatProvider formatProvider) 84 | { 85 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)})"; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Poltergeist.Core/Math/Vector3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Poltergeist.Core.Math 6 | { 7 | [StructLayout(LayoutKind.Sequential)] 8 | public readonly struct Vector3 : IEquatable, IFormattable 9 | { 10 | private const float Epsilon = 0.001f; 11 | 12 | public static Vector3 Zero => new(0F, 0F, 0F); 13 | public static Vector3 One => new(1F, 1F, 1F); 14 | public static Vector3 Forward => new(0F, 0F, 1F); 15 | public static Vector3 Back => new(0F, 0F, -1F); 16 | public static Vector3 Up => new(0F, 1F, 0F); 17 | public static Vector3 Down => new(0F, -1F, 0F); 18 | public static Vector3 Left => new(-1F, 0F, 0F); 19 | public static Vector3 Right => new(1F, 0F, 0F); 20 | public static Vector3 PositiveInfinity => new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); 21 | public static Vector3 NegativeInfinity => new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); 22 | 23 | public readonly float X; 24 | public readonly float Y; 25 | public readonly float Z; 26 | 27 | public Vector3(float x, float y, float z) 28 | { 29 | X = x; 30 | Y = y; 31 | Z = z; 32 | } 33 | 34 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 35 | public static Vector3 operator -(Vector3 a) => new(-a.X, -a.Y, -a.Z); 36 | 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | public static Vector3 operator +(Vector3 a, Vector3 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z); 39 | 40 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 | public static Vector3 operator -(Vector3 a, Vector3 b) => new(a.X - b.X, a.Y + b.Y, a.Z + b.Z); 42 | 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | public static Vector3 operator *(Vector3 a, Vector3 b) => new(a.X * b.X, a.Y * b.Y, a.Z * b.Z); 45 | 46 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 47 | public static Vector3 operator /(Vector3 a, Vector3 b) => new(a.X / b.X, a.Y / b.Y, a.Z / b.Z); 48 | 49 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 50 | public static Vector3 operator *(Vector3 a, float d) => new(a.X * d, a.Y * d, a.Z * d); 51 | 52 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 53 | public static Vector3 operator *(float d, Vector3 a) => new(a.X * d, a.Y * d, a.Z * d); 54 | 55 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 56 | public static Vector3 operator /(Vector3 a, float d) => new(a.X / d, a.Y / d, a.Z / d); 57 | 58 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 59 | public bool Equals(Vector3 other) 60 | { 61 | return MathF.Abs(X - other.X) <= Epsilon && MathF.Abs(Y - other.Y) <= Epsilon && MathF.Abs(Z - other.Z) <= Epsilon; 62 | } 63 | 64 | public override bool Equals(object obj) 65 | { 66 | return obj is Vector3 other && Equals(other); 67 | } 68 | 69 | public override int GetHashCode() 70 | { 71 | return HashCode.Combine(X, Y, Z); 72 | } 73 | 74 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 75 | public static bool operator ==(Vector3 left, Vector3 right) 76 | { 77 | return left.Equals(right); 78 | } 79 | 80 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 81 | public static bool operator !=(Vector3 left, Vector3 right) 82 | { 83 | return !left.Equals(right); 84 | } 85 | 86 | public override string ToString() 87 | { 88 | return $"({X}, {Y}, {Z})"; 89 | } 90 | 91 | public string ToString(string format, IFormatProvider formatProvider) 92 | { 93 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)}, {Z.ToString(format, formatProvider)})"; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Poltergeist.Core/Math/Vector3Int.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Poltergeist.Core.Math 6 | { 7 | [StructLayout(LayoutKind.Sequential)] 8 | public readonly struct Vector3Int : IEquatable, IFormattable 9 | { 10 | public static Vector3Int Zero => new(0, 0, 0); 11 | public static Vector3Int One => new(1, 1, 1); 12 | public static Vector3Int Up => new(0, 1, 0); 13 | public static Vector3Int Down => new(0, -1, 0); 14 | public static Vector3Int Left => new(-1, 0, 0); 15 | public static Vector3Int Right => new(1, 0, 0); 16 | 17 | public readonly int X; 18 | public readonly int Y; 19 | public readonly int Z; 20 | 21 | public Vector3Int(int x, int y, int z) 22 | { 23 | X = x; 24 | Y = y; 25 | Z = z; 26 | } 27 | 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | public static Vector3Int operator -(Vector3Int a) => new(-a.X, -a.Y, -a.Z); 30 | 31 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 32 | public static Vector3Int operator +(Vector3Int a, Vector3Int b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z); 33 | 34 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 35 | public static Vector3Int operator -(Vector3Int a, Vector3Int b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z); 36 | 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | public static Vector3Int operator *(Vector3Int a, Vector3Int b) => new(a.X * b.X, a.Y * b.Y, a.Z * b.Z); 39 | 40 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 | public static Vector3Int operator /(Vector3Int a, Vector3Int b) => new(a.X / b.X, a.Y / b.Y, a.Z / b.Z); 42 | 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | public static Vector3Int operator *(Vector3Int a, int d) => new(a.X * d, a.Y * d, a.Z * d); 45 | 46 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 47 | public static Vector3Int operator *(int d, Vector3Int a) => new(a.X * d, a.Y * d, a.Z * d); 48 | 49 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 50 | public static Vector3Int operator /(Vector3Int a, int d) => new(a.X / d, a.Y / d, a.Z / d); 51 | 52 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 53 | public bool Equals(Vector3Int other) 54 | { 55 | return X == other.X && Y == other.Y && Z == other.Z; 56 | } 57 | 58 | public override bool Equals(object obj) 59 | { 60 | return obj is Vector3Int other && Equals(other); 61 | } 62 | 63 | public override int GetHashCode() 64 | { 65 | return HashCode.Combine(X, Y, Z); 66 | } 67 | 68 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 69 | public static bool operator ==(Vector3Int left, Vector3Int right) 70 | { 71 | return left.Equals(right); 72 | } 73 | 74 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 75 | public static bool operator !=(Vector3Int left, Vector3Int right) 76 | { 77 | return !left.Equals(right); 78 | } 79 | 80 | public override string ToString() 81 | { 82 | return $"({X}, {Y}, {Z})"; 83 | } 84 | 85 | public string ToString(string format, IFormatProvider formatProvider) 86 | { 87 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)}, {Z.ToString(format, formatProvider)})"; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Poltergeist.Core/Math/Vector4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Poltergeist.Core.Math 6 | { 7 | [StructLayout(LayoutKind.Sequential)] 8 | public readonly struct Vector4 : IEquatable, IFormattable 9 | { 10 | private const float Epsilon = 0.001f; 11 | 12 | public static Vector4 Zero => new(0F, 0F, 0F, 0F); 13 | public static Vector4 One => new(1F, 1F, 1F, 1F); 14 | public static Vector4 PositiveInfinity => new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); 15 | public static Vector4 NegativeInfinity => new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); 16 | 17 | public readonly float X; 18 | public readonly float Y; 19 | public readonly float Z; 20 | public readonly float W; 21 | 22 | public Vector4(float x, float y, float z, float w) 23 | { 24 | X = x; 25 | Y = y; 26 | Z = z; 27 | W = w; 28 | } 29 | 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public static Vector4 operator -(Vector4 a) => new(-a.X, -a.Y, -a.Z, -a.W); 32 | 33 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 34 | public static Vector4 operator +(Vector4 a, Vector4 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W); 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public static Vector4 operator -(Vector4 a, Vector4 b) => new(a.X - b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W); 38 | 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | public static Vector4 operator *(Vector4 a, Vector4 b) => new(a.X * b.X, a.Y * b.Y, a.Z * b.Z, a.W * b.W); 41 | 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | public static Vector4 operator /(Vector4 a, Vector4 b) => new(a.X / b.X, a.Y / b.Y, a.Z / b.Z, a.W / b.W); 44 | 45 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 46 | public static Vector4 operator *(Vector4 a, float d) => new(a.X * d, a.Y * d, a.Z * d, a.W * d); 47 | 48 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 49 | public static Vector4 operator *(float d, Vector4 a) => new(a.X * d, a.Y * d, a.Z * d, a.W * d); 50 | 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public static Vector4 operator /(Vector4 a, float d) => new(a.X / d, a.Y / d, a.Z / d, a.W / d); 53 | 54 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 55 | public bool Equals(Vector4 other) 56 | { 57 | return MathF.Abs(X - other.X) <= Epsilon && MathF.Abs(Y - other.Y) <= Epsilon && MathF.Abs(Z - other.Z) <= Epsilon && MathF.Abs(W - other.W) <= Epsilon; 58 | } 59 | 60 | public override bool Equals(object obj) 61 | { 62 | return obj is Vector4 other && Equals(other); 63 | } 64 | 65 | public override int GetHashCode() 66 | { 67 | return HashCode.Combine(X, Y, Z, W); 68 | } 69 | 70 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 71 | public static bool operator ==(Vector4 left, Vector4 right) 72 | { 73 | return left.Equals(right); 74 | } 75 | 76 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 77 | public static bool operator !=(Vector4 left, Vector4 right) 78 | { 79 | return !left.Equals(right); 80 | } 81 | 82 | public override string ToString() 83 | { 84 | return $"({X}, {Y}, {Z}, {W})"; 85 | } 86 | 87 | public string ToString(string format, IFormatProvider formatProvider) 88 | { 89 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)}, {Z.ToString(format, formatProvider)}, {W.ToString(format, formatProvider)})"; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Poltergeist.Core/Math/Vector4Int.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Poltergeist.Core.Math 6 | { 7 | [StructLayout(LayoutKind.Sequential)] 8 | public readonly struct Vector4Int : IEquatable, IFormattable 9 | { 10 | public static Vector4Int Zero => new(0, 0, 0, 0); 11 | public static Vector4Int One => new(1, 1, 1, 1); 12 | 13 | public readonly int X; 14 | public readonly int Y; 15 | public readonly int Z; 16 | public readonly int W; 17 | 18 | public Vector4Int(int x, int y, int z, int w) 19 | { 20 | X = x; 21 | Y = y; 22 | Z = z; 23 | W = w; 24 | } 25 | 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | public static Vector4Int operator -(Vector4Int a) => new(-a.X, -a.Y, -a.Z, -a.W); 28 | 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | public static Vector4Int operator +(Vector4Int a, Vector4Int b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W); 31 | 32 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 | public static Vector4Int operator -(Vector4Int a, Vector4Int b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z, a.W - b.W); 34 | 35 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 36 | public static Vector4Int operator *(Vector4Int a, Vector4Int b) => new(a.X * b.X, a.Y * b.Y, a.Z * b.Z, a.W * b.W); 37 | 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | public static Vector4Int operator /(Vector4Int a, Vector4Int b) => new(a.X / b.X, a.Y / b.Y, a.Z / b.Z, a.W / b.W); 40 | 41 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 42 | public static Vector4Int operator *(Vector4Int a, int d) => new(a.X * d, a.Y * d, a.Z * d, a.W * d); 43 | 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | public static Vector4Int operator *(int d, Vector4Int a) => new(a.X * d, a.Y * d, a.Z * d, a.W * d); 46 | 47 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 48 | public static Vector4Int operator /(Vector4Int a, int d) => new(a.X / d, a.Y / d, a.Z / d, a.W / d); 49 | 50 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 51 | public bool Equals(Vector4Int other) 52 | { 53 | return X == other.X && Y == other.Y && Z == other.Z && W == other.W; 54 | } 55 | 56 | public override bool Equals(object obj) 57 | { 58 | return obj is Vector4Int other && Equals(other); 59 | } 60 | 61 | public override int GetHashCode() 62 | { 63 | return HashCode.Combine(X, Y, Z, W); 64 | } 65 | 66 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 67 | public static bool operator ==(Vector4Int left, Vector4Int right) 68 | { 69 | return left.Equals(right); 70 | } 71 | 72 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 73 | public static bool operator !=(Vector4Int left, Vector4Int right) 74 | { 75 | return !left.Equals(right); 76 | } 77 | 78 | public override string ToString() 79 | { 80 | return $"({X}, {Y}, {Z}, {W})"; 81 | } 82 | 83 | public string ToString(string format, IFormatProvider formatProvider) 84 | { 85 | return $"({X.ToString(format, formatProvider)}, {Y.ToString(format, formatProvider)}, {Z.ToString(format, formatProvider)}, {W.ToString(format, formatProvider)})"; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Poltergeist.Core/Memory/CoTaskMemAllocator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Poltergeist.Core.Memory 6 | { 7 | public sealed class CoTaskMemAllocator : INativeAllocator 8 | { 9 | private readonly HashSet _allocatedPointers = new(); 10 | private readonly object _lock = new(); 11 | 12 | public IntPtr Allocate(int size) 13 | { 14 | if (size < 0) 15 | throw new ArgumentOutOfRangeException(nameof(size), size, "Allocation size must be positive"); 16 | IntPtr ptr = Marshal.AllocCoTaskMem(size); 17 | if (ptr == IntPtr.Zero) 18 | throw new NullReferenceException(); 19 | lock (_lock) 20 | _allocatedPointers.Add(ptr); 21 | return ptr; 22 | } 23 | 24 | public void Free(IntPtr data) 25 | { 26 | lock (_lock) 27 | if (!_allocatedPointers.Remove(data)) 28 | throw new InvalidOperationException("Tried to doublefree or free invalid data"); 29 | Marshal.FreeCoTaskMem(data); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Poltergeist.Core/Memory/INativeAllocator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Poltergeist.Core.Memory 4 | { 5 | public interface INativeAllocator 6 | { 7 | IntPtr Allocate(int size); 8 | void Free(IntPtr data); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Poltergeist.Core/Memory/NativeArray.cs: -------------------------------------------------------------------------------- 1 | using Poltergeist.Core.Math; 2 | using System; 3 | using System.Buffers; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Diagnostics.Contracts; 8 | using System.Numerics; 9 | using System.Runtime.CompilerServices; 10 | 11 | // ReSharper disable UnusedType.Global 12 | // ReSharper disable MemberCanBePrivate.Global 13 | 14 | namespace Poltergeist.Core.Memory 15 | { 16 | public sealed unsafe class NativeArray : MemoryManager, IReadOnlyList, IDisposable where T : unmanaged 17 | { 18 | // ReSharper disable StaticMemberInGenericType 19 | public static readonly int MaxSize = int.MaxValue / sizeof(T); 20 | public static readonly int DefaultMemoryAlignment = System.Math.Max(System.Math.Max(16, sizeof(T)), MathUtils.Align(Vector.Count, 16)); 21 | public static readonly INativeAllocator DefaultAllocator = new CoTaskMemAllocator(); 22 | // ReSharper restore StaticMemberInGenericType 23 | 24 | public readonly T* Data; 25 | public readonly int Count; 26 | public readonly int ByteSize; 27 | 28 | internal readonly INativeAllocator Allocator; 29 | internal readonly int AlignedSize; 30 | 31 | private readonly object _freeLock = new(); 32 | private readonly bool _zeroOnFree; 33 | 34 | #if DEBUG 35 | private readonly string _allocationStacktrace; 36 | #endif 37 | 38 | private bool _valid; 39 | 40 | int IReadOnlyCollection.Count => Count; 41 | 42 | public NativeArray(ReadOnlySpan data, bool zeroOnFree = false, INativeAllocator allocator = null) 43 | : this(data, DefaultMemoryAlignment, zeroOnFree, allocator) 44 | { 45 | } 46 | 47 | public NativeArray(ReadOnlySpan data, int alignment, bool zeroOnFree = false, INativeAllocator allocator = null) 48 | : this(data.Length, alignment, false, zeroOnFree, allocator) 49 | { 50 | data.CopyTo(AsSpan()); 51 | } 52 | 53 | public NativeArray(int size, bool zeroMemory = true, bool zeroOnFree = false, INativeAllocator allocator = null) 54 | : this(size, DefaultMemoryAlignment, zeroMemory, zeroOnFree, allocator) 55 | { 56 | } 57 | 58 | public NativeArray(int size, int alignment, bool zeroMemory = true, bool zeroOnFree = false, INativeAllocator allocator = null) 59 | { 60 | if (size < 0) 61 | throw new ArgumentOutOfRangeException(nameof(size), size, "Allocation size must be positive"); 62 | if (alignment < sizeof(T)) 63 | throw new ArgumentOutOfRangeException(nameof(alignment), alignment, "Alignment must be at least the element size"); 64 | if (size > MaxSize) 65 | throw new ArgumentOutOfRangeException(nameof(size), size, "Allocation size must be below 2GB"); 66 | Count = size; 67 | ByteSize = size * sizeof(T); 68 | Allocator = allocator ?? DefaultAllocator; 69 | AlignedSize = ByteSize == 0 ? 0 : (int)System.Math.Min((uint)MathUtils.Align(ByteSize, alignment), int.MaxValue); 70 | Data = (T*)Allocator.Allocate(AlignedSize).ToPointer(); 71 | _valid = Data != null; 72 | 73 | if (AlignedSize == 0) 74 | return; 75 | GC.AddMemoryPressure(AlignedSize); 76 | if (zeroMemory) 77 | Clear(); 78 | _zeroOnFree = zeroOnFree; 79 | 80 | #if DEBUG 81 | _allocationStacktrace = Environment.StackTrace; 82 | #endif 83 | } 84 | 85 | public T this[int index] 86 | { 87 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 88 | get 89 | { 90 | if (index >= Count || index < 0) 91 | ThrowHelper.IndexOutOfRange(); 92 | return Data[index]; 93 | } 94 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 95 | set 96 | { 97 | if (index >= Count || index < 0) 98 | ThrowHelper.IndexOutOfRange(); 99 | Data[index] = value; 100 | } 101 | } 102 | 103 | public T this[uint index] 104 | { 105 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 106 | get 107 | { 108 | if (index >= Count) 109 | ThrowHelper.IndexOutOfRange(); 110 | return Data[index]; 111 | } 112 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 113 | set 114 | { 115 | if (index >= Count) 116 | ThrowHelper.IndexOutOfRange(); 117 | Data[index] = value; 118 | } 119 | } 120 | 121 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 122 | public void CopyTo(Span destination) 123 | { 124 | AsReadOnlySpan().CopyTo(destination); 125 | } 126 | 127 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 128 | public bool TryCopyTo(Span destination) 129 | { 130 | return AsReadOnlySpan().TryCopyTo(destination); 131 | } 132 | 133 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 134 | public void Fill(T value) 135 | { 136 | AsSpan().Fill(value); 137 | } 138 | 139 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 140 | public void Clear() 141 | { 142 | AsSpan().Clear(); 143 | } 144 | 145 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 146 | public Span AsSpan() 147 | { 148 | return new(Data, Count); 149 | } 150 | 151 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 152 | public ReadOnlySpan AsReadOnlySpan() 153 | { 154 | return new(Data, Count); 155 | } 156 | 157 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 158 | public T[] ToArray() 159 | { 160 | if (Count == 0) 161 | return Array.Empty(); 162 | 163 | T[] destination = new T[Count]; 164 | CopyTo(destination); 165 | return destination; 166 | } 167 | 168 | #region MemoryOwner 169 | protected override void Dispose(bool disposing) => Dispose(); 170 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 171 | public override Span GetSpan() => AsSpan(); 172 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 173 | public override MemoryHandle Pin(int elementIndex = 0) => new(Data + elementIndex, pinnable: this); 174 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 175 | public override void Unpin() { } 176 | 177 | protected override bool TryGetArray(out ArraySegment segment) 178 | { 179 | segment = default; 180 | return false; 181 | } 182 | #endregion 183 | 184 | #region IEnumerable 185 | public NativeArrayEnumerator GetEnumerator() => new(this); 186 | // ReSharper disable HeapView.BoxingAllocation 187 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 188 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 189 | // ReSharper restore HeapView.BoxingAllocation 190 | #endregion 191 | 192 | #region IDisposeable 193 | private void Free() 194 | { 195 | lock (_freeLock) 196 | { 197 | if (!_valid) 198 | return; 199 | if (_zeroOnFree) 200 | Clear(); 201 | Allocator.Free(new IntPtr(Data)); 202 | if (AlignedSize > 0) 203 | GC.RemoveMemoryPressure(AlignedSize); 204 | _valid = false; 205 | } 206 | } 207 | 208 | public void Dispose() 209 | { 210 | Free(); 211 | GC.SuppressFinalize(this); 212 | } 213 | 214 | #pragma warning disable CA2015 215 | ~NativeArray() 216 | { 217 | #if DEBUG 218 | Debug.WriteLine($"NativeArray leaked to the GC, this might lead to use after free with spans. Allocation stacktrace: {_allocationStacktrace}"); 219 | #endif 220 | Free(); 221 | } 222 | #pragma warning restore CA2015 223 | #endregion 224 | 225 | public struct NativeArrayEnumerator : IEnumerator 226 | { 227 | private readonly NativeArray _array; 228 | private int _currentIndex; 229 | 230 | public T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } 231 | // ReSharper disable once HeapView.BoxingAllocation 232 | object IEnumerator.Current => Current; 233 | 234 | internal NativeArrayEnumerator(NativeArray array) 235 | { 236 | _array = array; 237 | _currentIndex = -1; 238 | Current = default; 239 | } 240 | 241 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 242 | public bool MoveNext() 243 | { 244 | if (++_currentIndex >= _array.Count) 245 | return false; 246 | Current = _array.Data[_currentIndex]; 247 | return true; 248 | } 249 | 250 | public void Reset() => _currentIndex = -1; 251 | public void Dispose() { } 252 | } 253 | 254 | [Pure] 255 | private static class ThrowHelper 256 | { 257 | public static void IndexOutOfRange() => throw new IndexOutOfRangeException(); 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /Poltergeist.Core/Memory/NativeArrayPool.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // Code is a modified version of TlsOverPerCoreLockedStacksArrayPool from .NET 5 4 | 5 | using System; 6 | using System.Numerics; 7 | using System.Runtime.CompilerServices; 8 | using System.Threading; 9 | 10 | namespace Poltergeist.Core.Memory 11 | { 12 | public static class NativeArrayPool where T : unmanaged 13 | { 14 | private const int NumBuckets = 17; 15 | private const int MaxPerCorePerArraySizeStacks = 64; 16 | private const int MaxBuffersPerArraySizePerCore = 8; 17 | 18 | private static readonly NativeArray Empty = new(0, false); 19 | 20 | // ReSharper disable once StaticMemberInGenericType 21 | private static readonly int[] BucketArraySizes; 22 | private static readonly PerCoreLockedStacks[] Buckets = new PerCoreLockedStacks[NumBuckets]; 23 | 24 | [ThreadStatic] 25 | private static NativeArray[] _tTlsBuckets; 26 | 27 | static NativeArrayPool() 28 | { 29 | int[] sizes = new int[NumBuckets]; 30 | for (int i = 0; i < sizes.Length; i++) 31 | sizes[i] = Utilities.GetMaxSizeForBucket(i); 32 | BucketArraySizes = sizes; 33 | } 34 | 35 | private static PerCoreLockedStacks CreatePerCoreLockedStacks(int bucketIndex) 36 | { 37 | PerCoreLockedStacks stacks = new(); 38 | return Interlocked.CompareExchange(ref Buckets[bucketIndex], stacks, null) ?? stacks; 39 | } 40 | 41 | public static NativeArray Rent(int minimumLength) 42 | { 43 | switch (minimumLength) 44 | { 45 | case < 0: 46 | throw new ArgumentOutOfRangeException(nameof(minimumLength)); 47 | case 0: 48 | return Empty; 49 | } 50 | 51 | int bucketIndex = Utilities.SelectBucketIndex(minimumLength); 52 | 53 | if (bucketIndex >= Buckets.Length) 54 | return new NativeArray(minimumLength, false); 55 | 56 | NativeArray[] tlsBuckets = _tTlsBuckets; 57 | if (tlsBuckets != null) 58 | { 59 | NativeArray buffer = tlsBuckets[bucketIndex]; 60 | if (buffer != null) 61 | { 62 | tlsBuckets[bucketIndex] = null; 63 | return buffer; 64 | } 65 | } 66 | 67 | PerCoreLockedStacks bucket = Buckets[bucketIndex]; 68 | if (bucket != null) 69 | { 70 | NativeArray buffer = bucket.TryPop(); 71 | if (buffer != null) 72 | return buffer; 73 | } 74 | 75 | return new NativeArray(BucketArraySizes[bucketIndex], false); 76 | } 77 | 78 | public static void Return(NativeArray nativeArray, bool clearArray = false) 79 | { 80 | if (nativeArray == null) 81 | throw new ArgumentNullException(nameof(nativeArray)); 82 | 83 | if (nativeArray == Empty) 84 | return; 85 | 86 | if (nativeArray.Count == 0) 87 | throw new ArgumentException("Memory not from pool", nameof(nativeArray)); 88 | 89 | int bucketIndex = Utilities.SelectBucketIndex(nativeArray.Count); 90 | 91 | if (bucketIndex >= Buckets.Length) 92 | { 93 | nativeArray.Dispose(); 94 | return; 95 | } 96 | 97 | if (clearArray) 98 | nativeArray.Clear(); 99 | 100 | if (nativeArray.Count != BucketArraySizes[bucketIndex]) 101 | throw new ArgumentException("Memory not from pool", nameof(nativeArray)); 102 | 103 | NativeArray[] tlsBuckets = _tTlsBuckets; 104 | if (tlsBuckets == null) 105 | { 106 | _tTlsBuckets = tlsBuckets = new NativeArray[NumBuckets]; 107 | tlsBuckets[bucketIndex] = nativeArray; 108 | return; 109 | } 110 | 111 | NativeArray prev = tlsBuckets[bucketIndex]; 112 | tlsBuckets[bucketIndex] = nativeArray; 113 | 114 | if (prev == null) 115 | return; 116 | 117 | PerCoreLockedStacks stackBucket = Buckets[bucketIndex] ?? CreatePerCoreLockedStacks(bucketIndex); 118 | stackBucket.TryPush(prev); 119 | } 120 | 121 | private sealed class PerCoreLockedStacks 122 | { 123 | private readonly LockedStack[] _perCoreStacks; 124 | 125 | public PerCoreLockedStacks() 126 | { 127 | LockedStack[] stacks = new LockedStack[System.Math.Min(Environment.ProcessorCount, MaxPerCorePerArraySizeStacks)]; 128 | for (int i = 0; i < stacks.Length; i++) 129 | stacks[i] = new LockedStack(); 130 | _perCoreStacks = stacks; 131 | } 132 | 133 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 134 | public void TryPush(NativeArray array) 135 | { 136 | LockedStack[] stacks = _perCoreStacks; 137 | int index = Thread.GetCurrentProcessorId() % stacks.Length; 138 | for (int i = 0; i < stacks.Length; i++) 139 | { 140 | if (stacks[index].TryPush(array)) 141 | return; 142 | if (++index == stacks.Length) 143 | index = 0; 144 | } 145 | } 146 | 147 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 148 | public NativeArray TryPop() 149 | { 150 | LockedStack[] stacks = _perCoreStacks; 151 | int index = Thread.GetCurrentProcessorId() % stacks.Length; 152 | for (int i = 0; i < stacks.Length; i++) 153 | { 154 | NativeArray arr = stacks[index].TryPop(); 155 | if (arr != null) 156 | return arr; 157 | if (++index == stacks.Length) 158 | index = 0; 159 | } 160 | return null; 161 | } 162 | } 163 | 164 | private sealed class LockedStack 165 | { 166 | private readonly NativeArray[] _arrays = new NativeArray[MaxBuffersPerArraySizePerCore]; 167 | private int _count; 168 | 169 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 170 | public bool TryPush(NativeArray array) 171 | { 172 | bool enqueued = false; 173 | Monitor.Enter(this); 174 | if (_count < MaxBuffersPerArraySizePerCore) 175 | { 176 | _arrays[_count++] = array; 177 | enqueued = true; 178 | } 179 | Monitor.Exit(this); 180 | return enqueued; 181 | } 182 | 183 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 184 | public NativeArray TryPop() 185 | { 186 | NativeArray nativeArray = null; 187 | Monitor.Enter(this); 188 | if (_count > 0) 189 | { 190 | nativeArray = _arrays[--_count]; 191 | _arrays[_count] = null; 192 | } 193 | Monitor.Exit(this); 194 | return nativeArray; 195 | } 196 | } 197 | 198 | private static class Utilities 199 | { 200 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 201 | public static int SelectBucketIndex(int bufferSize) => BitOperations.Log2((uint)bufferSize - 1 | 15) - 3; 202 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 203 | public static int GetMaxSizeForBucket(int binIndex) => 16 << binIndex; 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Poltergeist.Core/Memory/NativeList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Diagnostics.Contracts; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.InteropServices; 8 | 9 | // ReSharper disable UnusedType.Global 10 | // ReSharper disable MemberCanBePrivate.Global 11 | 12 | namespace Poltergeist.Core.Memory 13 | { 14 | public sealed unsafe class NativeList : IList, IReadOnlyList, IList, IDisposable where T : unmanaged, IEquatable 15 | { 16 | private const int DefaultCapacity = 8; 17 | 18 | // ReSharper disable StaticMemberInGenericType 19 | public static readonly int MaxSize = int.MaxValue / sizeof(T); 20 | public static readonly INativeAllocator DefaultAllocator = new CoTaskMemAllocator(); 21 | // ReSharper restore StaticMemberInGenericType 22 | 23 | internal readonly INativeAllocator Allocator; 24 | 25 | private readonly object _freeLock = new(); 26 | private readonly bool _zeroOnFree; 27 | 28 | #if DEBUG 29 | private readonly string _allocationStacktrace; 30 | #endif 31 | 32 | private bool _valid; 33 | private int _version; 34 | 35 | public T* Data { get; private set; } 36 | public int Count { get; private set; } 37 | public int Capacity { get; private set; } 38 | 39 | public bool IsSynchronized => false; 40 | public object SyncRoot => this; 41 | public bool IsReadOnly => false; 42 | public bool IsFixedSize => false; 43 | 44 | public NativeList(ReadOnlySpan data, bool zeroOnFree = false, INativeAllocator allocator = null) 45 | : this(System.Math.Max(data.Length, DefaultCapacity), zeroOnFree, allocator) 46 | { 47 | data.CopyTo(new Span(Data, Capacity)); 48 | Count = data.Length; 49 | } 50 | 51 | public NativeList(int capacity = DefaultCapacity, bool zeroOnFree = false, INativeAllocator allocator = null) 52 | { 53 | if (capacity < 1) 54 | throw new ArgumentOutOfRangeException(nameof(capacity), capacity, "Capacity must be positive"); 55 | if (capacity > MaxSize) 56 | throw new ArgumentOutOfRangeException(nameof(capacity), capacity, "Capacity must be below 2GB"); 57 | Capacity = capacity; 58 | Allocator = allocator ?? DefaultAllocator; 59 | Data = (T*)Allocator.Allocate(capacity * sizeof(T)).ToPointer(); 60 | _valid = Data != null; 61 | 62 | GC.AddMemoryPressure(capacity * sizeof(T)); 63 | _zeroOnFree = zeroOnFree; 64 | 65 | #if DEBUG 66 | _allocationStacktrace = Environment.StackTrace; 67 | #endif 68 | } 69 | 70 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 71 | public void EnsureCapacity(int capacity) 72 | { 73 | if (Capacity >= capacity) 74 | return; 75 | Resize(capacity); 76 | } 77 | 78 | [MethodImpl(MethodImplOptions.NoInlining)] 79 | private void Resize(int capacity) 80 | { 81 | capacity = System.Math.Max(Capacity * 2, capacity); 82 | if (capacity > MaxSize || capacity < 1) 83 | throw new InvalidOperationException(); 84 | int size = capacity * sizeof(T); 85 | T* newData = (T*)Allocator.Allocate(size).ToPointer(); 86 | GC.AddMemoryPressure(size); 87 | CopyTo(new Span(newData, Count)); 88 | if (_zeroOnFree) 89 | AsSpan().Clear(); 90 | Allocator.Free(new IntPtr(Data)); 91 | GC.RemoveMemoryPressure(Capacity * sizeof(T)); 92 | Data = newData; 93 | Capacity = capacity; 94 | } 95 | 96 | object IList.this[int index] 97 | { 98 | // ReSharper disable once HeapView.BoxingAllocation 99 | get => this[index]; 100 | set 101 | { 102 | if (value is not T v) 103 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value)); 104 | this[index] = v; 105 | } 106 | } 107 | 108 | public T this[int index] 109 | { 110 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 111 | get 112 | { 113 | if (index >= Count || index < 0) 114 | ThrowHelper.IndexOutOfRange(); 115 | return Data[index]; 116 | } 117 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 118 | set 119 | { 120 | if (index >= Count || index < 0) 121 | ThrowHelper.IndexOutOfRange(); 122 | Data[index] = value; 123 | } 124 | } 125 | 126 | public T this[uint index] 127 | { 128 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 129 | get 130 | { 131 | if (index >= Count) 132 | ThrowHelper.IndexOutOfRange(); 133 | return Data[index]; 134 | } 135 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 136 | set 137 | { 138 | if (index >= Count) 139 | ThrowHelper.IndexOutOfRange(); 140 | Data[index] = value; 141 | } 142 | } 143 | 144 | public int Add(object value) 145 | { 146 | if (value is not T v) 147 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value)); 148 | Add(v); 149 | return Count - 1; 150 | } 151 | 152 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 153 | public void Add(T item) 154 | { 155 | EnsureCapacity(Count + 1); 156 | Data[Count++] = item; 157 | _version++; 158 | } 159 | 160 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 161 | public void AddRange(ReadOnlySpan items) 162 | { 163 | EnsureCapacity(Count + items.Length); 164 | items.CopyTo(new Span(Data + Count, Capacity - Count)); 165 | Count += items.Length; 166 | _version++; 167 | } 168 | 169 | public void Insert(int index, object value) 170 | { 171 | if (value is not T v) 172 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value)); 173 | Insert(index, v); 174 | } 175 | 176 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 177 | public void Insert(int index, T item) 178 | { 179 | if (index > Count || index < 0) 180 | ThrowHelper.IndexOutOfRange(); 181 | EnsureCapacity(Count + 1); 182 | if (Count != index) 183 | new ReadOnlySpan(Data + index, Count - index).CopyTo(new Span(Data + index + 1, Count - index)); 184 | Data[index] = item; 185 | Count++; 186 | _version++; 187 | } 188 | 189 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 190 | public void InsertRange(int index, ReadOnlySpan items) 191 | { 192 | if (index > Count || index < 0) 193 | ThrowHelper.IndexOutOfRange(); 194 | EnsureCapacity(Count + items.Length); 195 | if (Count != index) 196 | new ReadOnlySpan(Data + index, Count - index).CopyTo(new Span(Data + index + items.Length, Count - index)); 197 | items.CopyTo(new Span(Data + index, Capacity - index)); 198 | Count += items.Length; 199 | _version++; 200 | } 201 | 202 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 203 | public void RemoveAt(int index) 204 | { 205 | if (index >= Count || index < 0) 206 | ThrowHelper.IndexOutOfRange(); 207 | _version++; 208 | if (index == Count - 1) 209 | { 210 | Data[--Count] = default; 211 | return; 212 | } 213 | 214 | new ReadOnlySpan(Data + index + 1, Count - index - 1).CopyTo(new Span(Data + index, Count - index - 1)); 215 | Data[--Count] = default; 216 | } 217 | 218 | public void Remove(object value) 219 | { 220 | if (value is not T v) 221 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value)); 222 | Remove(v); 223 | } 224 | 225 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 226 | public bool Remove(T item) 227 | { 228 | int i = IndexOf(item); 229 | if (i != -1) 230 | { 231 | RemoveAt(i); 232 | return true; 233 | } 234 | 235 | return false; 236 | } 237 | 238 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 239 | public void RemoveRange(int index, int count) 240 | { 241 | if (index >= Count || index < 0) 242 | ThrowHelper.IndexOutOfRange(); 243 | // ReSharper disable HeapView.BoxingAllocation 244 | if (count < 0) 245 | ThrowHelper.ArgumentOutOfRange("Tried to remove a negative count of elements", nameof(count), count); 246 | if (Count - index < count) 247 | ThrowHelper.ArgumentOutOfRange("Tried to remove more elements than the collection contains after the index", nameof(count), count); 248 | // ReSharper restore HeapView.BoxingAllocation 249 | 250 | if (count == 0) 251 | return; 252 | new ReadOnlySpan(Data + index + count, Count - index + 1 - count).CopyTo(new Span(Data + index, Count - index + 1 - count)); 253 | if (_zeroOnFree) 254 | new Span(Data + Count - count, count).Clear(); 255 | Count -= count; 256 | _version++; 257 | } 258 | 259 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 260 | public void Clear() 261 | { 262 | if (_zeroOnFree) 263 | AsSpan().Clear(); 264 | Count = 0; 265 | } 266 | 267 | public int IndexOf(object value) 268 | { 269 | if (value is not T v) 270 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value)); 271 | return IndexOf(v); 272 | } 273 | 274 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 275 | public int IndexOf(T item) 276 | { 277 | return AsReadOnlySpan().IndexOf(item); 278 | } 279 | 280 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 281 | public int LastIndexOf(T item) 282 | { 283 | return AsReadOnlySpan().LastIndexOf(item); 284 | } 285 | 286 | public bool Contains(object value) 287 | { 288 | if (value is not T v) 289 | throw new ArgumentException($"Argument is not of type {typeof(T).Name}", nameof(value)); 290 | return Contains(v); 291 | } 292 | 293 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 294 | public bool Contains(T item) 295 | { 296 | return AsReadOnlySpan().Contains(item); 297 | } 298 | 299 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 300 | public void CopyTo(Array array, int index) 301 | { 302 | if (Count == 0) 303 | return; 304 | if (index >= array.Length || index < 0) 305 | ThrowHelper.IndexOutOfRange(); 306 | if (array is T[] a) 307 | { 308 | CopyTo(new Span(a, index, a.Length - index)); 309 | return; 310 | } 311 | 312 | CopyToSlow(array, index); 313 | } 314 | 315 | [MethodImpl(MethodImplOptions.NoInlining)] 316 | private void CopyToSlow(Array array, int index) 317 | { 318 | if (array.Rank != 1 || array.GetLowerBound(0) != 0) 319 | throw new ArgumentException("Only copies to single dimensional, zero based arrays are permitted", nameof(array)); 320 | GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned); 321 | try 322 | { 323 | CopyTo(new Span((T*)handle.AddrOfPinnedObject() + index, array.Length - index)); 324 | } 325 | finally 326 | { 327 | handle.Free(); 328 | } 329 | } 330 | 331 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 332 | public void CopyTo(T[] array, int index) 333 | { 334 | if (Count == 0) 335 | return; 336 | if (index >= array.Length || index < 0) 337 | ThrowHelper.IndexOutOfRange(); 338 | CopyTo(new Span(array, index, array.Length - index)); 339 | } 340 | 341 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 342 | public void CopyTo(Span destination) 343 | { 344 | AsReadOnlySpan().CopyTo(destination); 345 | } 346 | 347 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 348 | public bool TryCopyTo(Span destination) 349 | { 350 | return AsReadOnlySpan().TryCopyTo(destination); 351 | } 352 | 353 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 354 | public Span AsSpan() 355 | { 356 | return new(Data, Count); 357 | } 358 | 359 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 360 | public ReadOnlySpan AsReadOnlySpan() 361 | { 362 | return new(Data, Count); 363 | } 364 | 365 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 366 | public T[] ToArray() 367 | { 368 | if (Count == 0) 369 | return Array.Empty(); 370 | 371 | T[] destination = new T[Count]; 372 | CopyTo(destination); 373 | return destination; 374 | } 375 | 376 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 377 | public List ToList() 378 | { 379 | return new(this); 380 | } 381 | 382 | // ReSharper disable HeapView.BoxingAllocation 383 | public NativeListEnumerator GetEnumerator() => new(this); 384 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 385 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 386 | // ReSharper restore HeapView.BoxingAllocation 387 | 388 | private void Free() 389 | { 390 | lock (_freeLock) 391 | { 392 | if (!_valid) 393 | return; 394 | if (_zeroOnFree) 395 | AsSpan().Clear(); 396 | Allocator.Free(new IntPtr(Data)); 397 | GC.RemoveMemoryPressure(Capacity * sizeof(T)); 398 | _valid = false; 399 | } 400 | } 401 | 402 | public void Dispose() 403 | { 404 | Free(); 405 | GC.SuppressFinalize(this); 406 | } 407 | 408 | ~NativeList() 409 | { 410 | #if DEBUG 411 | Debug.WriteLine($"NativeList leaked to the GC, this might lead to use after free with spans. Allocation stacktrace: {_allocationStacktrace}"); 412 | #endif 413 | Free(); 414 | } 415 | 416 | public struct NativeListEnumerator : IEnumerator 417 | { 418 | private readonly NativeList _list; 419 | private readonly int _version; 420 | private int _currentIndex; 421 | 422 | public T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } 423 | // ReSharper disable once HeapView.BoxingAllocation 424 | object IEnumerator.Current => Current; 425 | 426 | internal NativeListEnumerator(NativeList list) 427 | { 428 | _list = list; 429 | _currentIndex = -1; 430 | _version = list._version; 431 | Current = default; 432 | } 433 | 434 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 435 | public bool MoveNext() 436 | { 437 | if (_version != _list._version) 438 | ThrowHelper.InvalidOperation(); 439 | if (++_currentIndex >= _list.Count) 440 | return false; 441 | Current = _list.Data[_currentIndex]; 442 | return true; 443 | } 444 | 445 | public void Reset() 446 | { 447 | if (_version != _list._version) 448 | ThrowHelper.InvalidOperation(); 449 | _currentIndex = -1; 450 | } 451 | 452 | public void Dispose() { } 453 | } 454 | 455 | [Pure] 456 | private static class ThrowHelper 457 | { 458 | public static void IndexOutOfRange() => throw new IndexOutOfRangeException(); 459 | public static void InvalidOperation() => throw new InvalidOperationException(); 460 | public static void ArgumentOutOfRange(string message, string name, object value) => throw new ArgumentOutOfRangeException(name, value, message); 461 | } 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /Poltergeist.Core/Memory/NativeStruct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Poltergeist.Core.Memory 5 | { 6 | public sealed unsafe class NativeStruct : IDisposable where T : struct 7 | { 8 | // ReSharper disable once StaticMemberInGenericType 9 | public static readonly INativeAllocator DefaultAllocator = new CoTaskMemAllocator(); 10 | 11 | public readonly void* Data; 12 | public readonly int Size; 13 | 14 | internal readonly INativeAllocator Allocator; 15 | 16 | private readonly bool _zeroOnFree; 17 | private readonly object _disposeLock = new(); 18 | 19 | private bool _valid; 20 | 21 | public NativeStruct(T structure, bool zeroOnFree = false, INativeAllocator allocator = null) 22 | { 23 | Size = Marshal.SizeOf(); 24 | 25 | Allocator = allocator ?? DefaultAllocator; 26 | IntPtr ptr = Allocator.Allocate(Size); 27 | GC.AddMemoryPressure(Size); 28 | 29 | Marshal.StructureToPtr(structure, ptr, false); 30 | Data = ptr.ToPointer(); 31 | 32 | _valid = Data != null; 33 | _zeroOnFree = zeroOnFree; 34 | } 35 | 36 | private void Free() 37 | { 38 | lock (_disposeLock) 39 | { 40 | if (!_valid) 41 | return; 42 | IntPtr ptr = new(Data); 43 | 44 | Marshal.DestroyStructure(ptr); 45 | if (_zeroOnFree) 46 | new Span(Data, Size).Clear(); 47 | Allocator.Free(ptr); 48 | GC.RemoveMemoryPressure(Size); 49 | 50 | _valid = false; 51 | } 52 | } 53 | 54 | public void Dispose() 55 | { 56 | Free(); 57 | GC.SuppressFinalize(this); 58 | } 59 | 60 | ~NativeStruct() 61 | { 62 | Free(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Poltergeist.Core/Memory/PointerUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace Poltergeist.Core.Memory 4 | { 5 | public static unsafe class PointerUtils 6 | { 7 | [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] 8 | public static bool IsReadable(T* ptr) where T : unmanaged 9 | { 10 | try 11 | { 12 | _ = *ptr; 13 | return true; 14 | } 15 | catch 16 | { 17 | return false; 18 | } 19 | } 20 | [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] 21 | public static bool IsReadable(T** ptr) where T : unmanaged 22 | { 23 | try 24 | { 25 | _ = *ptr; 26 | return true; 27 | } 28 | catch 29 | { 30 | return false; 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Poltergeist.Core/Poltergeist.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 9 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <_Parameter1>$(AssemblyName).Tests 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Poltergeist.GameKit.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30907.101 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Poltergeist.Core", "Poltergeist.Core\Poltergeist.Core.csproj", "{796E38FA-5EC0-4BDA-A0D0-D966D7D58348}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Poltergeist.Core.Tests", "Poltergeist.Core.Tests\Poltergeist.Core.Tests.csproj", "{69DA7EB8-C89F-487F-9E00-4EEA1FC17C72}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FACD440C-DD6D-46A7-83AA-7B3D803FEE45}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|x64 = Debug|x64 18 | Release|x64 = Release|x64 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {796E38FA-5EC0-4BDA-A0D0-D966D7D58348}.Debug|x64.ActiveCfg = Debug|Any CPU 22 | {796E38FA-5EC0-4BDA-A0D0-D966D7D58348}.Debug|x64.Build.0 = Debug|Any CPU 23 | {796E38FA-5EC0-4BDA-A0D0-D966D7D58348}.Release|x64.ActiveCfg = Release|Any CPU 24 | {796E38FA-5EC0-4BDA-A0D0-D966D7D58348}.Release|x64.Build.0 = Release|Any CPU 25 | {69DA7EB8-C89F-487F-9E00-4EEA1FC17C72}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {69DA7EB8-C89F-487F-9E00-4EEA1FC17C72}.Debug|x64.Build.0 = Debug|Any CPU 27 | {69DA7EB8-C89F-487F-9E00-4EEA1FC17C72}.Release|x64.ActiveCfg = Release|Any CPU 28 | {69DA7EB8-C89F-487F-9E00-4EEA1FC17C72}.Release|x64.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {EF765CE4-A8E7-44C4-89AF-A52CB2C39625} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /PoltergeistEditor/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(PoltergeistEditor src/PoltergeistEditor.cpp src/ImGUIContent.cpp) 2 | target_include_directories(PoltergeistEditor PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/") 3 | find_package(glfw3 CONFIG REQUIRED) 4 | find_package(glad CONFIG REQUIRED) 5 | find_package(imgui CONFIG REQUIRED) 6 | target_link_libraries(PoltergeistEditor PUBLIC Poltergeist::PoltergeistEngine PRIVATE glfw glad::glad imgui::imgui) 7 | add_executable(Poltergeist::PoltergeistEditor ALIAS PoltergeistEditor) 8 | add_custom_target(copy-shaders-to-editor ALL COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/PoltergeistEngine/shaders ${CMAKE_CURRENT_BINARY_DIR}) 9 | add_custom_target(copy-resources-to-editor ALL COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/PoltergeistEngine/resources ${CMAKE_CURRENT_BINARY_DIR}) 10 | add_dependencies(PoltergeistEditor copy-shaders-to-editor copy-resources-to-editor) 11 | -------------------------------------------------------------------------------- /PoltergeistEditor/include/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProjectPoltergeist/Poltergeist/ecdcadd3d9dfe537e588dc984e290a0dd71a7f6c/PoltergeistEditor/include/.gitkeep -------------------------------------------------------------------------------- /PoltergeistEditor/include/ImGUIContent.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_IMGUICONTENT_HPP 2 | #define POLTERGEIST_IMGUICONTENT_HPP 3 | 4 | void GUIContent(); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /PoltergeistEditor/src/ImGUIContent.cpp: -------------------------------------------------------------------------------- 1 | #include "ImGUIContent.hpp" 2 | #include 3 | 4 | void GUIContent() 5 | { 6 | ImGui::BeginMainMenuBar(); 7 | ImGui::EndMainMenuBar(); 8 | ImGui::ShowDemoWindow(); 9 | } 10 | -------------------------------------------------------------------------------- /PoltergeistEditor/src/PoltergeistEditor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "ImGUIContent.hpp" 9 | #include 10 | #ifdef WIN32 11 | #include 12 | #endif 13 | 14 | extern "C" 15 | { 16 | POLTERGEIST_PUBLIC uint32_t NvOptimusEnablement = 1; 17 | POLTERGEIST_PUBLIC int32_t AmdPowerXpressRequestHighPerformance = 1; 18 | } 19 | 20 | void OnWindowSizeUpdate(GLFWwindow* window, int width, int height) 21 | { 22 | glViewport(0, 0, width, height); 23 | } 24 | 25 | int main() 26 | { 27 | #ifdef WIN32 28 | SetConsoleOutputCP(CP_UTF8); 29 | setvbuf(stdout, nullptr, _IOFBF, 1024); 30 | #endif 31 | 32 | std::cout << "Hello editor!" << std::endl; 33 | 34 | glfwInit(); 35 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 36 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 37 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 38 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); 39 | 40 | { 41 | std::unique_ptr window { 42 | glfwCreateWindow(800, 600, "Poltergeist Editor", nullptr, nullptr), 43 | &glfwDestroyWindow 44 | }; 45 | 46 | if (window.get() == nullptr) 47 | { 48 | glfwTerminate(); 49 | return -1; 50 | } 51 | 52 | glfwMakeContextCurrent(window.get()); 53 | glfwSetWindowSizeCallback(window.get(), OnWindowSizeUpdate); 54 | 55 | if (!gladLoadGLLoader(reinterpret_cast(glfwGetProcAddress))) 56 | { 57 | glfwTerminate(); 58 | return -2; 59 | } 60 | 61 | IMGUI_CHECKVERSION(); 62 | 63 | ImGui::CreateContext(); 64 | ImGui::StyleColorsDark(); 65 | ImGui_ImplGlfw_InitForOpenGL(window.get(), true); 66 | ImGui_ImplOpenGL3_Init(); 67 | 68 | while (!glfwWindowShouldClose(window.get())) 69 | { 70 | glfwPollEvents(); 71 | 72 | ImGui_ImplOpenGL3_NewFrame(); 73 | ImGui_ImplGlfw_NewFrame(); 74 | ImGui::NewFrame(); 75 | 76 | GUIContent(); 77 | ImGui::Render(); 78 | 79 | ImVec4 backgroundColor(1.0f, 1.0f, 1.0f, 1.0f); 80 | glClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, backgroundColor.w); 81 | glClear(GL_COLOR_BUFFER_BIT); 82 | 83 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 84 | 85 | glfwSwapBuffers(window.get()); 86 | } 87 | } 88 | 89 | ImGui_ImplOpenGL3_Shutdown(); 90 | ImGui_ImplGlfw_Shutdown(); 91 | ImGui::DestroyContext(); 92 | 93 | glfwTerminate(); 94 | 95 | return 0; 96 | } 97 | -------------------------------------------------------------------------------- /PoltergeistEngine/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(PoltergeistEngine STATIC src/PoltergeistEngine/Rendering/VertexArray.cpp src/PoltergeistEngine/Rendering/IndexBuffer.cpp src/PoltergeistEngine/Rendering/VertexBuffer.cpp src/PoltergeistEngine/Rendering/Shader.cpp src/PoltergeistEngine/Rendering/ShaderStage.cpp src/PoltergeistEngine/Rendering/Texture.cpp src/PoltergeistEngine/Image/Image.cpp src/PoltergeistEngine/Rendering/Renderer.cpp src/PoltergeistEngine/Encoding/EncodingUtilities.cpp src/PoltergeistEngine/IO/FileUtilities.cpp src/PoltergeistEngine/Rendering/FrameBuffer.cpp) 2 | target_include_directories(PoltergeistEngine PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/") 3 | find_package(glad CONFIG REQUIRED) 4 | find_package(glm CONFIG REQUIRED) 5 | find_package(libpng CONFIG REQUIRED) 6 | target_link_libraries(PoltergeistEngine PRIVATE glad::glad glm::glm png) 7 | add_library(Poltergeist::PoltergeistEngine ALIAS PoltergeistEngine) 8 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProjectPoltergeist/Poltergeist/ecdcadd3d9dfe537e588dc984e290a0dd71a7f6c/PoltergeistEngine/include/.gitkeep -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Encoding/EncodingUtilities.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_ENCODINGUTILITIES_HPP 2 | #define POLTERGEIST_ENCODINGUTILITIES_HPP 3 | 4 | #ifdef WIN32 5 | wchar_t* ConvertUtf8ToUtf16(const char* data); 6 | char* ConvertUtf16ToUtf8(const wchar_t* data); 7 | #endif 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/IO/FileUtilities.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_FILEUTILITIES_HPP 2 | #define POLTERGEIST_FILEUTILITIES_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | FILE* OpenFile(const char* filename, const char* mode); 9 | 10 | [[nodiscard]] static std::optional GetFileContent(const std::filesystem::path& filePath) 11 | { 12 | std::ifstream fileStream(filePath); 13 | 14 | if (fileStream.good()) 15 | { 16 | auto result = std::make_optional(); 17 | 18 | fileStream.seekg(0, std::ios::end); 19 | result->resize(fileStream.tellg()); 20 | fileStream.seekg(0, std::ios::beg); 21 | fileStream.read(result->data(), result->size()); 22 | 23 | return result; 24 | } 25 | 26 | return {}; 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Image/Image.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_IMAGE_HPP 2 | #define POLTERGEIST_IMAGE_HPP 3 | 4 | #include 5 | #include 6 | 7 | class Image 8 | { 9 | private: 10 | uint32_t m_width = 0, m_height = 0; 11 | uint8_t* m_data = nullptr; 12 | public: 13 | explicit Image(const std::filesystem::path &imagePath); 14 | ~Image() noexcept; 15 | 16 | uint32_t GetWidth() const noexcept; 17 | uint32_t GetHeight() const noexcept; 18 | uint8_t* GetData() const noexcept; 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Macros.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_MACROS_HPP 2 | #define POLTERGEIST_MACROS_HPP 3 | 4 | #ifdef POLTERGEIST_ENGINE_EXPORTS 5 | #endif 6 | #if defined _WIN32 || defined WIN32 || defined __CYGWIN__ 7 | #ifdef _MSC_VER 8 | #define POLTERGEIST_PUBLIC __declspec(dllexport) 9 | #else 10 | #define POLTERGEIST_PUBLIC __attribute__ ((dllexport)) 11 | #endif 12 | #define POLTERGEIST_INTERNAL 13 | #else 14 | #if __clang_major__ >= 3 || __GNUC__ >= 4 15 | #define POLTERGEIST_PUBLIC __attribute__ ((visibility ("default"))) 16 | #define POLTERGEIST_INTERNAL __attribute__ ((visibility ("hidden"))) 17 | #else 18 | #define POLTERGEIST_PUBLIC 19 | #define POLTERGEIST_INTERNAL 20 | #endif 21 | #endif 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/FrameBuffer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_FRAMEBUFFER_HPP 2 | #define POLTERGEIST_FRAMEBUFFER_HPP 3 | 4 | #include 5 | #include 6 | #include "PoltergeistEngine/Rendering/Texture.hpp" 7 | 8 | class FrameBuffer 9 | { 10 | private: 11 | uint32_t m_frameBufferId; 12 | std::shared_ptr m_textureAttachment; 13 | uint32_t m_depthAndStencilAttachmentId; 14 | public: 15 | FrameBuffer(uint32_t width, uint32_t height); 16 | ~FrameBuffer() noexcept; 17 | 18 | void Bind() const noexcept; 19 | void Unbind() const noexcept; 20 | 21 | std::shared_ptr GetTextureAttachment() const noexcept; 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/IndexBuffer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_INDEXBUFFER_HPP 2 | #define POLTERGEIST_INDEXBUFFER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class IndexBuffer 9 | { 10 | private: 11 | uint32_t m_indexBufferId; 12 | public: 13 | IndexBuffer(const uint32_t* indices, size_t count) noexcept; 14 | ~IndexBuffer() noexcept; 15 | 16 | void Bind() const noexcept; 17 | void Unbind() const noexcept; 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/OpenGlUtilities.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_OPENGLUTILITIES_HPP 2 | #define POLTERGEIST_OPENGLUTILITIES_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | inline constexpr bool alwaysFalse = false; 11 | 12 | template 13 | constexpr int32_t GetOpenGlType() 14 | { 15 | if constexpr(std::is_same_v) 16 | { 17 | return GL_FLOAT; 18 | } 19 | else 20 | { 21 | static_assert(alwaysFalse, "Incorrect type"); 22 | return 0; 23 | } 24 | } 25 | 26 | static size_t GetOpenGlTypeSize(uint32_t openGlType) 27 | { 28 | switch (openGlType) 29 | { 30 | case GL_FLOAT: return sizeof(float); 31 | default: 32 | assert(false); 33 | return 0; 34 | } 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/Renderer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_RENDERER_HPP 2 | #define POLTERGEIST_RENDERER_HPP 3 | 4 | #include 5 | #include 6 | #include "Shader.hpp" 7 | #include "Texture.hpp" 8 | #include "Vertex.hpp" 9 | #include "FrameBuffer.hpp" 10 | 11 | class Renderer 12 | { 13 | private: 14 | std::shared_ptr m_coreShader; 15 | std::shared_ptr m_whiteTexture; 16 | public: 17 | [[nodiscard]] static std::shared_ptr Create(); 18 | 19 | void BeginRenderPass() const noexcept; 20 | void BeginRenderPass(FrameBuffer& frameBuffer) const noexcept; 21 | 22 | void EndRenderPass() const noexcept; 23 | 24 | void Clear(glm::vec3 color) const noexcept; 25 | void Clear(glm::vec4 color) const noexcept; 26 | 27 | void DrawQuad(glm::vec2 position, float rotation, glm::vec2 scale, glm::vec3 color) const noexcept; 28 | void DrawQuad(glm::vec2 position, float rotation, glm::vec2 scale, glm::vec4 color) const noexcept; 29 | void DrawQuad(glm::vec2 position, float rotation, glm::vec2 scale, std::shared_ptr texture) const noexcept; 30 | 31 | void DrawVertices(std::vector& vertices, std::vector& indices, std::shared_ptr shader, std::shared_ptr texture) const noexcept; 32 | private: 33 | void RotateVertices(std::vector& vertices, float rotation) const noexcept; 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/Shader.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_SHADER_HPP 2 | #define POLTERGEIST_SHADER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "ShaderStage.hpp" 9 | #include "ShaderStageType.hpp" 10 | 11 | class Shader 12 | { 13 | private: 14 | uint32_t m_shaderId; 15 | public: 16 | explicit Shader(uint32_t shaderId) noexcept; 17 | ~Shader() noexcept; 18 | 19 | [[nodiscard]] static std::shared_ptr Create(const std::filesystem::path& vertexShaderFilePath, const std::filesystem::path& fragmentShaderFilePath); 20 | 21 | void Bind() const noexcept; 22 | void Unbind() const noexcept; 23 | 24 | void SetUniform(const std::string& uniformName, int32_t value) const noexcept; 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/ShaderStage.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_SHADERSTAGE_HPP 2 | #define POLTERGEIST_SHADERSTAGE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "PoltergeistEngine/IO/FileUtilities.hpp" 9 | #include "ShaderStageType.hpp" 10 | 11 | class ShaderStage 12 | { 13 | private: 14 | uint32_t m_shaderStageId; 15 | 16 | explicit ShaderStage(uint32_t shaderStageId) noexcept; 17 | public: 18 | ~ShaderStage() noexcept; 19 | 20 | [[nodiscard]] static ShaderStage Create(ShaderStageType shaderStageType, const std::filesystem::path& shaderStageFilePath); 21 | 22 | uint32_t GetId() const noexcept; 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/ShaderStageType.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_SHADERSTAGETYPE_HPP 2 | #define POLTERGEIST_SHADERSTAGETYPE_HPP 3 | 4 | #include 5 | 6 | enum ShaderStageType 7 | { 8 | VertexStage = GL_VERTEX_SHADER, 9 | FragmentStage = GL_FRAGMENT_SHADER 10 | }; 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/Texture.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_TEXTURE_HPP 2 | #define POLTERGEIST_TEXTURE_HPP 3 | 4 | #include 5 | #include 6 | 7 | class Texture 8 | { 9 | private: 10 | uint32_t m_textureId; 11 | uint8_t m_slot; 12 | 13 | Texture(uint32_t textureId, uint8_t slot) noexcept; 14 | public: 15 | ~Texture() noexcept; 16 | 17 | [[nodiscard]] static std::shared_ptr CreateEmpty(uint32_t width, uint32_t height, uint8_t slot); 18 | [[nodiscard]] static std::shared_ptr CreateFromFile(const std::filesystem::path& texturePath, uint8_t slot); 19 | 20 | void Bind() const noexcept; 21 | void Unbind() const noexcept; 22 | 23 | uint32_t GetId() const noexcept; 24 | uint8_t GetSlot() const noexcept; 25 | }; 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/Vertex.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_VERTEX_HPP 2 | #define POLTERGEIST_VERTEX_HPP 3 | 4 | #include 5 | 6 | struct Vertex 7 | { 8 | glm::vec2 m_position; 9 | glm::vec4 m_color; 10 | glm::vec2 m_textureCoordinate; 11 | 12 | Vertex(glm::vec2 position, glm::vec4 color, glm::vec2 textureCoordinate) noexcept 13 | { 14 | m_position = position; 15 | m_color = color; 16 | m_textureCoordinate = textureCoordinate; 17 | } 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/VertexArray.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_VERTEXARRAY_HPP 2 | #define POLTERGEIST_VERTEXARRAY_HPP 3 | 4 | #include 5 | #include 6 | 7 | class VertexArray 8 | { 9 | private: 10 | uint32_t m_vertexArrayId; 11 | public: 12 | VertexArray() noexcept; 13 | ~VertexArray() noexcept; 14 | 15 | void Bind() const noexcept; 16 | void Unbind() const noexcept; 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/VertexBuffer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_VERTEXBUFFER_HPP 2 | #define POLTERGEIST_VERTEXBUFFER_HPP 3 | 4 | #include 5 | #include 6 | #include "VertexBufferLayout.hpp" 7 | #include "VertexBufferLayoutElement.hpp" 8 | 9 | class VertexBuffer 10 | { 11 | private: 12 | uint32_t m_vertexBufferId; 13 | public: 14 | VertexBuffer(const void* vertices, const size_t size, const VertexBufferLayout& layout) noexcept; 15 | ~VertexBuffer() noexcept; 16 | 17 | void Bind() const noexcept; 18 | void Unbind() const noexcept; 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/VertexBufferLayout.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_VERTEXBUFFERLAYOUT_HPP 2 | #define POLTERGEIST_VERTEXBUFFERLAYOUT_HPP 3 | 4 | #include 5 | #include "VertexBufferLayoutElement.hpp" 6 | #include "OpenGlUtilities.hpp" 7 | 8 | class VertexBufferLayout 9 | { 10 | private: 11 | std::vector m_elements; 12 | size_t m_stride = 0; 13 | public: 14 | template 15 | void AddElement(size_t count) noexcept 16 | { 17 | m_elements.emplace_back(GetOpenGlType(), count); 18 | m_stride += sizeof(T) * count; 19 | } 20 | 21 | [[nodiscard]] const std::vector& GetElements() const noexcept 22 | { 23 | return m_elements; 24 | } 25 | 26 | [[nodiscard]] const size_t GetStride() const noexcept 27 | { 28 | return m_stride; 29 | } 30 | }; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /PoltergeistEngine/include/PoltergeistEngine/Rendering/VertexBufferLayoutElement.hpp: -------------------------------------------------------------------------------- 1 | #ifndef POLTERGEIST_VERTEXBUFFERLAYOUTELEMENT_HPP 2 | #define POLTERGEIST_VERTEXBUFFERLAYOUTELEMENT_HPP 3 | 4 | #include 5 | 6 | class VertexBufferLayoutElement 7 | { 8 | private: 9 | int32_t m_type; 10 | size_t m_count; 11 | public: 12 | VertexBufferLayoutElement(int32_t type, size_t count) : m_type(type), m_count(count) {} 13 | 14 | [[nodiscard]] int32_t GetType() const noexcept 15 | { 16 | return m_type; 17 | } 18 | 19 | [[nodiscard]] size_t GetCount() const noexcept 20 | { 21 | return m_count; 22 | } 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /PoltergeistEngine/resources/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProjectPoltergeist/Poltergeist/ecdcadd3d9dfe537e588dc984e290a0dd71a7f6c/PoltergeistEngine/resources/texture.png -------------------------------------------------------------------------------- /PoltergeistEngine/resources/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProjectPoltergeist/Poltergeist/ecdcadd3d9dfe537e588dc984e290a0dd71a7f6c/PoltergeistEngine/resources/white.png -------------------------------------------------------------------------------- /PoltergeistEngine/shaders/core.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout(location = 0) out vec4 color; 4 | 5 | in vec4 v_Color; 6 | in vec2 v_TextureCoordinates; 7 | 8 | uniform sampler2D u_Texture; 9 | 10 | void main() 11 | { 12 | color = v_Color * texture(u_Texture, v_TextureCoordinates); 13 | } 14 | -------------------------------------------------------------------------------- /PoltergeistEngine/shaders/core.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout (location = 0) in vec2 position; 4 | layout (location = 1) in vec4 color; 5 | layout (location = 2) in vec2 texture_coordinates; 6 | 7 | out vec4 v_Color; 8 | out vec2 v_TextureCoordinates; 9 | 10 | void main() 11 | { 12 | gl_Position = vec4(position, 1.0, 1.0); 13 | v_Color = color; 14 | v_TextureCoordinates = texture_coordinates; 15 | } 16 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/Encoding/EncodingUtilities.cpp: -------------------------------------------------------------------------------- 1 | #include "PoltergeistEngine/Encoding/EncodingUtilities.hpp" 2 | #include 3 | #ifdef WIN32 4 | #include 5 | #endif 6 | 7 | #ifdef WIN32 8 | wchar_t* ConvertUtf8ToUtf16(const char* data) 9 | { 10 | const size_t dataLength = strlen(data); 11 | int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, data, dataLength, nullptr, 0); 12 | if (FAILED(length)) 13 | throw std::runtime_error("Encoding conversion error"); 14 | auto* result = new wchar_t[length + 1]; 15 | length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, data, dataLength, result, length); 16 | if (FAILED(length)) 17 | { 18 | delete[] result; 19 | throw std::runtime_error("Encoding conversion error"); 20 | } 21 | result[length] = L'\0'; 22 | return result; 23 | } 24 | 25 | char* ConvertUtf16ToUtf8(const wchar_t* data) 26 | { 27 | const size_t dataLength = wcslen(data); 28 | int length = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, data, dataLength, nullptr, 0, nullptr, nullptr); 29 | if (FAILED(length)) 30 | throw std::runtime_error("Encoding conversion error"); 31 | auto* result = new char[length + 1]; 32 | length = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, data, dataLength, result, length, nullptr, nullptr); 33 | if (FAILED(length)) 34 | { 35 | delete[] result; 36 | throw std::runtime_error("Encoding conversion error"); 37 | } 38 | result[length] = '\0'; 39 | return result; 40 | } 41 | #endif 42 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/IO/FileUtilities.cpp: -------------------------------------------------------------------------------- 1 | #include "PoltergeistEngine/IO/FileUtilities.hpp" 2 | #ifdef WIN32 3 | #include "PoltergeistEngine/Encoding/EncodingUtilities.hpp" 4 | #endif 5 | 6 | FILE* OpenFile(const char* filename, const char* mode) 7 | { 8 | #ifdef WIN32 9 | const auto* wideFileName = ConvertUtf8ToUtf16(filename); 10 | const auto* wideMode = ConvertUtf8ToUtf16(mode); 11 | 12 | std::wstring openMode(wideMode); 13 | openMode += L", ccs=UTF-8"; 14 | 15 | FILE* file = _wfopen(wideFileName, openMode.c_str()); 16 | 17 | delete[] wideFileName; 18 | delete[] wideMode; 19 | 20 | return file; 21 | #else 22 | return fopen(filename, mode); 23 | #endif 24 | } 25 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/Image/Image.cpp: -------------------------------------------------------------------------------- 1 | #include "PoltergeistEngine/Image/Image.hpp" 2 | #include "PoltergeistEngine/IO/FileUtilities.hpp" 3 | #include 4 | #include 5 | 6 | Image::Image(const std::filesystem::path &imagePath) 7 | { 8 | FILE* file = OpenFile(imagePath.generic_string().c_str(), "rb"); 9 | 10 | if (!file) 11 | throw std::runtime_error("Couldn't open the file"); 12 | 13 | uint8_t header[8]; 14 | fread(header, 1, 8, file); 15 | fseek(file, -8, SEEK_CUR); 16 | 17 | if (png_sig_cmp(header, 0, 8) != 0) 18 | throw std::runtime_error("Unsupported format"); 19 | 20 | png_structp internalState = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); 21 | 22 | png_set_user_limits(internalState, 0x7fffffff, 0x7fffffff); 23 | 24 | png_infop imageInfo = png_create_info_struct(internalState); 25 | 26 | png_init_io(internalState, file); 27 | png_read_info(internalState, imageInfo); 28 | png_set_palette_to_rgb(internalState); 29 | 30 | m_width = png_get_image_width(internalState, imageInfo); 31 | m_height = png_get_image_height(internalState, imageInfo); 32 | 33 | png_read_update_info(internalState, imageInfo); 34 | 35 | png_bytepp rows = new png_bytep[m_height]; 36 | 37 | for (size_t row = 0; row < m_height; row++) 38 | { 39 | rows[row] = new png_byte[m_width * 3]; 40 | } 41 | 42 | png_read_image(internalState, rows); 43 | png_read_end(internalState, imageInfo); 44 | 45 | m_data = new uint8_t[m_width * m_height * 3]; 46 | 47 | for (uint32_t y = 0; y < m_height; y++) 48 | { 49 | for (uint32_t x = 0; x < m_width * 3; x++) 50 | { 51 | m_data[y * m_width * 3 + x] = rows[y][x]; 52 | } 53 | } 54 | 55 | delete[] rows; 56 | 57 | png_destroy_read_struct(&internalState, &imageInfo, nullptr); 58 | fclose(file); 59 | } 60 | 61 | Image::~Image() noexcept 62 | { 63 | delete[] m_data; 64 | } 65 | 66 | uint32_t Image::GetWidth() const noexcept 67 | { 68 | return m_width; 69 | } 70 | 71 | uint32_t Image::GetHeight() const noexcept 72 | { 73 | return m_height; 74 | } 75 | 76 | uint8_t* Image::GetData() const noexcept 77 | { 78 | return m_data; 79 | } 80 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/Rendering/FrameBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "PoltergeistEngine/Rendering/FrameBuffer.hpp" 3 | 4 | FrameBuffer::FrameBuffer(uint32_t width, uint32_t height) 5 | { 6 | glGenFramebuffers(1, &m_frameBufferId); 7 | 8 | Bind(); 9 | 10 | m_textureAttachment = Texture::CreateEmpty(width, height, 1); 11 | 12 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_textureAttachment->GetId(), 0); 13 | 14 | glGenRenderbuffers(1, &m_depthAndStencilAttachmentId); 15 | glBindRenderbuffer(GL_RENDERBUFFER, m_depthAndStencilAttachmentId); 16 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); 17 | glBindRenderbuffer(GL_RENDERBUFFER, 0); 18 | 19 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthAndStencilAttachmentId); 20 | 21 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { 22 | throw std::runtime_error("Failed to create a frame buffer"); 23 | } 24 | 25 | Unbind(); 26 | } 27 | 28 | FrameBuffer::~FrameBuffer() noexcept 29 | { 30 | glDeleteFramebuffers(1, &m_frameBufferId); 31 | } 32 | 33 | void FrameBuffer::Bind() const noexcept 34 | { 35 | glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferId); 36 | } 37 | 38 | void FrameBuffer::Unbind() const noexcept 39 | { 40 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 41 | } 42 | 43 | std::shared_ptr FrameBuffer::GetTextureAttachment() const noexcept 44 | { 45 | return m_textureAttachment; 46 | } 47 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/Rendering/IndexBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "PoltergeistEngine/Rendering/IndexBuffer.hpp" 2 | 3 | IndexBuffer::IndexBuffer(const uint32_t* indices, size_t count) noexcept 4 | { 5 | glGenBuffers(1, &m_indexBufferId); 6 | Bind(); 7 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * count, indices, GL_STATIC_DRAW); 8 | Unbind(); 9 | } 10 | 11 | IndexBuffer::~IndexBuffer() noexcept 12 | { 13 | glDeleteBuffers(1, &m_indexBufferId); 14 | } 15 | 16 | void IndexBuffer::Bind() const noexcept 17 | { 18 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBufferId); 19 | } 20 | 21 | void IndexBuffer::Unbind() const noexcept 22 | { 23 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 24 | } 25 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/Rendering/Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "PoltergeistEngine/Rendering/Renderer.hpp" 3 | #include "PoltergeistEngine/Rendering/VertexArray.hpp" 4 | #include "PoltergeistEngine/Rendering/VertexBuffer.hpp" 5 | #include "PoltergeistEngine/Rendering/IndexBuffer.hpp" 6 | 7 | std::shared_ptr Renderer::Create() 8 | { 9 | std::shared_ptr renderer(new Renderer()); 10 | 11 | renderer->m_coreShader = Shader::Create("core.vert", "core.frag"); 12 | renderer->m_whiteTexture = Texture::CreateFromFile("white.png", 0); 13 | 14 | return renderer; 15 | } 16 | 17 | void Renderer::Clear(glm::vec3 color) const noexcept 18 | { 19 | Clear(glm::vec4(color, 1.0)); 20 | } 21 | 22 | void Renderer::Clear(glm::vec4 color) const noexcept 23 | { 24 | glClear(GL_COLOR_BUFFER_BIT); 25 | glClearColor(color.x, color.y, color.z, color.w); 26 | } 27 | 28 | void Renderer::DrawQuad(glm::vec2 position, float rotation, glm::vec2 scale, glm::vec3 color) const noexcept 29 | { 30 | DrawQuad(position, rotation, scale, glm::vec4(color, 1.0)); 31 | } 32 | 33 | void Renderer::DrawQuad(glm::vec2 position, float rotation, glm::vec2 scale, glm::vec4 color) const noexcept 34 | { 35 | std::vector vertices { 36 | Vertex(glm::vec2(position.x + scale.x, position.y + scale.y), color, glm::vec2(1.0f, 0.0f)), 37 | Vertex(glm::vec2(position.x + scale.x, position.y - scale.y), color, glm::vec2(1.0f, 1.0f)), 38 | Vertex(glm::vec2(position.x - scale.x, position.y - scale.y), color, glm::vec2(0.0f, 1.0f)), 39 | Vertex(glm::vec2(position.x - scale.x, position.y + scale.y), color, glm::vec2(0.0f, 0.0f)), 40 | }; 41 | 42 | RotateVertices(vertices, rotation); 43 | 44 | std::vector indices { 45 | 0, 1, 3, 46 | 1, 2, 3 47 | }; 48 | 49 | DrawVertices(vertices, indices, m_coreShader, m_whiteTexture); 50 | } 51 | 52 | void Renderer::DrawQuad(glm::vec2 position, float rotation, glm::vec2 scale, std::shared_ptr texture) const noexcept 53 | { 54 | std::vector vertices { 55 | Vertex(glm::vec2(position.x + scale.x, position.y + scale.y), glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 0.0f)), 56 | Vertex(glm::vec2(position.x + scale.x, position.y - scale.y), glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)), 57 | Vertex(glm::vec2(position.x - scale.x, position.y - scale.y), glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(0.0f, 1.0f)), 58 | Vertex(glm::vec2(position.x - scale.x, position.y + scale.y), glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(0.0f, 0.0f)), 59 | }; 60 | 61 | RotateVertices(vertices, rotation); 62 | 63 | std::vector indices { 64 | 0, 1, 3, 65 | 1, 2, 3 66 | }; 67 | 68 | DrawVertices(vertices, indices, m_coreShader, texture); 69 | } 70 | 71 | void Renderer::DrawVertices(std::vector& vertices, std::vector& indices, std::shared_ptr shader, std::shared_ptr texture) const noexcept 72 | { 73 | m_coreShader->Bind(); 74 | 75 | VertexArray vertexArray; 76 | vertexArray.Bind(); 77 | 78 | VertexBufferLayout layout; 79 | layout.AddElement(2); 80 | layout.AddElement(4); 81 | layout.AddElement(2); 82 | 83 | VertexBuffer vertexBuffer(vertices.data(), vertices.size() * sizeof(Vertex), layout); 84 | 85 | IndexBuffer indexBuffer(indices.data(), indices.size()); 86 | indexBuffer.Bind(); 87 | 88 | texture->Bind(); 89 | shader->SetUniform("u_Texture", texture->GetSlot()); 90 | 91 | glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, nullptr); 92 | 93 | texture->Unbind(); 94 | indexBuffer.Unbind(); 95 | vertexArray.Unbind(); 96 | shader->Unbind(); 97 | } 98 | 99 | void Renderer::RotateVertices(std::vector& vertices, float rotation) const noexcept 100 | { 101 | for (Vertex& vertex : vertices) 102 | { 103 | vertex.m_position = glm::vec2( 104 | vertex.m_position.x * glm::cos(glm::radians(rotation)) + vertex.m_position.y * sin(glm::radians(rotation)), 105 | vertex.m_position.y * glm::cos(glm::radians(rotation)) - vertex.m_position.x * sin(glm::radians(rotation)) 106 | ); 107 | } 108 | } 109 | 110 | void Renderer::BeginRenderPass() const noexcept 111 | { 112 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 113 | 114 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 115 | } 116 | 117 | void Renderer::BeginRenderPass(FrameBuffer& frameBuffer) const noexcept 118 | { 119 | frameBuffer.Bind(); 120 | 121 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 122 | } 123 | 124 | void Renderer::EndRenderPass() const noexcept 125 | { 126 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 127 | } 128 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/Rendering/Shader.cpp: -------------------------------------------------------------------------------- 1 | #include "PoltergeistEngine/Rendering/Shader.hpp" 2 | 3 | Shader::Shader(uint32_t shaderId) noexcept 4 | { 5 | m_shaderId = shaderId; 6 | } 7 | 8 | Shader::~Shader() noexcept 9 | { 10 | glDeleteProgram(m_shaderId); 11 | } 12 | 13 | std::shared_ptr Shader::Create(const std::filesystem::path& vertexShaderFilePath, const std::filesystem::path& fragmentShaderFilePath) 14 | { 15 | uint32_t shaderId = glCreateProgram(); 16 | 17 | { 18 | auto vertexShaderStage = ShaderStage::Create(ShaderStageType::VertexStage, vertexShaderFilePath); 19 | auto fragmentShaderStage = ShaderStage::Create(ShaderStageType::FragmentStage, fragmentShaderFilePath); 20 | 21 | glAttachShader(shaderId, vertexShaderStage.GetId()); 22 | glAttachShader(shaderId, fragmentShaderStage.GetId()); 23 | glLinkProgram(shaderId); 24 | glDetachShader(shaderId, vertexShaderStage.GetId()); 25 | glDetachShader(shaderId, fragmentShaderStage.GetId()); 26 | } 27 | 28 | int32_t success = 0; 29 | glGetProgramiv(shaderId, GL_LINK_STATUS, &success); 30 | 31 | if (!success) 32 | { 33 | constexpr size_t SIZE = 512; 34 | 35 | char info[SIZE]; 36 | glGetProgramInfoLog(shaderId, SIZE, nullptr, info); 37 | 38 | std::cout << "Failed to link the shader: " << info << "\n"; 39 | 40 | throw std::runtime_error("Failed to link the shader"); 41 | } 42 | 43 | return std::shared_ptr(new Shader(shaderId)); 44 | } 45 | 46 | void Shader::Bind() const noexcept 47 | { 48 | glUseProgram(m_shaderId); 49 | } 50 | 51 | void Shader::Unbind() const noexcept 52 | { 53 | glUseProgram(0); 54 | } 55 | 56 | void Shader::SetUniform(const std::string &name, int32_t value) const noexcept 57 | { 58 | int location = glGetUniformLocation(m_shaderId, name.c_str()); 59 | 60 | glUniform1i(location, value); 61 | } 62 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/Rendering/ShaderStage.cpp: -------------------------------------------------------------------------------- 1 | #include "PoltergeistEngine/Rendering/ShaderStage.hpp" 2 | 3 | ShaderStage::ShaderStage(uint32_t shaderStageId) noexcept 4 | { 5 | m_shaderStageId = shaderStageId; 6 | } 7 | 8 | ShaderStage::~ShaderStage() noexcept 9 | { 10 | glDeleteShader(m_shaderStageId); 11 | } 12 | 13 | ShaderStage ShaderStage::Create(ShaderStageType shaderStageType, const std::filesystem::path &shaderStageFilePath) 14 | { 15 | std::optional shaderStageSource = GetFileContent(shaderStageFilePath); 16 | 17 | if (!shaderStageSource) 18 | { 19 | throw std::runtime_error("Failed to read the file"); 20 | } 21 | 22 | const char* shaderStageSourceCString = shaderStageSource->data(); 23 | auto shaderStageSourceSize = static_cast(shaderStageSource->size()); 24 | uint32_t shaderStageId = glCreateShader(static_cast(shaderStageType)); 25 | 26 | glShaderSource(shaderStageId, 1, &shaderStageSourceCString, &shaderStageSourceSize); 27 | glCompileShader(shaderStageId); 28 | 29 | int32_t success = 0; 30 | glGetShaderiv(shaderStageId, GL_COMPILE_STATUS, &success); 31 | 32 | if (!success) 33 | { 34 | constexpr size_t SIZE = 512; 35 | 36 | char infoLog[SIZE]; 37 | glGetShaderInfoLog(shaderStageId, SIZE, nullptr, infoLog); 38 | 39 | std::cout << "Failed to compile the shader stage: " << infoLog << "\n"; 40 | 41 | throw std::runtime_error("Failed to compile the shader stage"); 42 | } 43 | 44 | return ShaderStage(shaderStageId); 45 | } 46 | 47 | uint32_t ShaderStage::GetId() const noexcept 48 | { 49 | return m_shaderStageId; 50 | } 51 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/Rendering/Texture.cpp: -------------------------------------------------------------------------------- 1 | #include "PoltergeistEngine/Rendering/Texture.hpp" 2 | #include "PoltergeistEngine/Image/Image.hpp" 3 | #include 4 | 5 | Texture::Texture(uint32_t textureId, uint8_t slot) noexcept 6 | { 7 | m_textureId = textureId; 8 | m_slot = slot; 9 | } 10 | 11 | Texture::~Texture() noexcept 12 | { 13 | glDeleteTextures(1, &m_textureId); 14 | } 15 | 16 | std::shared_ptr Texture::CreateEmpty(uint32_t width, uint32_t height, uint8_t slot) 17 | { 18 | uint32_t textureId; 19 | glGenTextures(1, &textureId); 20 | 21 | std::shared_ptr texture(new Texture(textureId, slot)); 22 | 23 | glBindTexture(GL_TEXTURE_2D, textureId); 24 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 25 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 26 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 27 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 28 | 29 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); 30 | 31 | texture->Unbind(); 32 | 33 | return texture; 34 | } 35 | 36 | std::shared_ptr Texture::CreateFromFile(const std::filesystem::path &texturePath, uint8_t slot) 37 | { 38 | uint32_t textureId; 39 | glGenTextures(1, &textureId); 40 | 41 | std::shared_ptr texture(new Texture(textureId, slot)); 42 | 43 | glBindTexture(GL_TEXTURE_2D, textureId); 44 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 45 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 46 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 47 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 48 | 49 | Image image(texturePath); 50 | 51 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 52 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.GetWidth(), image.GetHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, image.GetData()); 53 | 54 | texture->Unbind(); 55 | 56 | return texture; 57 | } 58 | 59 | void Texture::Bind() const noexcept 60 | { 61 | glActiveTexture(GL_TEXTURE0 + m_slot); 62 | glBindTexture(GL_TEXTURE_2D, m_textureId); 63 | } 64 | 65 | void Texture::Unbind() const noexcept 66 | { 67 | glBindTexture(GL_TEXTURE_2D, 0); 68 | } 69 | 70 | uint32_t Texture::GetId() const noexcept 71 | { 72 | return m_textureId; 73 | } 74 | 75 | uint8_t Texture::GetSlot() const noexcept 76 | { 77 | return m_slot; 78 | } 79 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/Rendering/VertexArray.cpp: -------------------------------------------------------------------------------- 1 | #include "PoltergeistEngine/Rendering/VertexArray.hpp" 2 | 3 | VertexArray::VertexArray() noexcept 4 | { 5 | glGenVertexArrays(1, &m_vertexArrayId); 6 | } 7 | 8 | VertexArray::~VertexArray() noexcept 9 | { 10 | glDeleteVertexArrays(1, &m_vertexArrayId); 11 | } 12 | 13 | void VertexArray::Bind() const noexcept 14 | { 15 | glBindVertexArray(m_vertexArrayId); 16 | } 17 | 18 | void VertexArray::Unbind() const noexcept 19 | { 20 | glBindVertexArray(0); 21 | } 22 | -------------------------------------------------------------------------------- /PoltergeistEngine/src/PoltergeistEngine/Rendering/VertexBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "PoltergeistEngine/Rendering/VertexBuffer.hpp" 2 | #include "PoltergeistEngine/Rendering/OpenGlUtilities.hpp" 3 | 4 | VertexBuffer::VertexBuffer(const void* vertices, const size_t size, const VertexBufferLayout& layout) noexcept 5 | { 6 | glGenBuffers(1, &m_vertexBufferId); 7 | Bind(); 8 | glBufferData(GL_ARRAY_BUFFER, size, vertices, GL_STATIC_DRAW); 9 | 10 | size_t offset = 0; 11 | 12 | for (size_t i = 0; i < layout.GetElements().size(); i++) 13 | { 14 | auto& element = layout.GetElements()[i]; 15 | 16 | glVertexAttribPointer(i, element.GetCount(), element.GetType(), GL_FALSE, layout.GetStride(), reinterpret_cast(offset)); 17 | glEnableVertexAttribArray(i); 18 | 19 | offset += element.GetCount() * GetOpenGlTypeSize(element.GetType()); 20 | } 21 | 22 | Unbind(); 23 | } 24 | 25 | VertexBuffer::~VertexBuffer() noexcept 26 | { 27 | glDeleteBuffers(1, &m_vertexBufferId); 28 | } 29 | 30 | void VertexBuffer::Bind() const noexcept 31 | { 32 | glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId); 33 | } 34 | 35 | void VertexBuffer::Unbind() const noexcept 36 | { 37 | glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferId); 38 | } 39 | -------------------------------------------------------------------------------- /PoltergeistSandbox/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(PoltergeistSandbox src/PoltergeistSandbox.cpp) 2 | target_include_directories(PoltergeistSandbox PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/") 3 | find_package(glfw3 CONFIG REQUIRED) 4 | find_package(glad CONFIG REQUIRED) 5 | target_link_libraries(PoltergeistSandbox PUBLIC Poltergeist::PoltergeistEngine PRIVATE glfw glad::glad) 6 | add_executable(Poltergeist::PoltergeistSandbox ALIAS PoltergeistSandbox) 7 | add_custom_target(copy-shaders-to-sandbox ALL COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/PoltergeistEngine/shaders ${CMAKE_CURRENT_BINARY_DIR}) 8 | add_custom_target(copy-resources-to-sandbox ALL COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/PoltergeistEngine/resources ${CMAKE_CURRENT_BINARY_DIR}) 9 | add_dependencies(PoltergeistSandbox copy-shaders-to-sandbox copy-resources-to-sandbox) 10 | -------------------------------------------------------------------------------- /PoltergeistSandbox/include/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProjectPoltergeist/Poltergeist/ecdcadd3d9dfe537e588dc984e290a0dd71a7f6c/PoltergeistSandbox/include/.gitkeep -------------------------------------------------------------------------------- /PoltergeistSandbox/src/PoltergeistSandbox.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #ifdef WIN32 10 | #include 11 | #endif 12 | 13 | extern "C" 14 | { 15 | POLTERGEIST_PUBLIC uint32_t NvOptimusEnablement = 1; 16 | POLTERGEIST_PUBLIC int32_t AmdPowerXpressRequestHighPerformance = 1; 17 | } 18 | 19 | void OnWindowSizeUpdate(GLFWwindow* window, int width, int height) 20 | { 21 | glViewport(0, 0, width, height); 22 | } 23 | 24 | int main() 25 | { 26 | #ifdef WIN32 27 | SetConsoleOutputCP(CP_UTF8); 28 | setvbuf(stdout, nullptr, _IOFBF, 1024); 29 | #endif 30 | 31 | std::cout << "Hello sandbox!" << std::endl; 32 | 33 | glfwInit(); 34 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 35 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 36 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 37 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); 38 | 39 | { 40 | std::unique_ptr window { 41 | glfwCreateWindow(800, 600, "Poltergeist Sandbox", nullptr, nullptr), 42 | &glfwDestroyWindow 43 | }; 44 | 45 | if (window.get() == nullptr) 46 | { 47 | glfwTerminate(); 48 | return -1; 49 | } 50 | 51 | glfwMakeContextCurrent(window.get()); 52 | glfwSetWindowSizeCallback(window.get(), OnWindowSizeUpdate); 53 | 54 | if (!gladLoadGLLoader(reinterpret_cast(glfwGetProcAddress))) 55 | { 56 | glfwTerminate(); 57 | return -2; 58 | } 59 | 60 | std::shared_ptr texture = Texture::CreateFromFile("texture.png", 1); 61 | std::shared_ptr renderer = Renderer::Create(); 62 | 63 | FrameBuffer frameBuffer(800, 600); 64 | 65 | while (!glfwWindowShouldClose(window.get())) 66 | { 67 | glfwPollEvents(); 68 | 69 | renderer->BeginRenderPass(frameBuffer); 70 | 71 | renderer->Clear(glm::vec3(0.3f, 0.3f, 0.3f)); 72 | renderer->DrawQuad(glm::vec2(-0.70f, 0.0f), 0.0f, glm::vec2(0.25f, 0.25f), glm::vec3(1.0, 0.0, 0.0)); 73 | renderer->DrawQuad(glm::vec2(0.25f, 0.25f), 45.0f, glm::vec2(0.50f, 0.50f), texture); 74 | 75 | renderer->EndRenderPass(); 76 | 77 | renderer->BeginRenderPass(); 78 | 79 | renderer->Clear(glm::vec3(0.3f, 0.3f, 0.3f)); 80 | renderer->DrawQuad(glm::vec2(0.0f, 0.0f), 0.0f, glm::vec2(0.5f, 0.5f), frameBuffer.GetTextureAttachment()); 81 | 82 | renderer->EndRenderPass(); 83 | 84 | glfwSwapBuffers(window.get()); 85 | } 86 | } 87 | 88 | glfwTerminate(); 89 | 90 | return 0; 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Poltergeist 2 | An experimental 2D game engine written in C#, for fun. 3 | 4 | # License 5 | MPL 2.0 6 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"poltergeist", 3 | "version-string":"0.0.1", 4 | "dependencies":[ 5 | "glfw3", 6 | "glad", 7 | "glm", 8 | "libpng", 9 | { 10 | "name":"imgui", 11 | "features":[ 12 | "glfw-binding", 13 | "opengl3-glad-binding" 14 | ] 15 | } 16 | ] 17 | } 18 | --------------------------------------------------------------------------------