Initial commit

This commit is contained in:
Luke Street 2022-07-27 11:25:25 -04:00
commit 9a725c89cf
104 changed files with 20111 additions and 0 deletions

29
.clang-format Normal file
View File

@ -0,0 +1,29 @@
---
BasedOnStyle: LLVM
ColumnLimit: 120
UseTab: Never
TabWidth: 2
---
Language: Cpp
DerivePointerAlignment: false
PointerAlignment: Left
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
IndentCaseLabels: false
AllowShortBlocksOnASingleLine: Always
AlignOperands: true
AlignTrailingComments: true
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BreakConstructorInitializersBeforeComma: true
AlwaysBreakAfterReturnType: None
AlwaysBreakAfterDefinitionReturnType: None
AllowShortFunctionsOnASingleLine: All
Cpp11BracedListStyle: true
NamespaceIndentation: None
BinPackArguments: true
BinPackParameters: true
SortIncludes: false
AccessModifierOffset: -2
ConstructorInitializerIndentWidth: 0
ConstructorInitializerAllOnOneLineOrOnePerLine: true

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.buildcache/
.DS_Store
.idea/
.vs/
build/
cmake-build-*/
CMakeUserPresets.json
out/

13
.gitmodules vendored Normal file
View File

@ -0,0 +1,13 @@
[submodule "extern/dawn"]
path = extern/dawn
url = https://github.com/encounter/dawn-cmake.git
[submodule "extern/SDL"]
path = extern/SDL
url = https://github.com/encounter/SDL.git
branch = merged
[submodule "extern/imgui"]
path = extern/imgui
url = https://github.com/ocornut/imgui.git
[submodule "extern/fmt"]
path = extern/fmt
url = https://github.com/fmtlib/fmt.git

86
CMakeLists.txt Normal file
View File

@ -0,0 +1,86 @@
cmake_minimum_required(VERSION 3.13)
project(aurora LANGUAGES C CXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)
option(AURORA_NATIVE_MATRIX "Assume OpenGL-layout matrices, disables transposing" OFF)
add_subdirectory(extern)
add_library(aurora STATIC
lib/aurora.cpp
lib/webgpu/gpu.cpp
lib/imgui.cpp
lib/input.cpp
lib/window.cpp
lib/dawn/BackendBinding.cpp
lib/gfx/common.cpp
lib/gfx/texture.cpp
lib/gfx/gx.cpp
lib/gfx/gx_shader.cpp
lib/gfx/texture_convert.cpp
lib/gfx/stream/shader.cpp
lib/gfx/model/shader.cpp
lib/dolphin/GXBump.cpp
lib/dolphin/GXCull.cpp
lib/dolphin/GXDispList.cpp
lib/dolphin/GXDraw.cpp
lib/dolphin/GXExtra.cpp
lib/dolphin/GXFifo.cpp
lib/dolphin/GXFrameBuffer.cpp
lib/dolphin/GXGeometry.cpp
lib/dolphin/GXGet.cpp
lib/dolphin/GXLighting.cpp
lib/dolphin/GXManage.cpp
lib/dolphin/GXPerf.cpp
lib/dolphin/GXPixel.cpp
lib/dolphin/GXTev.cpp
lib/dolphin/GXTexture.cpp
lib/dolphin/GXTransform.cpp
lib/dolphin/GXVert.cpp
lib/dolphin/vi.cpp
)
add_library(aurora::aurora ALIAS aurora)
target_compile_definitions(aurora PUBLIC AURORA TARGET_PC)
if (AURORA_NATIVE_MATRIX)
target_compile_definitions(aurora PRIVATE AURORA_NATIVE_MATRIX)
endif ()
target_include_directories(aurora PUBLIC include)
target_include_directories(aurora PRIVATE ../imgui)
if (NOT TARGET SDL2::SDL2-static)
find_package(SDL2 REQUIRED)
endif ()
target_link_libraries(aurora PUBLIC SDL2::SDL2-static fmt::fmt imgui xxhash)
target_link_libraries(aurora PRIVATE dawn_native dawncpp webgpu_dawn absl::btree absl::flat_hash_map)
if (DAWN_ENABLE_VULKAN)
target_compile_definitions(aurora PRIVATE DAWN_ENABLE_BACKEND_VULKAN)
target_sources(aurora PRIVATE lib/dawn/VulkanBinding.cpp)
endif ()
if (DAWN_ENABLE_METAL)
target_compile_definitions(aurora PRIVATE DAWN_ENABLE_BACKEND_METAL)
target_sources(aurora PRIVATE lib/dawn/MetalBinding.mm)
set_source_files_properties(lib/dawn/MetalBinding.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
endif ()
if (DAWN_ENABLE_D3D12)
target_compile_definitions(aurora PRIVATE DAWN_ENABLE_BACKEND_D3D12)
target_sources(aurora PRIVATE lib/dawn/D3D12Binding.cpp)
endif ()
if (DAWN_ENABLE_DESKTOP_GL OR DAWN_ENABLE_OPENGLES)
target_compile_definitions(aurora PRIVATE DAWN_ENABLE_BACKEND_OPENGL)
if (DAWN_ENABLE_DESKTOP_GL)
target_compile_definitions(aurora PRIVATE DAWN_ENABLE_BACKEND_DESKTOP_GL)
endif ()
if (DAWN_ENABLE_OPENGLES)
target_compile_definitions(aurora PRIVATE DAWN_ENABLE_BACKEND_OPENGLES)
endif ()
target_sources(aurora PRIVATE lib/dawn/OpenGLBinding.cpp)
endif ()
if (DAWN_ENABLE_NULL)
target_compile_definitions(aurora PRIVATE DAWN_ENABLE_BACKEND_NULL)
target_sources(aurora PRIVATE lib/dawn/NullBinding.cpp)
endif ()
# Optional
add_library(aurora_main STATIC lib/main.cpp)
target_include_directories(aurora_main PUBLIC include)
target_link_libraries(aurora_main PUBLIC SDL2::SDL2main)
add_library(aurora::main ALIAS aurora_main)

266
GX.md Normal file
View File

@ -0,0 +1,266 @@
# GX API Support
- GXBump
- [x] GXSetNumIndStages
- [x] GXSetIndTexOrder
- [x] GXSetIndTexCoordScale
- [x] GXSetIndTexMtx
- [x] GXSetTevIndirect
- [x] GXSetTevDirect
- [x] GXSetTevIndWarp
- [ ] GXSetTevIndTile
- [ ] GXSetTevIndBumpST
- [ ] GXSetTevIndBumpXYZ
- [ ] GXSetTevIndRepeat
- GXCull
- [x] GXSetScissor
- [x] GXSetCullMode
- [ ] GXSetCoPlanar
- GXDispList
- [x] GXBeginDisplayList (stub)
- [x] GXEndDisplayList (stub)
- [x] GXCallDisplayList
- GXDraw
- [ ] GXDrawCylinder
- [ ] GXDrawTorus
- [ ] GXDrawSphere
- [ ] GXDrawCube
- [ ] GXDrawDodeca
- [ ] GXDrawOctahedron
- [ ] GXDrawIcosahedron
- [ ] GXDrawSphere1
- [ ] GXGenNormalTable
- GXFifo
- [x] GXGetGPStatus (stub)
- [ ] GXGetFifoStatus
- [x] GXGetFifoPtrs (stub)
- [x] GXGetCPUFifo (stub)
- [x] GXGetGPFifo (stub)
- [ ] GXGetFifoBase
- [ ] GXGetFifoSize
- [ ] GXGetFifoLimits
- [ ] GXSetBreakPtCallback
- [ ] GXEnableBreakPt
- [ ] GXDisableBreakPt
- [x] GXInitFifoBase (stub)
- [x] GXInitFifoPtrs (stub)
- [ ] GXInitFifoLimits
- [x] GXSetCPUFifo (stub)
- [x] GXSetGPFifo (stub)
- [x] GXSaveCPUFifo (stub)
- [ ] GXSaveGPFifo
- [ ] GXRedirectWriteGatherPipe
- [ ] GXRestoreWriteGatherPipe
- [ ] GXSetCurrentGXThread
- [ ] GXGetCurrentGXThread
- [ ] GXGetOverflowCount
- [ ] GXResetOverflowCount
- GXFrameBuffer
- [x] GXAdjustForOverscan
- [x] GXSetDispCopySrc (stub)
- [x] GXSetTexCopySrc
- [x] GXSetDispCopyDst (stub)
- [x] GXSetTexCopyDst
- [ ] GXSetDispCopyFrame2Field
- [ ] GXSetCopyClamp
- [x] GXSetDispCopyYScale (stub)
- [x] GXSetCopyClear
- [x] GXSetCopyFilter (stub)
- [x] GXSetDispCopyGamma (stub)
- [x] GXCopyDisp (stub)
- [x] GXCopyTex
- [ ] GXGetYScaleFactor
- [ ] GXGetNumXfbLines
- [ ] GXClearBoundingBox
- [ ] GXReadBoundingBox
- GXGeometry
- [x] GXSetVtxDesc
- [x] GXSetVtxDescv
- [x] GXClearVtxDesc
- [x] GXSetVtxAttrFmt
- [ ] GXSetVtxAttrFmtv
- [x] GXSetArray
- [x] GXBegin
- [x] GXEnd
- [x] GXSetTexCoordGen2
- [x] GXSetNumTexGens
- [ ] GXInvalidateVtxCache
- [ ] GXSetLineWidth
- [ ] GXSetPointSize
- [ ] GXEnableTexOffsets
- GXGet
- [ ] GXGetVtxDesc
- [ ] GXGetVtxDescv
- [ ] GXGetVtxAttrFmtv
- [ ] GXGetLineWidth
- [ ] GXGetPointSize
- [x] GXGetVtxAttrFmt
- [ ] GXGetViewportv
- [x] GXGetProjectionv
- [ ] GXGetScissor
- [ ] GXGetCullMode
- [x] GXGetLightAttnA
- [x] GXGetLightAttnK
- [x] GXGetLightPos
- [x] GXGetLightDir
- [x] GXGetLightColor
- [x] GXGetTexObjData
- [x] GXGetTexObjWidth
- [x] GXGetTexObjHeight
- [x] GXGetTexObjFmt
- [x] GXGetTexObjWrapS
- [x] GXGetTexObjWrapT
- [x] GXGetTexObjMipMap
- [ ] GXGetTexObjAll
- [ ] GXGetTexObjMinFilt
- [ ] GXGetTexObjMagFilt
- [ ] GXGetTexObjMinLOD
- [ ] GXGetTexObjMaxLOD
- [ ] GXGetTexObjLODBias
- [ ] GXGetTexObjBiasClamp
- [ ] GXGetTexObjEdgeLOD
- [ ] GXGetTexObjMaxAniso
- [ ] GXGetTexObjLODAll
- [ ] GXGetTexObjTlut
- [ ] GXGetTlutObjData
- [ ] GXGetTlutObjFmt
- [ ] GXGetTlutObjNumEntries
- [ ] GXGetTlutObjAll
- [ ] GXGetTexRegionAll
- [ ] GXGetTlutRegionAll
- GXLighting
- [x] GXInitLightAttn
- [x] GXInitLightAttnA
- [x] GXInitLightAttnK
- [x] GXInitLightSpot
- [x] GXInitLightDistAttn
- [x] GXInitLightPos
- [x] GXInitLightColor
- [x] GXLoadLightObjImm
- [ ] GXLoadLightObjIndx
- [x] GXSetChanAmbColor
- [x] GXSetChanMatColor
- [x] GXSetNumChans
- [x] GXInitLightDir
- [x] GXInitSpecularDir
- [x] GXInitSpecularDirHA
- [x] GXSetChanCtrl
- GXManage
- [x] GXInit (stub)
- [ ] GXAbortFrame
- [ ] GXSetDrawSync
- [ ] GXReadDrawSync
- [ ] GXSetDrawSyncCallback
- [x] GXDrawDone (stub)
- [x] GXSetDrawDone (stub)
- [ ] GXWaitDrawDone
- [x] GXSetDrawDoneCallback (stub)
- [ ] GXSetResetWritePipe
- [x] GXFlush (stub)
- [ ] GXResetWriteGatherPipe
- [x] GXPixModeSync (stub)
- [x] GXTexModeSync (stub)
- [ ] IsWriteGatherBufferEmpty
- [ ] GXSetMisc
- GXPerf
- [ ] GXSetGPMetric
- [ ] GXClearGPMetric
- [ ] GXReadGPMetric
- [ ] GXReadGP0Metric
- [ ] GXReadGP1Metric
- [ ] GXReadMemMetric
- [ ] GXClearMemMetric
- [ ] GXReadPixMetric
- [ ] GXClearPixMetric
- [ ] GXSetVCacheMetric
- [ ] GXReadVCacheMetric
- [ ] GXClearVCacheMetric
- [ ] GXReadXfRasMetric
- [ ] GXInitXfRasMetric
- [ ] GXReadClksPerVtx
- GXPixel
- [x] GXSetFog
- [x] GXSetFogColor
- [ ] GXInitFogAdjTable
- [ ] GXSetFogRangeAdj
- [x] GXSetBlendMode
- [x] GXSetColorUpdate
- [x] GXSetAlphaUpdate
- [x] GXSetZMode
- [ ] GXSetZCompLoc
- [x] GXSetPixelFmt (stub)
- [x] GXSetDither (stub)
- [x] GXSetDstAlpha
- [ ] GXSetFieldMask
- [ ] GXSetFieldMode
- GXTev
- [x] GXSetTevOp
- [x] GXSetTevColorIn
- [x] GXSetTevAlphaIn
- [x] GXSetTevColorOp
- [x] GXSetTevAlphaOp
- [x] GXSetTevColor
- [x] GXSetTevColorS10
- [x] GXSetAlphaCompare
- [x] GXSetTevOrder
- [ ] GXSetZTexture
- [x] GXSetNumTevStages
- [x] GXSetTevKColor
- [x] GXSetTevKColorSel
- [x] GXSetTevKAlphaSel
- [x] GXSetTevSwapMode
- [x] GXSetTevSwapModeTable
- GXTexture
- [x] GXInitTexObj
- [x] GXInitTexObjCI
- [x] GXInitTexObjLOD
- [x] GXInitTexObjData
- [x] GXInitTexObjWrapMode
- [x] GXInitTexObjTlut
- [ ] GXInitTexObjFilter
- [ ] GXInitTexObjMaxLOD
- [ ] GXInitTexObjMinLOD
- [ ] GXInitTexObjLODBias
- [ ] GXInitTexObjBiasClamp
- [ ] GXInitTexObjEdgeLOD
- [ ] GXInitTexObjMaxAniso
- [ ] GXInitTexObjUserData
- [ ] GXGetTexObjUserData
- [x] GXLoadTexObj
- [x] GXGetTexBufferSize
- [x] GXInitTlutObj
- [x] GXLoadTlut
- [ ] GXInitTexCacheRegion
- [ ] GXInitTexPreLoadRegion
- [ ] GXInitTlutRegion
- [ ] GXInvalidateTexRegion
- [x] GXInvalidateTexAll (stub)
- [ ] GXPreLoadEntireTexture
- [ ] GXSetTexRegionCallback
- [ ] GXSetTlutRegionCallback
- [ ] GXLoadTexObjPreLoaded
- [ ] GXSetTexCoordScaleManually
- [ ] GXSetTexCoordCylWrap
- [ ] GXSetTexCoordBias
- GXTransform
- [x] GXSetProjection
- [ ] GXSetProjectionv
- [x] GXLoadPosMtxImm
- [ ] GXLoadPosMtxIndx
- [x] GXLoadNrmMtxImm
- [ ] GXLoadNrmMtxImm3x3
- [ ] GXLoadNrmMtxIndx3x3
- [x] GXSetCurrentMtx
- [x] GXLoadTexMtxImm
- [ ] GXLoadTexMtxIndx
- [ ] GXProject
- [x] GXSetViewport
- [x] GXSetViewportJitter
- [ ] GXSetZScaleOffset
- [ ] GXSetScissorBoxOffset
- [ ] GXSetClipMode
- GXVert
- [x] GXPosition\[n]\[t]
- [x] GXNormal\[n]\[t]
- [x] GXColor\[n]\[t]
- [x] GXTexCoord\[n]\[t]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2022 Luke Street
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

33
README.md Normal file
View File

@ -0,0 +1,33 @@
# Aurora
Aurora is a source-level GameCube & Wii compatibility layer intended for use with game reverse engineering projects.
Originally developed for use in [Metaforce](https://github.com/AxioDL/metaforce), a Metroid Prime reverse engineering
project.
### Features
- GX compatibility layer
- Graphics API support: D3D12, Vulkan, Metal, OpenGL 4.4+ and OpenGL ES 3.1+
- *Planned: deko3d backend for Switch*
- Application layer using SDL
- Runs on Windows, Linux, macOS, iOS, tvOS (Android coming soon)
- Audio support with SDL_audio
- PAD compatibility layer
- Utilizes SDL_GameController for wide controller support, including GameCube controllers.
- *Planned: Wii remote support*
- [Dear ImGui](https://github.com/ocornut/imgui) built-in for UI
### GX
The GX compatibility layer is built on top of [WebGPU](https://www.w3.org/TR/webgpu/), a cross-platform graphics API
abstraction layer. WebGPU allows targeting all major platforms simultaneously with minimal overhead.
Currently, the WebGPU implementation used is Chromium's [Dawn](https://dawn.googlesource.com/dawn/).
See [GX API support](GX.md) for more information.
### PAD
The PAD compatibility layer utilizes SDL_GameController to automatically support & provide mappings for hundreds of
controllers across all platforms.

38
extern/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,38 @@
if (CMAKE_SYSTEM_NAME STREQUAL Windows)
set(DAWN_ENABLE_DESKTOP_GL ON CACHE BOOL "Enable compilation of the OpenGL backend" FORCE)
endif ()
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
set(DAWN_ENABLE_OPENGLES ON CACHE BOOL "Enable compilation of the OpenGL ES backend" FORCE)
endif ()
add_subdirectory(dawn EXCLUDE_FROM_ALL)
if (DAWN_ENABLE_VULKAN)
target_compile_definitions(dawn_native PRIVATE
DAWN_ENABLE_VULKAN_VALIDATION_LAYERS
DAWN_VK_DATA_DIR="vulkandata")
endif ()
if (MSVC)
target_compile_options(dawn_native PRIVATE /bigobj)
else ()
target_compile_options(SPIRV-Tools-static PRIVATE -Wno-implicit-fallthrough)
target_compile_options(SPIRV-Tools-opt PRIVATE -Wno-implicit-fallthrough)
endif ()
if (WIN32)
set(SDL_LIBC ON CACHE BOOL "Use the system C library" FORCE)
endif ()
add_subdirectory(SDL EXCLUDE_FROM_ALL)
if (NOT MSVC)
target_compile_options(SDL2-static PRIVATE -Wno-implicit-fallthrough -Wno-shadow)
endif ()
add_subdirectory(xxhash EXCLUDE_FROM_ALL)
add_subdirectory(fmt EXCLUDE_FROM_ALL)
add_library(imgui
imgui/imgui.cpp
imgui/imgui_demo.cpp
imgui/imgui_draw.cpp
imgui/imgui_tables.cpp
imgui/imgui_widgets.cpp
imgui/misc/cpp/imgui_stdlib.cpp)
target_include_directories(imgui PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/imgui)

1
extern/SDL vendored Submodule

@ -0,0 +1 @@
Subproject commit 34458fe9f4a11a5b15ced6193e4c5d7ef97cca36

1
extern/dawn vendored Submodule

@ -0,0 +1 @@
Subproject commit 64a23ce0ede5f232cc209b69d64164ede6810b65

1
extern/fmt vendored Submodule

@ -0,0 +1 @@
Subproject commit 81f1cc74a776581cdef8659d176049d3aeb743c6

1
extern/imgui vendored Submodule

@ -0,0 +1 @@
Subproject commit e99c4fc6688e218a0e5da50f56638aebab45da9b

3
extern/xxhash/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,3 @@
add_library(xxhash xxhash_impl.c)
target_include_directories(xxhash PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(xxhash INTERFACE XXH_STATIC_LINKING_ONLY)

26
extern/xxhash/LICENSE vendored Normal file
View File

@ -0,0 +1,26 @@
xxHash Library
Copyright (c) 2012-2021 Yann Collet
All rights reserved.
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

770
extern/xxhash/xxh_x86dispatch.c vendored Normal file
View File

@ -0,0 +1,770 @@
/*
* xxHash - Extremely Fast Hash algorithm
* Copyright (C) 2020-2021 Yann Collet
*
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You can contact the author at:
* - xxHash homepage: https://www.xxhash.com
* - xxHash source repository: https://github.com/Cyan4973/xxHash
*/
/*!
* @file xxh_x86dispatch.c
*
* Automatic dispatcher code for the @ref XXH3_family on x86-based targets.
*
* Optional add-on.
*
* **Compile this file with the default flags for your target.** Do not compile
* with flags like `-mavx*`, `-march=native`, or `/arch:AVX*`, there will be
* an error. See @ref XXH_X86DISPATCH_ALLOW_AVX for details.
*
* @defgroup dispatch x86 Dispatcher
* @{
*/
#if defined (__cplusplus)
extern "C" {
#endif
#if !(defined(__x86_64__) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64))
# error "Dispatching is currently only supported on x86 and x86_64."
#endif
/*!
* @def XXH_X86DISPATCH_ALLOW_AVX
* @brief Disables the AVX sanity check.
*
* Don't compile xxh_x86dispatch.c with options like `-mavx*`, `-march=native`,
* or `/arch:AVX*`. It is intended to be compiled for the minimum target, and
* it selectively enables SSE2, AVX2, and AVX512 when it is needed.
*
* Using this option _globally_ allows this feature, and therefore makes it
* undefined behavior to execute on any CPU without said feature.
*
* Even if the source code isn't directly using AVX intrinsics in a function,
* the compiler can still generate AVX code from autovectorization and by
* "upgrading" SSE2 intrinsics to use the VEX prefixes (a.k.a. AVX128).
*
* Use the same flags that you use to compile the rest of the program; this
* file will safely generate SSE2, AVX2, and AVX512 without these flags.
*
* Define XXH_X86DISPATCH_ALLOW_AVX to ignore this check, and feel free to open
* an issue if there is a target in the future where AVX is a default feature.
*/
#ifdef XXH_DOXYGEN
# define XXH_X86DISPATCH_ALLOW_AVX
#endif
#if defined(__AVX__) && !defined(XXH_X86DISPATCH_ALLOW_AVX)
# error "Do not compile xxh_x86dispatch.c with AVX enabled! See the comment above."
#endif
#ifdef __has_include
# define XXH_HAS_INCLUDE(header) __has_include(header)
#else
# define XXH_HAS_INCLUDE(header) 0
#endif
/*!
* @def XXH_DISPATCH_SCALAR
* @brief Enables/dispatching the scalar code path.
*
* If this is defined to 0, SSE2 support is assumed. This reduces code size
* when the scalar path is not needed.
*
* This is automatically defined to 0 when...
* - SSE2 support is enabled in the compiler
* - Targeting x86_64
* - Targeting Android x86
* - Targeting macOS
*/
#ifndef XXH_DISPATCH_SCALAR
# if defined(__SSE2__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) /* SSE2 on by default */ \
|| defined(__x86_64__) || defined(_M_X64) /* x86_64 */ \
|| defined(__ANDROID__) || defined(__APPLEv__) /* Android or macOS */
# define XXH_DISPATCH_SCALAR 0 /* disable */
# else
# define XXH_DISPATCH_SCALAR 1
# endif
#endif
/*!
* @def XXH_DISPATCH_AVX2
* @brief Enables/disables dispatching for AVX2.
*
* This is automatically detected if it is not defined.
* - GCC 4.7 and later are known to support AVX2, but >4.9 is required for
* to get the AVX2 intrinsics and typedefs without -mavx -mavx2.
* - Visual Studio 2013 Update 2 and later are known to support AVX2.
* - The GCC/Clang internal header `<avx2intrin.h>` is detected. While this is
* not allowed to be included directly, it still appears in the builtin
* include path and is detectable with `__has_include`.
*
* @see XXH_AVX2
*/
#ifndef XXH_DISPATCH_AVX2
# if (defined(__GNUC__) && (__GNUC__ > 4)) /* GCC 5.0+ */ \
|| (defined(_MSC_VER) && _MSC_VER >= 1900) /* VS 2015+ */ \
|| (defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 180030501) /* VS 2013 Update 2 */ \
|| XXH_HAS_INCLUDE(<avx2intrin.h>) /* GCC/Clang internal header */
# define XXH_DISPATCH_AVX2 1 /* enable dispatch towards AVX2 */
# else
# define XXH_DISPATCH_AVX2 0
# endif
#endif /* XXH_DISPATCH_AVX2 */
/*!
* @def XXH_DISPATCH_AVX512
* @brief Enables/disables dispatching for AVX512.
*
* Automatically detected if one of the following conditions is met:
* - GCC 4.9 and later are known to support AVX512.
* - Visual Studio 2017 and later are known to support AVX2.
* - The GCC/Clang internal header `<avx512fintrin.h>` is detected. While this
* is not allowed to be included directly, it still appears in the builtin
* include path and is detectable with `__has_include`.
*
* @see XXH_AVX512
*/
#ifndef XXH_DISPATCH_AVX512
# if (defined(__GNUC__) \
&& (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))) /* GCC 4.9+ */ \
|| (defined(_MSC_VER) && _MSC_VER >= 1910) /* VS 2017+ */ \
|| XXH_HAS_INCLUDE(<avx512fintrin.h>) /* GCC/Clang internal header */
# define XXH_DISPATCH_AVX512 1 /* enable dispatch towards AVX512 */
# else
# define XXH_DISPATCH_AVX512 0
# endif
#endif /* XXH_DISPATCH_AVX512 */
/*!
* @def XXH_TARGET_SSE2
* @brief Allows a function to be compiled with SSE2 intrinsics.
*
* Uses `__attribute__((__target__("sse2")))` on GCC to allow SSE2 to be used
* even with `-mno-sse2`.
*
* @def XXH_TARGET_AVX2
* @brief Like @ref XXH_TARGET_SSE2, but for AVX2.
*
* @def XXH_TARGET_AVX512
* @brief Like @ref XXH_TARGET_SSE2, but for AVX512.
*/
#if defined(__GNUC__)
# include <emmintrin.h> /* SSE2 */
# if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
# include <immintrin.h> /* AVX2, AVX512F */
# endif
# define XXH_TARGET_SSE2 __attribute__((__target__("sse2")))
# define XXH_TARGET_AVX2 __attribute__((__target__("avx2")))
# define XXH_TARGET_AVX512 __attribute__((__target__("avx512f")))
#elif defined(_MSC_VER)
# include <intrin.h>
# define XXH_TARGET_SSE2
# define XXH_TARGET_AVX2
# define XXH_TARGET_AVX512
#else
# error "Dispatching is currently not supported for your compiler."
#endif
#ifdef XXH_DISPATCH_DEBUG
/* debug logging */
# include <stdio.h>
# define XXH_debugPrint(str) { fprintf(stderr, "DEBUG: xxHash dispatch: %s \n", str); fflush(NULL); }
#else
# define XXH_debugPrint(str) ((void)0)
# undef NDEBUG /* avoid redefinition */
# define NDEBUG
#endif
#include <assert.h>
#define XXH_INLINE_ALL
#define XXH_X86DISPATCH
#include "xxhash.h"
/*
* Support both AT&T and Intel dialects
*
* GCC doesn't convert AT&T syntax to Intel syntax, and will error out if
* compiled with -masm=intel. Instead, it supports dialect switching with
* curly braces: { AT&T syntax | Intel syntax }
*
* Clang's integrated assembler automatically converts AT&T syntax to Intel if
* needed, making the dialect switching useless (it isn't even supported).
*
* Note: Comments are written in the inline assembly itself.
*/
#ifdef __clang__
# define XXH_I_ATT(intel, att) att "\n\t"
#else
# define XXH_I_ATT(intel, att) "{" att "|" intel "}\n\t"
#endif
/*!
* @internal
* @brief Runs CPUID.
*
* @param eax , ecx The parameters to pass to CPUID, %eax and %ecx respectively.
* @param abcd The array to store the result in, `{ eax, ebx, ecx, edx }`
*/
static void XXH_cpuid(xxh_u32 eax, xxh_u32 ecx, xxh_u32* abcd)
{
#if defined(_MSC_VER)
__cpuidex(abcd, eax, ecx);
#else
xxh_u32 ebx, edx;
# if defined(__i386__) && defined(__PIC__)
__asm__(
"# Call CPUID\n\t"
"#\n\t"
"# On 32-bit x86 with PIC enabled, we are not allowed to overwrite\n\t"
"# EBX, so we use EDI instead.\n\t"
XXH_I_ATT("mov edi, ebx", "movl %%ebx, %%edi")
XXH_I_ATT("cpuid", "cpuid" )
XXH_I_ATT("xchg edi, ebx", "xchgl %%ebx, %%edi")
: "=D" (ebx),
# else
__asm__(
"# Call CPUID\n\t"
XXH_I_ATT("cpuid", "cpuid")
: "=b" (ebx),
# endif
"+a" (eax), "+c" (ecx), "=d" (edx));
abcd[0] = eax;
abcd[1] = ebx;
abcd[2] = ecx;
abcd[3] = edx;
#endif
}
/*
* Modified version of Intel's guide
* https://software.intel.com/en-us/articles/how-to-detect-new-instruction-support-in-the-4th-generation-intel-core-processor-family
*/
#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
/*!
* @internal
* @brief Runs `XGETBV`.
*
* While the CPU may support AVX2, the operating system might not properly save
* the full YMM/ZMM registers.
*
* xgetbv is used for detecting this: Any compliant operating system will define
* a set of flags in the xcr0 register indicating how it saves the AVX registers.
*
* You can manually disable this flag on Windows by running, as admin:
*
* bcdedit.exe /set xsavedisable 1
*
* and rebooting. Run the same command with 0 to re-enable it.
*/
static xxh_u64 XXH_xgetbv(void)
{
#if defined(_MSC_VER)
return _xgetbv(0); /* min VS2010 SP1 compiler is required */
#else
xxh_u32 xcr0_lo, xcr0_hi;
__asm__(
"# Call XGETBV\n\t"
"#\n\t"
"# Older assemblers (e.g. macOS's ancient GAS version) don't support\n\t"
"# the XGETBV opcode, so we encode it by hand instead.\n\t"
"# See <https://github.com/asmjit/asmjit/issues/78> for details.\n\t"
".byte 0x0f, 0x01, 0xd0\n\t"
: "=a" (xcr0_lo), "=d" (xcr0_hi) : "c" (0));
return xcr0_lo | ((xxh_u64)xcr0_hi << 32);
#endif
}
#endif
#define XXH_SSE2_CPUID_MASK (1 << 26)
#define XXH_OSXSAVE_CPUID_MASK ((1 << 26) | (1 << 27))
#define XXH_AVX2_CPUID_MASK (1 << 5)
#define XXH_AVX2_XGETBV_MASK ((1 << 2) | (1 << 1))
#define XXH_AVX512F_CPUID_MASK (1 << 16)
#define XXH_AVX512F_XGETBV_MASK ((7 << 5) | (1 << 2) | (1 << 1))
/*!
* @internal
* @brief Returns the best XXH3 implementation.
*
* Runs various CPUID/XGETBV tests to try and determine the best implementation.
*
* @return The best @ref XXH_VECTOR implementation.
* @see XXH_VECTOR_TYPES
*/
static int XXH_featureTest(void)
{
xxh_u32 abcd[4];
xxh_u32 max_leaves;
int best = XXH_SCALAR;
#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
xxh_u64 xgetbv_val;
#endif
#if defined(__GNUC__) && defined(__i386__)
xxh_u32 cpuid_supported;
__asm__(
"# For the sake of ruthless backwards compatibility, check if CPUID\n\t"
"# is supported in the EFLAGS on i386.\n\t"
"# This is not necessary on x86_64 - CPUID is mandatory.\n\t"
"# The ID flag (bit 21) in the EFLAGS register indicates support\n\t"
"# for the CPUID instruction. If a software procedure can set and\n\t"
"# clear this flag, the processor executing the procedure supports\n\t"
"# the CPUID instruction.\n\t"
"# <https://c9x.me/x86/html/file_module_x86_id_45.html>\n\t"
"#\n\t"
"# Routine is from <https://wiki.osdev.org/CPUID>.\n\t"
"# Save EFLAGS\n\t"
XXH_I_ATT("pushfd", "pushfl" )
"# Store EFLAGS\n\t"
XXH_I_ATT("pushfd", "pushfl" )
"# Invert the ID bit in stored EFLAGS\n\t"
XXH_I_ATT("xor dword ptr[esp], 0x200000", "xorl $0x200000, (%%esp)")
"# Load stored EFLAGS (with ID bit inverted)\n\t"
XXH_I_ATT("popfd", "popfl" )
"# Store EFLAGS again (ID bit may or not be inverted)\n\t"
XXH_I_ATT("pushfd", "pushfl" )
"# eax = modified EFLAGS (ID bit may or may not be inverted)\n\t"
XXH_I_ATT("pop eax", "popl %%eax" )
"# eax = whichever bits were changed\n\t"
XXH_I_ATT("xor eax, dword ptr[esp]", "xorl (%%esp), %%eax" )
"# Restore original EFLAGS\n\t"
XXH_I_ATT("popfd", "popfl" )
"# eax = zero if ID bit can't be changed, else non-zero\n\t"
XXH_I_ATT("and eax, 0x200000", "andl $0x200000, %%eax" )
: "=a" (cpuid_supported) :: "cc");
if (XXH_unlikely(!cpuid_supported)) {
XXH_debugPrint("CPUID support is not detected!");
return best;
}
#endif
/* Check how many CPUID pages we have */
XXH_cpuid(0, 0, abcd);
max_leaves = abcd[0];
/* Shouldn't happen on hardware, but happens on some QEMU configs. */
if (XXH_unlikely(max_leaves == 0)) {
XXH_debugPrint("Max CPUID leaves == 0!");
return best;
}
/* Check for SSE2, OSXSAVE and xgetbv */
XXH_cpuid(1, 0, abcd);
/*
* Test for SSE2. The check is redundant on x86_64, but it doesn't hurt.
*/
if (XXH_unlikely((abcd[3] & XXH_SSE2_CPUID_MASK) != XXH_SSE2_CPUID_MASK))
return best;
XXH_debugPrint("SSE2 support detected.");
best = XXH_SSE2;
#if XXH_DISPATCH_AVX2 || XXH_DISPATCH_AVX512
/* Make sure we have enough leaves */
if (XXH_unlikely(max_leaves < 7))
return best;
/* Test for OSXSAVE and XGETBV */
if ((abcd[2] & XXH_OSXSAVE_CPUID_MASK) != XXH_OSXSAVE_CPUID_MASK)
return best;
/* CPUID check for AVX features */
XXH_cpuid(7, 0, abcd);
xgetbv_val = XXH_xgetbv();
#if XXH_DISPATCH_AVX2
/* Validate that AVX2 is supported by the CPU */
if ((abcd[1] & XXH_AVX2_CPUID_MASK) != XXH_AVX2_CPUID_MASK)
return best;
/* Validate that the OS supports YMM registers */
if ((xgetbv_val & XXH_AVX2_XGETBV_MASK) != XXH_AVX2_XGETBV_MASK) {
XXH_debugPrint("AVX2 supported by the CPU, but not the OS.");
return best;
}
/* AVX2 supported */
XXH_debugPrint("AVX2 support detected.");
best = XXH_AVX2;
#endif
#if XXH_DISPATCH_AVX512
/* Check if AVX512F is supported by the CPU */
if ((abcd[1] & XXH_AVX512F_CPUID_MASK) != XXH_AVX512F_CPUID_MASK) {
XXH_debugPrint("AVX512F not supported by CPU");
return best;
}
/* Validate that the OS supports ZMM registers */
if ((xgetbv_val & XXH_AVX512F_XGETBV_MASK) != XXH_AVX512F_XGETBV_MASK) {
XXH_debugPrint("AVX512F supported by the CPU, but not the OS.");
return best;
}
/* AVX512F supported */
XXH_debugPrint("AVX512F support detected.");
best = XXH_AVX512;
#endif
#endif
return best;
}
/* === Vector implementations === */
/*!
* @internal
* @brief Defines the various dispatch functions.
*
* TODO: Consolidate?
*
* @param suffix The suffix for the functions, e.g. sse2 or scalar
* @param target XXH_TARGET_* or empty.
*/
#define XXH_DEFINE_DISPATCH_FUNCS(suffix, target) \
\
/* === XXH3, default variants === */ \
\
XXH_NO_INLINE target XXH64_hash_t \
XXHL64_default_##suffix(const void* XXH_RESTRICT input, size_t len) \
{ \
return XXH3_hashLong_64b_internal( \
input, len, XXH3_kSecret, sizeof(XXH3_kSecret), \
XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix \
); \
} \
\
/* === XXH3, Seeded variants === */ \
\
XXH_NO_INLINE target XXH64_hash_t \
XXHL64_seed_##suffix(const void* XXH_RESTRICT input, size_t len, \
XXH64_hash_t seed) \
{ \
return XXH3_hashLong_64b_withSeed_internal( \
input, len, seed, XXH3_accumulate_512_##suffix, \
XXH3_scrambleAcc_##suffix, XXH3_initCustomSecret_##suffix \
); \
} \
\
/* === XXH3, Secret variants === */ \
\
XXH_NO_INLINE target XXH64_hash_t \
XXHL64_secret_##suffix(const void* XXH_RESTRICT input, size_t len, \
const void* secret, size_t secretLen) \
{ \
return XXH3_hashLong_64b_internal( \
input, len, secret, secretLen, \
XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix \
); \
} \
\
/* === XXH3 update variants === */ \
\
XXH_NO_INLINE target XXH_errorcode \
XXH3_update_##suffix(XXH3_state_t* state, const void* input, size_t len) \
{ \
return XXH3_update(state, (const xxh_u8*)input, len, \
XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix); \
} \
\
/* === XXH128 default variants === */ \
\
XXH_NO_INLINE target XXH128_hash_t \
XXHL128_default_##suffix(const void* XXH_RESTRICT input, size_t len) \
{ \
return XXH3_hashLong_128b_internal( \
input, len, XXH3_kSecret, sizeof(XXH3_kSecret), \
XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix \
); \
} \
\
/* === XXH128 Secret variants === */ \
\
XXH_NO_INLINE target XXH128_hash_t \
XXHL128_secret_##suffix(const void* XXH_RESTRICT input, size_t len, \
const void* XXH_RESTRICT secret, size_t secretLen) \
{ \
return XXH3_hashLong_128b_internal( \
input, len, (const xxh_u8*)secret, secretLen, \
XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix); \
} \
\
/* === XXH128 Seeded variants === */ \
\
XXH_NO_INLINE target XXH128_hash_t \
XXHL128_seed_##suffix(const void* XXH_RESTRICT input, size_t len, \
XXH64_hash_t seed) \
{ \
return XXH3_hashLong_128b_withSeed_internal(input, len, seed, \
XXH3_accumulate_512_##suffix, XXH3_scrambleAcc_##suffix, \
XXH3_initCustomSecret_##suffix); \
}
/* End XXH_DEFINE_DISPATCH_FUNCS */
#if XXH_DISPATCH_SCALAR
XXH_DEFINE_DISPATCH_FUNCS(scalar, /* nothing */)
#endif
XXH_DEFINE_DISPATCH_FUNCS(sse2, XXH_TARGET_SSE2)
#if XXH_DISPATCH_AVX2
XXH_DEFINE_DISPATCH_FUNCS(avx2, XXH_TARGET_AVX2)
#endif
#if XXH_DISPATCH_AVX512
XXH_DEFINE_DISPATCH_FUNCS(avx512, XXH_TARGET_AVX512)
#endif
#undef XXH_DEFINE_DISPATCH_FUNCS
/* ==== Dispatchers ==== */
typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_default)(const void* XXH_RESTRICT, size_t);
typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_withSeed)(const void* XXH_RESTRICT, size_t, XXH64_hash_t);
typedef XXH64_hash_t (*XXH3_dispatchx86_hashLong64_withSecret)(const void* XXH_RESTRICT, size_t, const void* XXH_RESTRICT, size_t);
typedef XXH_errorcode (*XXH3_dispatchx86_update)(XXH3_state_t*, const void*, size_t);
typedef struct {
XXH3_dispatchx86_hashLong64_default hashLong64_default;
XXH3_dispatchx86_hashLong64_withSeed hashLong64_seed;
XXH3_dispatchx86_hashLong64_withSecret hashLong64_secret;
XXH3_dispatchx86_update update;
} XXH_dispatchFunctions_s;
#define XXH_NB_DISPATCHES 4
/*!
* @internal
* @brief Table of dispatchers for @ref XXH3_64bits().
*
* @pre The indices must match @ref XXH_VECTOR_TYPE.
*/
static const XXH_dispatchFunctions_s XXH_kDispatch[XXH_NB_DISPATCHES] = {
#if XXH_DISPATCH_SCALAR
/* Scalar */ { XXHL64_default_scalar, XXHL64_seed_scalar, XXHL64_secret_scalar, XXH3_update_scalar },
#else
/* Scalar */ { NULL, NULL, NULL, NULL },
#endif
/* SSE2 */ { XXHL64_default_sse2, XXHL64_seed_sse2, XXHL64_secret_sse2, XXH3_update_sse2 },
#if XXH_DISPATCH_AVX2
/* AVX2 */ { XXHL64_default_avx2, XXHL64_seed_avx2, XXHL64_secret_avx2, XXH3_update_avx2 },
#else
/* AVX2 */ { NULL, NULL, NULL, NULL },
#endif
#if XXH_DISPATCH_AVX512
/* AVX512 */ { XXHL64_default_avx512, XXHL64_seed_avx512, XXHL64_secret_avx512, XXH3_update_avx512 }
#else
/* AVX512 */ { NULL, NULL, NULL, NULL }
#endif
};
/*!
* @internal
* @brief The selected dispatch table for @ref XXH3_64bits().
*/
static XXH_dispatchFunctions_s XXH_g_dispatch = { NULL, NULL, NULL, NULL };
typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_default)(const void* XXH_RESTRICT, size_t);
typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_withSeed)(const void* XXH_RESTRICT, size_t, XXH64_hash_t);
typedef XXH128_hash_t (*XXH3_dispatchx86_hashLong128_withSecret)(const void* XXH_RESTRICT, size_t, const void* XXH_RESTRICT, size_t);
typedef struct {
XXH3_dispatchx86_hashLong128_default hashLong128_default;
XXH3_dispatchx86_hashLong128_withSeed hashLong128_seed;
XXH3_dispatchx86_hashLong128_withSecret hashLong128_secret;
XXH3_dispatchx86_update update;
} XXH_dispatch128Functions_s;
/*!
* @internal
* @brief Table of dispatchers for @ref XXH3_128bits().
*
* @pre The indices must match @ref XXH_VECTOR_TYPE.
*/
static const XXH_dispatch128Functions_s XXH_kDispatch128[XXH_NB_DISPATCHES] = {
#if XXH_DISPATCH_SCALAR
/* Scalar */ { XXHL128_default_scalar, XXHL128_seed_scalar, XXHL128_secret_scalar, XXH3_update_scalar },
#else
/* Scalar */ { NULL, NULL, NULL, NULL },
#endif
/* SSE2 */ { XXHL128_default_sse2, XXHL128_seed_sse2, XXHL128_secret_sse2, XXH3_update_sse2 },
#if XXH_DISPATCH_AVX2
/* AVX2 */ { XXHL128_default_avx2, XXHL128_seed_avx2, XXHL128_secret_avx2, XXH3_update_avx2 },
#else
/* AVX2 */ { NULL, NULL, NULL, NULL },
#endif
#if XXH_DISPATCH_AVX512
/* AVX512 */ { XXHL128_default_avx512, XXHL128_seed_avx512, XXHL128_secret_avx512, XXH3_update_avx512 }
#else
/* AVX512 */ { NULL, NULL, NULL, NULL }
#endif
};
/*!
* @internal
* @brief The selected dispatch table for @ref XXH3_64bits().
*/
static XXH_dispatch128Functions_s XXH_g_dispatch128 = { NULL, NULL, NULL, NULL };
/*!
* @internal
* @brief Runs a CPUID check and sets the correct dispatch tables.
*/
static void XXH_setDispatch(void)
{
int vecID = XXH_featureTest();
XXH_STATIC_ASSERT(XXH_AVX512 == XXH_NB_DISPATCHES-1);
assert(XXH_SCALAR <= vecID && vecID <= XXH_AVX512);
#if !XXH_DISPATCH_SCALAR
assert(vecID != XXH_SCALAR);
#endif
#if !XXH_DISPATCH_AVX512
assert(vecID != XXH_AVX512);
#endif
#if !XXH_DISPATCH_AVX2
assert(vecID != XXH_AVX2);
#endif
XXH_g_dispatch = XXH_kDispatch[vecID];
XXH_g_dispatch128 = XXH_kDispatch128[vecID];
}
/* ==== XXH3 public functions ==== */
static XXH64_hash_t
XXH3_hashLong_64b_defaultSecret_selection(const void* input, size_t len,
XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
{
(void)seed64; (void)secret; (void)secretLen;
if (XXH_g_dispatch.hashLong64_default == NULL) XXH_setDispatch();
return XXH_g_dispatch.hashLong64_default(input, len);
}
XXH64_hash_t XXH3_64bits_dispatch(const void* input, size_t len)
{
return XXH3_64bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_defaultSecret_selection);
}
static XXH64_hash_t
XXH3_hashLong_64b_withSeed_selection(const void* input, size_t len,
XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
{
(void)secret; (void)secretLen;
if (XXH_g_dispatch.hashLong64_seed == NULL) XXH_setDispatch();
return XXH_g_dispatch.hashLong64_seed(input, len, seed64);
}
XXH64_hash_t XXH3_64bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed)
{
return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed_selection);
}
static XXH64_hash_t
XXH3_hashLong_64b_withSecret_selection(const void* input, size_t len,
XXH64_hash_t seed64, const xxh_u8* secret, size_t secretLen)
{
(void)seed64;
if (XXH_g_dispatch.hashLong64_secret == NULL) XXH_setDispatch();
return XXH_g_dispatch.hashLong64_secret(input, len, secret, secretLen);
}
XXH64_hash_t XXH3_64bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen)
{
return XXH3_64bits_internal(input, len, 0, secret, secretLen, XXH3_hashLong_64b_withSecret_selection);
}
XXH_errorcode
XXH3_64bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len)
{
if (XXH_g_dispatch.update == NULL) XXH_setDispatch();
return XXH_g_dispatch.update(state, (const xxh_u8*)input, len);
}
/* ==== XXH128 public functions ==== */
static XXH128_hash_t
XXH3_hashLong_128b_defaultSecret_selection(const void* input, size_t len,
XXH64_hash_t seed64, const void* secret, size_t secretLen)
{
(void)seed64; (void)secret; (void)secretLen;
if (XXH_g_dispatch128.hashLong128_default == NULL) XXH_setDispatch();
return XXH_g_dispatch128.hashLong128_default(input, len);
}
XXH128_hash_t XXH3_128bits_dispatch(const void* input, size_t len)
{
return XXH3_128bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_defaultSecret_selection);
}
static XXH128_hash_t
XXH3_hashLong_128b_withSeed_selection(const void* input, size_t len,
XXH64_hash_t seed64, const void* secret, size_t secretLen)
{
(void)secret; (void)secretLen;
if (XXH_g_dispatch128.hashLong128_seed == NULL) XXH_setDispatch();
return XXH_g_dispatch128.hashLong128_seed(input, len, seed64);
}
XXH128_hash_t XXH3_128bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed)
{
return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_withSeed_selection);
}
static XXH128_hash_t
XXH3_hashLong_128b_withSecret_selection(const void* input, size_t len,
XXH64_hash_t seed64, const void* secret, size_t secretLen)
{
(void)seed64;
if (XXH_g_dispatch128.hashLong128_secret == NULL) XXH_setDispatch();
return XXH_g_dispatch128.hashLong128_secret(input, len, secret, secretLen);
}
XXH128_hash_t XXH3_128bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen)
{
return XXH3_128bits_internal(input, len, 0, secret, secretLen, XXH3_hashLong_128b_withSecret_selection);
}
XXH_errorcode
XXH3_128bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len)
{
if (XXH_g_dispatch128.update == NULL) XXH_setDispatch();
return XXH_g_dispatch128.update(state, (const xxh_u8*)input, len);
}
#if defined (__cplusplus)
}
#endif
/*! @} */

85
extern/xxhash/xxh_x86dispatch.h vendored Normal file
View File

@ -0,0 +1,85 @@
/*
* xxHash - XXH3 Dispatcher for x86-based targets
* Copyright (C) 2020-2021 Yann Collet
*
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You can contact the author at:
* - xxHash homepage: https://www.xxhash.com
* - xxHash source repository: https://github.com/Cyan4973/xxHash
*/
#ifndef XXH_X86DISPATCH_H_13563687684
#define XXH_X86DISPATCH_H_13563687684
#include "xxhash.h" /* XXH64_hash_t, XXH3_state_t */
#if defined (__cplusplus)
extern "C" {
#endif
XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_dispatch(const void* input, size_t len);
XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed);
XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen);
XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len);
XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_dispatch(const void* input, size_t len);
XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSeed_dispatch(const void* input, size_t len, XXH64_hash_t seed);
XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecret_dispatch(const void* input, size_t len, const void* secret, size_t secretLen);
XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update_dispatch(XXH3_state_t* state, const void* input, size_t len);
#if defined (__cplusplus)
}
#endif
/* automatic replacement of XXH3 functions.
* can be disabled by setting XXH_DISPATCH_DISABLE_REPLACE */
#ifndef XXH_DISPATCH_DISABLE_REPLACE
# undef XXH3_64bits
# define XXH3_64bits XXH3_64bits_dispatch
# undef XXH3_64bits_withSeed
# define XXH3_64bits_withSeed XXH3_64bits_withSeed_dispatch
# undef XXH3_64bits_withSecret
# define XXH3_64bits_withSecret XXH3_64bits_withSecret_dispatch
# undef XXH3_64bits_update
# define XXH3_64bits_update XXH3_64bits_update_dispatch
# undef XXH128
# define XXH128 XXH3_128bits_withSeed_dispatch
# undef XXH3_128bits
# define XXH3_128bits XXH3_128bits_dispatch
# undef XXH3_128bits_withSeed
# define XXH3_128bits_withSeed XXH3_128bits_withSeed_dispatch
# undef XXH3_128bits_withSecret
# define XXH3_128bits_withSecret XXH3_128bits_withSecret_dispatch
# undef XXH3_128bits_update
# define XXH3_128bits_update XXH3_128bits_update_dispatch
#endif /* XXH_DISPATCH_DISABLE_REPLACE */
#endif /* XXH_X86DISPATCH_H_13563687684 */

43
extern/xxhash/xxhash.c vendored Normal file
View File

@ -0,0 +1,43 @@
/*
* xxHash - Extremely Fast Hash algorithm
* Copyright (C) 2012-2021 Yann Collet
*
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You can contact the author at:
* - xxHash homepage: https://www.xxhash.com
* - xxHash source repository: https://github.com/Cyan4973/xxHash
*/
/*
* xxhash.c instantiates functions defined in xxhash.h
*/
#define XXH_STATIC_LINKING_ONLY /* access advanced declarations */
#define XXH_IMPLEMENTATION /* access definitions */
#include "xxhash.h"

6074
extern/xxhash/xxhash.h vendored Normal file

File diff suppressed because it is too large Load Diff

4
extern/xxhash/xxhash_impl.c vendored Normal file
View File

@ -0,0 +1,4 @@
//#if defined(__x86_64__) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64)
//#include "xxh_x86dispatch.c"
//#endif
#include "xxhash.c"

5
extern/xxhash/xxhash_impl.h vendored Normal file
View File

@ -0,0 +1,5 @@
//#if defined(__x86_64__) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64)
//#include "xxh_x86dispatch.h"
//#else
#include "xxhash.h"
//#endif

84
include/aurora/aurora.h Normal file
View File

@ -0,0 +1,84 @@
#ifndef AURORA_AURORA_H
#define AURORA_AURORA_H
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include "stdint.h"
#include "stdbool.h"
#endif
typedef enum {
BACKEND_AUTO,
BACKEND_D3D12,
BACKEND_METAL,
BACKEND_VULKAN,
BACKEND_OPENGL,
BACKEND_OPENGLES,
BACKEND_WEBGPU,
BACKEND_NULL,
} AuroraBackend;
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARNING,
LOG_ERROR,
LOG_FATAL,
} AuroraLogLevel;
typedef struct {
uint32_t width;
uint32_t height;
uint32_t fb_width;
uint32_t fb_height;
float scale;
} AuroraWindowSize;
typedef struct AuroraEvent AuroraEvent;
typedef void (*AuroraLogCallback)(AuroraLogLevel level, const char* message, unsigned int len);
typedef void (*AuroraImGuiInitCallback)(const AuroraWindowSize* size);
typedef struct {
const char* appName;
const char* configPath;
AuroraBackend desiredBackend;
uint32_t msaa;
uint16_t maxTextureAnisotropy;
bool startFullscreen;
uint32_t windowWidth;
uint32_t windowHeight;
void* iconRGBA8;
uint32_t iconWidth;
uint32_t iconHeight;
AuroraLogCallback logCallback;
AuroraImGuiInitCallback imGuiInitCallback;
} AuroraConfig;
typedef struct {
AuroraBackend backend;
const char* configPath;
AuroraWindowSize windowSize;
} AuroraInfo;
AuroraInfo aurora_initialize(int argc, char* argv[], const AuroraConfig* config);
void aurora_shutdown();
const AuroraEvent* aurora_update();
bool aurora_begin_frame();
void aurora_end_frame();
#ifndef NDEBUG
#define AURORA_GFX_DEBUG_GROUPS
#endif
void push_debug_group(const char* label);
void pop_debug_group();
#ifdef __cplusplus
}
#endif
#endif

40
include/aurora/event.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef AURORA_EVENT_H
#define AURORA_EVENT_H
#include "aurora.h"
#include <SDL_events.h>
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include "stdint.h"
#endif
typedef enum {
AURORA_NONE,
AURORA_EXIT,
AURORA_SDL_EVENT,
AURORA_WINDOW_RESIZED,
AURORA_CONTROLLER_ADDED,
AURORA_CONTROLLER_REMOVED,
AURORA_PAUSED,
AURORA_UNPAUSED,
} AuroraEventType;
struct AuroraEvent {
AuroraEventType type;
union {
SDL_Event sdl;
AuroraWindowSize windowSize;
int32_t controller;
};
};
#ifdef __cplusplus
}
#endif
#endif

20
include/aurora/imgui.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef AURORA_IMGUI_H
#define AURORA_IMGUI_H
#include <imgui.h>
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#else
#include "stdint.h"
#endif
ImTextureID aurora_imgui_add_texture(uint32_t width, uint32_t height, const void* rgba8);
#ifdef __cplusplus
}
#endif
#endif

16
include/aurora/main.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef AURORA_MAIN_H
#define AURORA_MAIN_H
#ifdef __cplusplus
extern "C" {
#endif
int aurora_main(int argc, char* argv[]);
#ifdef __cplusplus
}
#endif
#define main aurora_main
#endif

254
include/aurora/math.hpp Normal file
View File

@ -0,0 +1,254 @@
#pragma once
#include <cstddef>
#include <cstdint>
#ifndef AURORA_VEC2_EXTRA
#define AURORA_VEC2_EXTRA
#endif
#ifndef AURORA_VEC3_EXTRA
#define AURORA_VEC3_EXTRA
#endif
#ifndef AURORA_VEC4_EXTRA
#define AURORA_VEC4_EXTRA
#endif
#ifndef AURORA_MAT4X4_EXTRA
#define AURORA_MAT4X4_EXTRA
#endif
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if __has_attribute(vector_size)
//#define USE_GCC_VECTOR_EXTENSIONS
#endif
namespace aurora {
template <typename T>
struct Vec2 {
T x{};
T y{};
constexpr Vec2() = default;
constexpr Vec2(T x, T y) : x(x), y(y) {}
AURORA_VEC2_EXTRA
#ifdef METAFORCE
constexpr Vec2(const zeus::CVector2f& vec) : x(vec.x()), y(vec.y()) {}
#endif
bool operator==(const Vec2& rhs) const { return x == rhs.x && y == rhs.y; }
};
template <typename T>
struct Vec3 {
T x{};
T y{};
T z{};
constexpr Vec3() = default;
constexpr Vec3(T x, T y, T z) : x(x), y(y), z(z) {}
AURORA_VEC3_EXTRA
#ifdef METAFORCE
constexpr Vec3(const zeus::CVector3f& vec) : x(vec.x()), y(vec.y()), z(vec.z()) {}
operator zeus::CVector3f() const { return {x, y, z}; }
#endif
bool operator==(const Vec3& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z; }
};
template <typename T>
struct Vec4 {
#ifdef USE_GCC_VECTOR_EXTENSIONS
typedef T Vt __attribute__((vector_size(sizeof(T) * 4)));
Vt m;
#else
using Vt = T[4];
Vt m;
#endif
constexpr Vec4() = default;
constexpr Vec4(Vt m) : m(m) {}
constexpr Vec4(T x, T y, T z, T w) : m{x, y, z, w} {}
// For Vec3 with padding
constexpr Vec4(T x, T y, T z) : m{x, y, z, {}} {}
// For Vec3 -> Vec4
constexpr Vec4(Vec3<T> v, T w) : m{v.x, v.y, v.z, w} {}
AURORA_VEC4_EXTRA
#ifdef METAFORCE
constexpr Vec4(const zeus::CVector4f& vec) : x(vec.x()), y(vec.y()), z(vec.z()), w(vec.w()) {}
constexpr Vec4(const zeus::CColor& color) : x(color.r()), y(color.g()), z(color.b()), w(color.a()) {}
#endif
inline Vec4& operator=(const Vec4& other) {
memcpy(&m, &other.m, sizeof(Vt));
return *this;
}
[[nodiscard]] inline T& x() { return m[0]; }
[[nodiscard]] inline T x() const { return m[0]; }
[[nodiscard]] inline T& y() { return m[1]; }
[[nodiscard]] inline T y() const { return m[1]; }
[[nodiscard]] inline T& z() { return m[2]; }
[[nodiscard]] inline T z() const { return m[2]; }
[[nodiscard]] inline T& w() { return m[3]; }
[[nodiscard]] inline T w() const { return m[3]; }
[[nodiscard]] inline T& operator[](size_t i) { return m[i]; }
[[nodiscard]] inline T operator[](size_t i) const { return m[i]; }
template <size_t x, size_t y, size_t z, size_t w>
[[nodiscard]] constexpr Vec4 shuffle() const {
static_assert(x < 4 && y < 4 && z < 4 && w < 4);
#if defined(USE_GCC_VECTOR_EXTENSIONS) && __has_builtin(__builtin_shuffle)
typedef int Vi __attribute__((vector_size(16)));
return __builtin_shuffle(m, Vi{x, y, z, w});
#else
return {m[x], m[y], m[z], m[w]};
#endif
}
bool operator==(const Vec4& rhs) const {
#if defined(USE_GCC_VECTOR_EXTENSIONS) && __has_builtin(__builtin_reduce_and)
return __builtin_reduce_and(m == rhs.m) != 0;
#else
return m[0] == rhs.m[0] && m[1] == rhs.m[1] && m[2] == rhs.m[2] && m[3] == rhs.m[3];
#endif
}
};
template <typename T>
[[nodiscard]] inline Vec4<T> operator+(const Vec4<T>& a, const Vec4<T>& b) {
#ifdef USE_GCC_VECTOR_EXTENSIONS
return a.m + b.m;
#else
return {a.m[0] + b.m[0], a.m[1] + b.m[1], a.m[2] + b.m[2], a.m[3] + b.m[3]};
#endif
}
template <typename T>
[[nodiscard]] inline Vec4<T> operator*(const Vec4<T>& a, const Vec4<T>& b) {
#ifdef USE_GCC_VECTOR_EXTENSIONS
return a.m * b.m;
#else
return {a.m[0] * b.m[0], a.m[1] * b.m[1], a.m[2] * b.m[2], a.m[3] * b.m[3]};
#endif
}
template <typename T>
struct Mat3x2 {
Vec2<T> m0{};
Vec2<T> m1{};
Vec2<T> m2{};
constexpr Mat3x2() = default;
constexpr Mat3x2(const Vec2<T>& m0, const Vec2<T>& m1, const Vec2<T>& m2) : m0(m0), m1(m1), m2(m2) {}
bool operator==(const Mat3x2& rhs) const { return m0 == rhs.m0 && m1 == rhs.m1 && m2 == rhs.m2; }
};
template <typename T>
struct Mat4x2 {
Vec2<T> m0{};
Vec2<T> m1{};
Vec2<T> m2{};
Vec2<T> m3{};
constexpr Mat4x2() = default;
constexpr Mat4x2(const Vec2<T>& m0, const Vec2<T>& m1, const Vec2<T>& m2, const Vec2<T>& m3)
: m0(m0), m1(m1), m2(m2), m3(m3) {}
inline Mat4x2 transpose() const {
return {
{m0.x, m2.x},
{m0.y, m2.y},
{m1.x, m3.x},
{m1.y, m3.y},
};
}
bool operator==(const Mat4x2& rhs) const { return m0 == rhs.m0 && m1 == rhs.m1 && m2 == rhs.m2 && m3 == rhs.m3; }
};
template <typename T>
struct Mat4x4;
template <typename T>
struct Mat3x4 {
Vec4<T> m0{};
Vec4<T> m1{};
Vec4<T> m2{};
constexpr Mat3x4() = default;
constexpr Mat3x4(const Vec4<T>& m0, const Vec4<T>& m1, const Vec4<T>& m2) : m0(m0), m1(m1), m2(m2) {}
inline Mat4x4<T> to4x4() const;
inline Mat4x4<T> toTransposed4x4() const;
};
static_assert(sizeof(Mat3x4<float>) == sizeof(float[3][4]));
template <typename T>
struct Mat4x4 {
Vec4<T> m0{};
Vec4<T> m1{};
Vec4<T> m2{};
Vec4<T> m3{};
constexpr Mat4x4() = default;
constexpr Mat4x4(const Vec4<T>& m0, const Vec4<T>& m1, const Vec4<T>& m2, const Vec4<T>& m3)
: m0(m0), m1(m1), m2(m2), m3(m3) {}
AURORA_MAT4X4_EXTRA
#ifdef METAFORCE
constexpr Mat4x4(const zeus::CMatrix4f& m) : m0(m[0]), m1(m[1]), m2(m[2]), m3(m[3]) {}
constexpr Mat4x4(const zeus::CTransform& m) : Mat4x4(m.toMatrix4f()) {}
#endif
[[nodiscard]] Mat4x4 transpose() const {
return {
{m0[0], m1[0], m2[0], m3[0]},
{m0[1], m1[1], m2[1], m3[1]},
{m0[2], m1[2], m2[2], m3[2]},
{m0[3], m1[3], m2[3], m3[3]},
};
}
inline Mat4x4& operator=(const Mat4x4& other) {
m0 = other.m0;
m1 = other.m1;
m2 = other.m2;
m3 = other.m3;
return *this;
}
inline Vec4<T>& operator[](size_t i) { return *(&m0 + i); }
inline const Vec4<T>& operator[](size_t i) const { return *(&m0 + i); }
bool operator==(const Mat4x4& rhs) const { return m0 == rhs.m0 && m1 == rhs.m1 && m2 == rhs.m2 && m3 == rhs.m3; }
};
static_assert(sizeof(Mat4x4<float>) == sizeof(float[4][4]));
template <typename T>
[[nodiscard]] inline Mat4x4<T> operator*(const Mat4x4<T>& a, const Mat4x4<T>& b) {
Mat4x4<T> out;
for (size_t i = 0; i < 4; ++i) {
*(&out.m0 + i) = a.m0 * b[i].template shuffle<0, 0, 0, 0>() + a.m1 * b[i].template shuffle<1, 1, 1, 1>() +
a.m2 * b[i].template shuffle<2, 2, 2, 2>() + a.m3 * b[i].template shuffle<3, 3, 3, 3>();
}
return out;
}
template <typename T>
[[nodiscard]] inline Mat4x4<T> Mat3x4<T>::to4x4() const {
return {
{m0.m[0], m0.m[1], m0.m[2], 0.f},
{m1.m[0], m1.m[1], m1.m[2], 0.f},
{m2.m[0], m2.m[1], m2.m[2], 0.f},
{m0.m[3], m1.m[3], m2.m[3], 1.f},
};
}
template <typename T>
[[nodiscard]] inline Mat4x4<T> Mat3x4<T>::toTransposed4x4() const {
return Mat4x4<T>{
m0,
m1,
m2,
{0.f, 0.f, 0.f, 1.f},
}
.transpose();
}
constexpr Mat4x4<float> Mat4x4_Identity{
Vec4<float>{1.f, 0.f, 0.f, 0.f},
Vec4<float>{0.f, 1.f, 0.f, 0.f},
Vec4<float>{0.f, 0.f, 1.f, 0.f},
Vec4<float>{0.f, 0.f, 0.f, 1.f},
};
} // namespace aurora

32
include/dolphin/gx.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef DOLPHIN_GX_H
#define DOLPHIN_GX_H
#ifdef __cplusplus
extern "C" {
#endif
#include <dolphin/gx/GXBump.h>
#include <dolphin/gx/GXCommandList.h>
#include <dolphin/gx/GXCull.h>
#include <dolphin/gx/GXDispList.h>
#include <dolphin/gx/GXDraw.h>
#include <dolphin/gx/GXExtra.h>
#include <dolphin/gx/GXFifo.h>
#include <dolphin/gx/GXFrameBuffer.h>
#include <dolphin/gx/GXGeometry.h>
#include <dolphin/gx/GXGet.h>
#include <dolphin/gx/GXLighting.h>
#include <dolphin/gx/GXManage.h>
#include <dolphin/gx/GXPerf.h>
#include <dolphin/gx/GXPixel.h>
#include <dolphin/gx/GXStruct.h>
#include <dolphin/gx/GXTev.h>
#include <dolphin/gx/GXTexture.h>
#include <dolphin/gx/GXTransform.h>
#include <dolphin/gx/GXVert.h>
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,28 @@
#ifndef DOLPHIN_GXBUMP_H
#define DOLPHIN_GXBUMP_H
#include <dolphin/gx/GXEnum.h>
#ifdef __cplusplus
extern "C" {
#endif
void GXSetTevDirect(GXTevStageID tev_stage);
void GXSetNumIndStages(u8 nIndStages);
#ifdef TARGET_PC
void GXSetIndTexMtx(GXIndTexMtxID mtx_sel, const void* offset, s8 scale_exp);
#else
void GXSetIndTexMtx(GXIndTexMtxID mtx_sel, f32 offset[2][3], s8 scale_exp);
#endif
void GXSetIndTexOrder(GXIndTexStageID ind_stage, GXTexCoordID tex_coord, GXTexMapID tex_map);
void GXSetTevIndirect(GXTevStageID tev_stage, GXIndTexStageID ind_stage, GXIndTexFormat format,
GXIndTexBiasSel bias_sel, GXIndTexMtxID matrix_sel, GXIndTexWrap wrap_s, GXIndTexWrap wrap_t,
GXBool add_prev, GXBool ind_lod, GXIndTexAlphaSel alpha_sel);
void GXSetTevIndWarp(GXTevStageID tev_stage, GXIndTexStageID ind_stage, GXBool signed_offsets, GXBool replace_mode,
GXIndTexMtxID matrix_sel);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,35 @@
#ifndef DOLPHIN_GXCOMMANDLIST_H
#define DOLPHIN_GXCOMMANDLIST_H
#ifdef __cplusplus
extern "C" {
#endif
#define GX_NOP 0x00
#define GX_DRAW_QUADS 0x80
#define GX_DRAW_TRIANGLES 0x90
#define GX_DRAW_TRIANGLE_STRIP 0x98
#define GX_DRAW_TRIANGLE_FAN 0xA0
#define GX_DRAW_LINES 0xA8
#define GX_DRAW_LINE_STRIP 0xB0
#define GX_DRAW_POINTS 0xB8
#define GX_LOAD_BP_REG 0x61
#define GX_LOAD_CP_REG 0x08
#define GX_LOAD_XF_REG 0x10
#define GX_LOAD_INDX_A 0x20
#define GX_LOAD_INDX_B 0x28
#define GX_LOAD_INDX_C 0x30
#define GX_LOAD_INDX_D 0x38
#define GX_CMD_CALL_DL 0x40
#define GX_CMD_INVL_VC 0x48
#define GX_OPCODE_MASK 0xF8
#define GX_VAT_MASK 0x07
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,18 @@
#ifndef DOLPHIN_GXCULL_H
#define DOLPHIN_GXCULL_H
#include <dolphin/types.h>
#ifdef __cplusplus
extern "C" {
#endif
void GXSetScissor(u32 left, u32 top, u32 wd, u32 ht);
void GXSetCullMode(GXCullMode mode);
void GXSetCoPlanar(GXBool enable);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,18 @@
#ifndef DOLPHIN_GXDISPLIST_H
#define DOLPHIN_GXDISPLIST_H
#include <dolphin/types.h>
#ifdef __cplusplus
extern "C" {
#endif
void GXBeginDisplayList(void* list, u32 size);
u32 GXEndDisplayList(void);
void GXCallDisplayList(const void* list, u32 nbytes);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,16 @@
#ifndef DOLPHIN_GXDRAW_H
#define DOLPHIN_GXDRAW_H
#include <dolphin/types.h>
#ifdef __cplusplus
extern "C" {
#endif
void GXDrawSphere(u8 numMajor, u8 numMinor);
#ifdef __cplusplus
}
#endif
#endif

758
include/dolphin/gx/GXEnum.h Normal file
View File

@ -0,0 +1,758 @@
#ifndef DOLPHIN_GXENUM_H
#define DOLPHIN_GXENUM_H
#include <dolphin/types.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef TARGET_PC
#include <stdbool.h>
typedef bool GXBool;
#else
typedef u8 GXBool;
#endif
#define GX_FALSE ((GXBool)0)
#define GX_TRUE ((GXBool)1)
#define GX_ENABLE ((GXBool)1)
#define GX_DISABLE ((GXBool)0)
typedef enum {
GX_PERSPECTIVE,
GX_ORTHOGRAPHIC,
} GXProjectionType;
typedef enum {
GX_NEVER,
GX_LESS,
GX_EQUAL,
GX_LEQUAL,
GX_GREATER,
GX_NEQUAL,
GX_GEQUAL,
GX_ALWAYS,
} GXCompare;
typedef enum {
GX_AOP_AND,
GX_AOP_OR,
GX_AOP_XOR,
GX_AOP_XNOR,
GX_MAX_ALPHAOP,
} GXAlphaOp;
typedef enum {
GX_ZC_LINEAR,
GX_ZC_NEAR,
GX_ZC_MID,
GX_ZC_FAR,
} GXZFmt16;
typedef enum {
GX_GM_1_0,
GX_GM_1_7,
GX_GM_2_2,
} GXGamma;
typedef enum {
GX_PF_RGB8_Z24,
GX_PF_RGBA6_Z24,
GX_PF_RGB565_Z16,
GX_PF_Z24,
GX_PF_Y8,
GX_PF_U8,
GX_PF_V8,
GX_PF_YUV420,
} GXPixelFmt;
typedef enum {
GX_QUADS = 0x80,
GX_TRIANGLES = 0x90,
GX_TRIANGLESTRIP = 0x98,
GX_TRIANGLEFAN = 0xA0,
GX_LINES = 0xA8,
GX_LINESTRIP = 0xB0,
GX_POINTS = 0xB8,
} GXPrimitive;
typedef enum {
GX_VTXFMT0,
GX_VTXFMT1,
GX_VTXFMT2,
GX_VTXFMT3,
GX_VTXFMT4,
GX_VTXFMT5,
GX_VTXFMT6,
GX_VTXFMT7,
GX_MAX_VTXFMT,
} GXVtxFmt;
typedef enum {
GX_VA_PNMTXIDX,
GX_VA_TEX0MTXIDX,
GX_VA_TEX1MTXIDX,
GX_VA_TEX2MTXIDX,
GX_VA_TEX3MTXIDX,
GX_VA_TEX4MTXIDX,
GX_VA_TEX5MTXIDX,
GX_VA_TEX6MTXIDX,
GX_VA_TEX7MTXIDX,
GX_VA_POS,
GX_VA_NRM,
GX_VA_CLR0,
GX_VA_CLR1,
GX_VA_TEX0,
GX_VA_TEX1,
GX_VA_TEX2,
GX_VA_TEX3,
GX_VA_TEX4,
GX_VA_TEX5,
GX_VA_TEX6,
GX_VA_TEX7,
GX_POS_MTX_ARRAY,
GX_NRM_MTX_ARRAY,
GX_TEX_MTX_ARRAY,
GX_LIGHT_ARRAY,
GX_VA_NBT,
GX_VA_MAX_ATTR,
GX_VA_NULL = 0xFF,
} GXAttr;
typedef enum {
GX_NONE,
GX_DIRECT,
GX_INDEX8,
GX_INDEX16,
} GXAttrType;
#define _GX_TF_CTF 0x20
#define _GX_TF_ZTF 0x10
typedef enum {
GX_TF_I4 = 0x0,
GX_TF_I8 = 0x1,
GX_TF_IA4 = 0x2,
GX_TF_IA8 = 0x3,
GX_TF_RGB565 = 0x4,
GX_TF_RGB5A3 = 0x5,
GX_TF_RGBA8 = 0x6,
GX_TF_CMPR = 0xE,
GX_CTF_R4 = 0x0 | _GX_TF_CTF,
GX_CTF_RA4 = 0x2 | _GX_TF_CTF,
GX_CTF_RA8 = 0x3 | _GX_TF_CTF,
GX_CTF_YUVA8 = 0x6 | _GX_TF_CTF,
GX_CTF_A8 = 0x7 | _GX_TF_CTF,
GX_CTF_R8 = 0x8 | _GX_TF_CTF,
GX_CTF_G8 = 0x9 | _GX_TF_CTF,
GX_CTF_B8 = 0xA | _GX_TF_CTF,
GX_CTF_RG8 = 0xB | _GX_TF_CTF,
GX_CTF_GB8 = 0xC | _GX_TF_CTF,
GX_TF_Z8 = 0x1 | _GX_TF_ZTF,
GX_TF_Z16 = 0x3 | _GX_TF_ZTF,
GX_TF_Z24X8 = 0x6 | _GX_TF_ZTF,
GX_CTF_Z4 = 0x0 | _GX_TF_ZTF | _GX_TF_CTF,
GX_CTF_Z8M = 0x9 | _GX_TF_ZTF | _GX_TF_CTF,
GX_CTF_Z8L = 0xA | _GX_TF_ZTF | _GX_TF_CTF,
GX_CTF_Z16L = 0xC | _GX_TF_ZTF | _GX_TF_CTF,
GX_TF_A8 = GX_CTF_A8,
} GXTexFmt;
typedef enum {
GX_TF_C4 = 0x8,
GX_TF_C8 = 0x9,
GX_TF_C14X2 = 0xa,
} GXCITexFmt;
typedef enum {
GX_CLAMP,
GX_REPEAT,
GX_MIRROR,
GX_MAX_TEXWRAPMODE,
} GXTexWrapMode;
typedef enum {
GX_NEAR,
GX_LINEAR,
GX_NEAR_MIP_NEAR,
GX_LIN_MIP_NEAR,
GX_NEAR_MIP_LIN,
GX_LIN_MIP_LIN,
} GXTexFilter;
typedef enum {
GX_ANISO_1,
GX_ANISO_2,
GX_ANISO_4,
GX_MAX_ANISOTROPY,
} GXAnisotropy;
typedef enum {
GX_TEXMAP0,
GX_TEXMAP1,
GX_TEXMAP2,
GX_TEXMAP3,
GX_TEXMAP4,
GX_TEXMAP5,
GX_TEXMAP6,
GX_TEXMAP7,
GX_MAX_TEXMAP,
GX_TEXMAP_NULL = 0xFF,
GX_TEX_DISABLE = 0x100,
} GXTexMapID;
typedef enum {
GX_TEXCOORD0,
GX_TEXCOORD1,
GX_TEXCOORD2,
GX_TEXCOORD3,
GX_TEXCOORD4,
GX_TEXCOORD5,
GX_TEXCOORD6,
GX_TEXCOORD7,
GX_MAX_TEXCOORD,
GX_TEXCOORD_NULL = 0xFF,
} GXTexCoordID;
typedef enum {
GX_TEVSTAGE0,
GX_TEVSTAGE1,
GX_TEVSTAGE2,
GX_TEVSTAGE3,
GX_TEVSTAGE4,
GX_TEVSTAGE5,
GX_TEVSTAGE6,
GX_TEVSTAGE7,
GX_TEVSTAGE8,
GX_TEVSTAGE9,
GX_TEVSTAGE10,
GX_TEVSTAGE11,
GX_TEVSTAGE12,
GX_TEVSTAGE13,
GX_TEVSTAGE14,
GX_TEVSTAGE15,
GX_MAX_TEVSTAGE,
} GXTevStageID;
typedef enum {
GX_MODULATE,
GX_DECAL,
GX_BLEND,
GX_REPLACE,
GX_PASSCLR,
} GXTevMode;
typedef enum {
GX_MTX3x4,
GX_MTX2x4,
} GXTexMtxType;
typedef enum {
GX_TG_MTX3x4,
GX_TG_MTX2x4,
GX_TG_BUMP0,
GX_TG_BUMP1,
GX_TG_BUMP2,
GX_TG_BUMP3,
GX_TG_BUMP4,
GX_TG_BUMP5,
GX_TG_BUMP6,
GX_TG_BUMP7,
GX_TG_SRTG,
} GXTexGenType;
typedef enum {
GX_PNMTX0 = 0,
GX_PNMTX1 = 3,
GX_PNMTX2 = 6,
GX_PNMTX3 = 9,
GX_PNMTX4 = 12,
GX_PNMTX5 = 15,
GX_PNMTX6 = 18,
GX_PNMTX7 = 21,
GX_PNMTX8 = 24,
GX_PNMTX9 = 27,
} GXPosNrmMtx;
typedef enum {
GX_TEXMTX0 = 30,
GX_TEXMTX1 = 33,
GX_TEXMTX2 = 36,
GX_TEXMTX3 = 39,
GX_TEXMTX4 = 42,
GX_TEXMTX5 = 45,
GX_TEXMTX6 = 48,
GX_TEXMTX7 = 51,
GX_TEXMTX8 = 54,
GX_TEXMTX9 = 57,
GX_IDENTITY = 60,
} GXTexMtx;
typedef enum {
GX_COLOR0,
GX_COLOR1,
GX_ALPHA0,
GX_ALPHA1,
GX_COLOR0A0,
GX_COLOR1A1,
GX_COLOR_ZERO,
GX_ALPHA_BUMP,
GX_ALPHA_BUMPN,
GX_COLOR_NULL = 0xFF,
} GXChannelID;
typedef enum {
GX_TG_POS,
GX_TG_NRM,
GX_TG_BINRM,
GX_TG_TANGENT,
GX_TG_TEX0,
GX_TG_TEX1,
GX_TG_TEX2,
GX_TG_TEX3,
GX_TG_TEX4,
GX_TG_TEX5,
GX_TG_TEX6,
GX_TG_TEX7,
GX_TG_TEXCOORD0,
GX_TG_TEXCOORD1,
GX_TG_TEXCOORD2,
GX_TG_TEXCOORD3,
GX_TG_TEXCOORD4,
GX_TG_TEXCOORD5,
GX_TG_TEXCOORD6,
GX_TG_COLOR0,
GX_TG_COLOR1,
GX_MAX_TEXGENSRC,
} GXTexGenSrc;
typedef enum {
GX_BM_NONE,
GX_BM_BLEND,
GX_BM_LOGIC,
GX_BM_SUBTRACT,
GX_MAX_BLENDMODE,
} GXBlendMode;
typedef enum {
GX_BL_ZERO,
GX_BL_ONE,
GX_BL_SRCCLR,
GX_BL_INVSRCCLR,
GX_BL_SRCALPHA,
GX_BL_INVSRCALPHA,
GX_BL_DSTALPHA,
GX_BL_INVDSTALPHA,
GX_BL_DSTCLR = GX_BL_SRCCLR,
GX_BL_INVDSTCLR = GX_BL_INVSRCCLR,
} GXBlendFactor;
typedef enum {
GX_LO_CLEAR,
GX_LO_AND,
GX_LO_REVAND,
GX_LO_COPY,
GX_LO_INVAND,
GX_LO_NOOP,
GX_LO_XOR,
GX_LO_OR,
GX_LO_NOR,
GX_LO_EQUIV,
GX_LO_INV,
GX_LO_REVOR,
GX_LO_INVCOPY,
GX_LO_INVOR,
GX_LO_NAND,
GX_LO_SET,
} GXLogicOp;
typedef enum {
GX_POS_XY = 0,
GX_POS_XYZ = 1,
GX_NRM_XYZ = 0,
GX_NRM_NBT = 1,
GX_NRM_NBT3 = 2,
GX_CLR_RGB = 0,
GX_CLR_RGBA = 1,
GX_TEX_S = 0,
GX_TEX_ST = 1,
} GXCompCnt;
typedef enum {
GX_U8 = 0,
GX_S8 = 1,
GX_U16 = 2,
GX_S16 = 3,
GX_F32 = 4,
GX_RGB565 = 0,
GX_RGB8 = 1,
GX_RGBX8 = 2,
GX_RGBA4 = 3,
GX_RGBA6 = 4,
GX_RGBA8 = 5,
} GXCompType;
typedef enum {
GX_PTTEXMTX0 = 64,
GX_PTTEXMTX1 = 67,
GX_PTTEXMTX2 = 70,
GX_PTTEXMTX3 = 73,
GX_PTTEXMTX4 = 76,
GX_PTTEXMTX5 = 79,
GX_PTTEXMTX6 = 82,
GX_PTTEXMTX7 = 85,
GX_PTTEXMTX8 = 88,
GX_PTTEXMTX9 = 91,
GX_PTTEXMTX10 = 94,
GX_PTTEXMTX11 = 97,
GX_PTTEXMTX12 = 100,
GX_PTTEXMTX13 = 103,
GX_PTTEXMTX14 = 106,
GX_PTTEXMTX15 = 109,
GX_PTTEXMTX16 = 112,
GX_PTTEXMTX17 = 115,
GX_PTTEXMTX18 = 118,
GX_PTTEXMTX19 = 121,
GX_PTIDENTITY = 125,
} GXPTTexMtx;
typedef enum {
GX_TEVPREV,
GX_TEVREG0,
GX_TEVREG1,
GX_TEVREG2,
GX_MAX_TEVREG,
} GXTevRegID;
typedef enum {
GX_DF_NONE,
GX_DF_SIGN,
GX_DF_CLAMP,
} GXDiffuseFn;
typedef enum {
GX_SRC_REG,
GX_SRC_VTX,
} GXColorSrc;
typedef enum {
GX_AF_SPEC,
GX_AF_SPOT,
GX_AF_NONE,
} GXAttnFn;
typedef enum {
GX_LIGHT0 = 0x001,
GX_LIGHT1 = 0x002,
GX_LIGHT2 = 0x004,
GX_LIGHT3 = 0x008,
GX_LIGHT4 = 0x010,
GX_LIGHT5 = 0x020,
GX_LIGHT6 = 0x040,
GX_LIGHT7 = 0x080,
GX_MAX_LIGHT = 0x100,
GX_LIGHT_NULL = 0,
} GXLightID;
typedef enum {
GX_TO_ZERO,
GX_TO_SIXTEENTH,
GX_TO_EIGHTH,
GX_TO_FOURTH,
GX_TO_HALF,
GX_TO_ONE,
GX_MAX_TEXOFFSET,
} GXTexOffset;
typedef enum {
GX_SP_OFF,
GX_SP_FLAT,
GX_SP_COS,
GX_SP_COS2,
GX_SP_SHARP,
GX_SP_RING1,
GX_SP_RING2,
} GXSpotFn;
typedef enum {
GX_DA_OFF,
GX_DA_GENTLE,
GX_DA_MEDIUM,
GX_DA_STEEP,
} GXDistAttnFn;
typedef enum {
GX_CULL_NONE,
GX_CULL_FRONT,
GX_CULL_BACK,
GX_CULL_ALL
} GXCullMode;
typedef enum { GX_TEV_SWAP0 = 0, GX_TEV_SWAP1, GX_TEV_SWAP2, GX_TEV_SWAP3, GX_MAX_TEVSWAP } GXTevSwapSel;
typedef enum { GX_CH_RED = 0, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA } GXTevColorChan;
typedef enum _GXFogType {
GX_FOG_NONE = 0,
GX_FOG_PERSP_LIN = 2,
GX_FOG_PERSP_EXP = 4,
GX_FOG_PERSP_EXP2 = 5,
GX_FOG_PERSP_REVEXP = 6,
GX_FOG_PERSP_REVEXP2 = 7,
GX_FOG_ORTHO_LIN = 10,
GX_FOG_ORTHO_EXP = 12,
GX_FOG_ORTHO_EXP2 = 13,
GX_FOG_ORTHO_REVEXP = 14,
GX_FOG_ORTHO_REVEXP2 = 15,
GX_FOG_LIN = GX_FOG_PERSP_LIN,
GX_FOG_EXP = GX_FOG_PERSP_EXP,
GX_FOG_EXP2 = GX_FOG_PERSP_EXP2,
GX_FOG_REVEXP = GX_FOG_PERSP_REVEXP,
GX_FOG_REVEXP2 = GX_FOG_PERSP_REVEXP2,
} GXFogType;
typedef enum {
GX_CC_CPREV,
GX_CC_APREV,
GX_CC_C0,
GX_CC_A0,
GX_CC_C1,
GX_CC_A1,
GX_CC_C2,
GX_CC_A2,
GX_CC_TEXC,
GX_CC_TEXA,
GX_CC_RASC,
GX_CC_RASA,
GX_CC_ONE,
GX_CC_HALF,
GX_CC_KONST,
GX_CC_ZERO
} GXTevColorArg;
typedef enum {
GX_CA_APREV,
GX_CA_A0,
GX_CA_A1,
GX_CA_A2,
GX_CA_TEXA,
GX_CA_RASA,
GX_CA_KONST,
GX_CA_ZERO
} GXTevAlphaArg;
typedef enum {
GX_TEV_ADD = 0,
GX_TEV_SUB = 1,
GX_TEV_COMP_R8_GT = 8,
GX_TEV_COMP_R8_EQ = 9,
GX_TEV_COMP_GR16_GT = 10,
GX_TEV_COMP_GR16_EQ = 11,
GX_TEV_COMP_BGR24_GT = 12,
GX_TEV_COMP_BGR24_EQ = 13,
GX_TEV_COMP_RGB8_GT = 14,
GX_TEV_COMP_RGB8_EQ = 15,
GX_TEV_COMP_A8_GT = GX_TEV_COMP_RGB8_GT,
GX_TEV_COMP_A8_EQ = GX_TEV_COMP_RGB8_EQ
} GXTevOp;
typedef enum { GX_TB_ZERO, GX_TB_ADDHALF, GX_TB_SUBHALF, GX_MAX_TEVBIAS } GXTevBias;
typedef enum { GX_CS_SCALE_1, GX_CS_SCALE_2, GX_CS_SCALE_4, GX_CS_DIVIDE_2, GX_MAX_TEVSCALE } GXTevScale;
typedef enum {
GX_TEV_KCSEL_8_8 = 0x00,
GX_TEV_KCSEL_7_8 = 0x01,
GX_TEV_KCSEL_6_8 = 0x02,
GX_TEV_KCSEL_5_8 = 0x03,
GX_TEV_KCSEL_4_8 = 0x04,
GX_TEV_KCSEL_3_8 = 0x05,
GX_TEV_KCSEL_2_8 = 0x06,
GX_TEV_KCSEL_1_8 = 0x07,
GX_TEV_KCSEL_1 = GX_TEV_KCSEL_8_8,
GX_TEV_KCSEL_3_4 = GX_TEV_KCSEL_6_8,
GX_TEV_KCSEL_1_2 = GX_TEV_KCSEL_4_8,
GX_TEV_KCSEL_1_4 = GX_TEV_KCSEL_2_8,
GX_TEV_KCSEL_K0 = 0x0C,
GX_TEV_KCSEL_K1 = 0x0D,
GX_TEV_KCSEL_K2 = 0x0E,
GX_TEV_KCSEL_K3 = 0x0F,
GX_TEV_KCSEL_K0_R = 0x10,
GX_TEV_KCSEL_K1_R = 0x11,
GX_TEV_KCSEL_K2_R = 0x12,
GX_TEV_KCSEL_K3_R = 0x13,
GX_TEV_KCSEL_K0_G = 0x14,
GX_TEV_KCSEL_K1_G = 0x15,
GX_TEV_KCSEL_K2_G = 0x16,
GX_TEV_KCSEL_K3_G = 0x17,
GX_TEV_KCSEL_K0_B = 0x18,
GX_TEV_KCSEL_K1_B = 0x19,
GX_TEV_KCSEL_K2_B = 0x1A,
GX_TEV_KCSEL_K3_B = 0x1B,
GX_TEV_KCSEL_K0_A = 0x1C,
GX_TEV_KCSEL_K1_A = 0x1D,
GX_TEV_KCSEL_K2_A = 0x1E,
GX_TEV_KCSEL_K3_A = 0x1F
} GXTevKColorSel;
typedef enum {
GX_TEV_KASEL_8_8 = 0x00,
GX_TEV_KASEL_7_8 = 0x01,
GX_TEV_KASEL_6_8 = 0x02,
GX_TEV_KASEL_5_8 = 0x03,
GX_TEV_KASEL_4_8 = 0x04,
GX_TEV_KASEL_3_8 = 0x05,
GX_TEV_KASEL_2_8 = 0x06,
GX_TEV_KASEL_1_8 = 0x07,
GX_TEV_KASEL_1 = GX_TEV_KASEL_8_8,
GX_TEV_KASEL_3_4 = GX_TEV_KASEL_6_8,
GX_TEV_KASEL_1_2 = GX_TEV_KASEL_4_8,
GX_TEV_KASEL_1_4 = GX_TEV_KASEL_2_8,
GX_TEV_KASEL_K0_R = 0x10,
GX_TEV_KASEL_K1_R = 0x11,
GX_TEV_KASEL_K2_R = 0x12,
GX_TEV_KASEL_K3_R = 0x13,
GX_TEV_KASEL_K0_G = 0x14,
GX_TEV_KASEL_K1_G = 0x15,
GX_TEV_KASEL_K2_G = 0x16,
GX_TEV_KASEL_K3_G = 0x17,
GX_TEV_KASEL_K0_B = 0x18,
GX_TEV_KASEL_K1_B = 0x19,
GX_TEV_KASEL_K2_B = 0x1A,
GX_TEV_KASEL_K3_B = 0x1B,
GX_TEV_KASEL_K0_A = 0x1C,
GX_TEV_KASEL_K1_A = 0x1D,
GX_TEV_KASEL_K2_A = 0x1E,
GX_TEV_KASEL_K3_A = 0x1F
} GXTevKAlphaSel;
typedef enum { GX_KCOLOR0 = 0, GX_KCOLOR1, GX_KCOLOR2, GX_KCOLOR3, GX_MAX_KCOLOR } GXTevKColorID;
typedef enum {
GX_ZT_DISABLE,
GX_ZT_ADD,
GX_ZT_REPLACE,
GX_MAX_ZTEXOP,
} GXZTexOp;
typedef enum {
GX_ITF_8,
GX_ITF_5,
GX_ITF_4,
GX_ITF_3,
GX_MAX_ITFORMAT,
} GXIndTexFormat;
typedef enum {
GX_ITB_NONE,
GX_ITB_S,
GX_ITB_T,
GX_ITB_ST,
GX_ITB_U,
GX_ITB_SU,
GX_ITB_TU,
GX_ITB_STU,
GX_MAX_ITBIAS,
} GXIndTexBiasSel;
typedef enum {
GX_ITBA_OFF,
GX_ITBA_S,
GX_ITBA_T,
GX_ITBA_U,
GX_MAX_ITBALPHA,
} GXIndTexAlphaSel;
typedef enum {
GX_ITM_OFF,
GX_ITM_0,
GX_ITM_1,
GX_ITM_2,
GX_ITM_S0 = 5,
GX_ITM_S1,
GX_ITM_S2,
GX_ITM_T0 = 9,
GX_ITM_T1,
GX_ITM_T2,
} GXIndTexMtxID;
typedef enum {
GX_ITW_OFF,
GX_ITW_256,
GX_ITW_128,
GX_ITW_64,
GX_ITW_32,
GX_ITW_16,
GX_ITW_0,
GX_MAX_ITWRAP,
} GXIndTexWrap;
typedef enum {
GX_INDTEXSTAGE0,
GX_INDTEXSTAGE1,
GX_INDTEXSTAGE2,
GX_INDTEXSTAGE3,
GX_MAX_INDTEXSTAGE,
} GXIndTexStageID;
typedef enum {
GX_ITS_1,
GX_ITS_2,
GX_ITS_4,
GX_ITS_8,
GX_ITS_16,
GX_ITS_32,
GX_ITS_64,
GX_ITS_128,
GX_ITS_256,
GX_MAX_ITSCALE,
} GXIndTexScale;
typedef enum {
GX_CLIP_ENABLE = 0,
GX_CLIP_DISABLE = 1,
} GXClipMode;
typedef enum {
GX_TLUT0 = 0,
GX_TLUT1 = 1,
GX_TLUT2 = 2,
GX_TLUT3 = 3,
GX_TLUT4 = 4,
GX_TLUT5 = 5,
GX_TLUT6 = 6,
GX_TLUT7 = 7,
GX_TLUT8 = 8,
GX_TLUT9 = 9,
GX_TLUT10 = 10,
GX_TLUT11 = 11,
GX_TLUT12 = 12,
GX_TLUT13 = 13,
GX_TLUT14 = 14,
GX_TLUT15 = 15,
GX_BIGTLUT0 = 16,
GX_BIGTLUT1 = 17,
GX_BIGTLUT2 = 18,
GX_BIGTLUT3 = 19,
} GXTlut;
typedef enum {
GX_TL_IA8,
GX_TL_RGB565,
GX_TL_RGB5A3,
GX_MAX_TLUTFMT,
} GXTlutFmt;
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,33 @@
#ifndef DOLPHIN_GXEXTRA_H
#define DOLPHIN_GXEXTRA_H
// Extra types for PC
#ifdef TARGET_PC
#include <dolphin/gx/GXStruct.h>
#include <dolphin/types.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
float r;
float g;
float b;
float a;
} GXColorF32;
typedef enum {
GX_TF_R8_PC = 0x60,
GX_TF_RGBA8_PC = 0x61,
} GXPCTexFmt;
void GXDestroyTexObj(GXTexObj* obj);
void GXColor4f32(float r, float g, float b, float a);
#ifdef __cplusplus
}
#endif
#endif
#endif

View File

@ -0,0 +1,31 @@
#ifndef DOLPHIN_GXFIFO_H
#define DOLPHIN_GXFIFO_H
#include <dolphin/gx/GXEnum.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
u8 pad[128];
} GXFifoObj;
void GXInitFifoBase(GXFifoObj* fifo, void* base, u32 size);
void GXInitFifoPtrs(GXFifoObj* fifo, void* readPtr, void* writePtr);
void GXGetFifoPtrs(GXFifoObj* fifo, void** readPtr, void** writePtr);
GXFifoObj* GXGetCPUFifo(void);
GXFifoObj* GXGetGPFifo(void);
void GXSetCPUFifo(GXFifoObj* fifo);
void GXSetGPFifo(GXFifoObj* fifo);
void GXSaveCPUFifo(GXFifoObj* fifo);
void GXGetFifoStatus(GXFifoObj* fifo, GXBool* overhi, GXBool* underlow, u32* fifoCount, GXBool* cpu_write,
GXBool* gp_read, GXBool* fifowrap);
void GXGetGPStatus(GXBool* overhi, GXBool* underlow, GXBool* readIdle, GXBool* cmdIdle, GXBool* brkpt);
void GXInitFifoLimits(GXFifoObj* fifo, u32 hiWaterMark, u32 loWaterMark);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,30 @@
#ifndef DOLPHIN_GXFRAMEBUFFER_H
#define DOLPHIN_GXFRAMEBUFFER_H
#include <dolphin/gx/GXEnum.h>
#include <dolphin/gx/GXStruct.h>
#ifdef __cplusplus
extern "C" {
#endif
#define GX_MAX_Z24 0x00FFFFFF
void GXSetCopyClear(GXColor clear_clr, u32 clear_z);
void GXAdjustForOverscan(GXRenderModeObj* rmin, GXRenderModeObj* rmout, u16 hor, u16 ver);
void GXCopyDisp(void* dest, GXBool clear);
void GXSetDispCopyGamma(GXGamma gamma);
void GXSetDispCopySrc(u16 left, u16 top, u16 wd, u16 ht);
void GXSetDispCopyDst(u16 wd, u16 ht);
u32 GXSetDispCopyYScale(f32 vscale);
void GXSetCopyFilter(GXBool aa, u8 sample_pattern[12][2], GXBool vf, u8 vfilter[7]);
void GXSetPixelFmt(GXPixelFmt pix_fmt, GXZFmt16 z_fmt);
void GXSetTexCopySrc(u16 left, u16 top, u16 wd, u16 ht);
void GXSetTexCopyDst(u16 wd, u16 ht, GXTexFmt fmt, GXBool mipmap);
void GXCopyTex(void* dest, GXBool clear);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,35 @@
#ifndef DOLPHIN_GXGEOMETRY_H
#define DOLPHIN_GXGEOMETRY_H
#include <dolphin/gx/GXEnum.h>
#ifdef __cplusplus
extern "C" {
#endif
void GXSetVtxDesc(GXAttr attr, GXAttrType type);
void GXSetVtxDescv(GXVtxDescList* list);
void GXClearVtxDesc(void);
void GXSetVtxAttrFmt(GXVtxFmt vtxfmt, GXAttr attr, GXCompCnt cnt, GXCompType type, u8 frac);
void GXSetNumTexGens(u8 nTexGens);
void GXBegin(GXPrimitive type, GXVtxFmt vtxfmt, u16 nverts);
void GXSetTexCoordGen2(GXTexCoordID dst_coord, GXTexGenType func, GXTexGenSrc src_param, u32 mtx, GXBool normalize,
u32 postmtx);
void GXSetLineWidth(u8 width, GXTexOffset texOffsets);
void GXSetPointSize(u8 pointSize, GXTexOffset texOffsets);
void GXEnableTexOffsets(GXTexCoordID coord, GXBool line_enable, GXBool point_enable);
#ifdef TARGET_PC
void GXSetArray(GXAttr attr, const void* data, u32 size, u8 stride);
#else
void GXSetArray(GXAttr attr, const void* data, u8 stride);
#endif
static inline void GXSetTexCoordGen(GXTexCoordID dst_coord, GXTexGenType func, GXTexGenSrc src_param, u32 mtx) {
GXSetTexCoordGen2(dst_coord, func, src_param, mtx, GX_FALSE, GX_PTIDENTITY);
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,27 @@
#ifndef DOLPHIN_GXGET_H
#define DOLPHIN_GXGET_H
#include <dolphin/gx/GXEnum.h>
#include <dolphin/gx/GXStruct.h>
#ifdef __cplusplus
extern "C" {
#endif
GXBool GXGetTexObjMipMap(GXTexObj* tex_obj);
GXTexFmt GXGetTexObjFmt(GXTexObj* tex_obj);
u16 GXGetTexObjHeight(GXTexObj* tex_obj);
u16 GXGetTexObjWidth(GXTexObj* tex_obj);
GXTexWrapMode GXGetTexObjWrapS(GXTexObj* tex_obj);
GXTexWrapMode GXGetTexObjWrapT(GXTexObj* tex_obj);
void* GXGetTexObjData(GXTexObj* tex_obj);
void GXGetProjectionv(f32* p);
void GXGetLightPos(GXLightObj* lt_obj, f32* x, f32* y, f32* z);
void GXGetLightColor(GXLightObj* lt_obj, GXColor* color);
void GXGetVtxAttrFmt(GXVtxFmt idx, GXAttr attr, GXCompCnt* compCnt, GXCompType* compType, u8* shift);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,31 @@
#ifndef DOLPHIN_GXLIGHTING_H
#define DOLPHIN_GXLIGHTING_H
#include <dolphin/gx/GXEnum.h>
#include <dolphin/gx/GXStruct.h>
#ifdef __cplusplus
extern "C" {
#endif
void GXSetNumChans(u8 nChans);
void GXSetChanCtrl(GXChannelID chan, GXBool enable, GXColorSrc amb_src, GXColorSrc mat_src, u32 light_mask,
GXDiffuseFn diff_fn, GXAttnFn attn_fn);
void GXSetChanAmbColor(GXChannelID chan, GXColor amb_color);
void GXSetChanMatColor(GXChannelID chan, GXColor mat_color);
void GXInitLightSpot(GXLightObj* lt_obj, f32 cutoff, GXSpotFn spot_func);
void GXInitLightDistAttn(GXLightObj* lt_obj, f32 ref_distance, f32 ref_brightness, GXDistAttnFn dist_func);
void GXInitLightPos(GXLightObj* lt_obj, f32 x, f32 y, f32 z);
void GXInitLightDir(GXLightObj* lt_obj, f32 nx, f32 ny, f32 nz);
void GXInitLightColor(GXLightObj* lt_obj, GXColor color);
void GXInitLightAttn(GXLightObj* lt_obj, f32 a0, f32 a1, f32 a2, f32 k0, f32 k1, f32 k2);
void GXInitLightAttnA(GXLightObj* lt_obj, f32 a0, f32 a1, f32 a2);
void GXInitLightAttnK(GXLightObj* lt_obj, f32 k0, f32 k1, f32 k2);
void GXLoadLightObjImm(GXLightObj* lt_obj, GXLightID light);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,23 @@
#ifndef DOLPHIN_GXMANAGE_H
#define DOLPHIN_GXMANAGE_H
#include <dolphin/gx/GXFifo.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*GXDrawDoneCallback)(void);
GXFifoObj* GXInit(void* base, u32 size);
GXDrawDoneCallback GXSetDrawDoneCallback(GXDrawDoneCallback cb);
void GXDrawDone(void);
void GXSetDrawDone(void);
void GXFlush(void);
void GXPixModeSync(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,16 @@
#ifndef DOLPHIN_GXPERF_H
#define DOLPHIN_GXPERF_H
#include <dolphin/types.h>
#ifdef __cplusplus
extern "C" {
#endif
void GXReadXfRasMetric(u32* xf_wait_in, u32* xf_wait_out, u32* ras_busy, u32* clocks);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,28 @@
#ifndef DOLPHIN_GXPIXEL_H
#define DOLPHIN_GXPIXEL_H
#include <dolphin/gx/GXEnum.h>
#ifdef __cplusplus
extern "C" {
#endif
void GXSetFog(GXFogType type, f32 startz, f32 endz, f32 nearz, f32 farz, GXColor color);
void GXSetFogColor(GXColor color);
// ? GXSetFogRangeAdj();
void GXSetBlendMode(GXBlendMode type, GXBlendFactor src_factor, GXBlendFactor dst_factor, GXLogicOp op);
void GXSetColorUpdate(GXBool update_enable);
void GXSetAlphaUpdate(GXBool update_enable);
void GXSetZMode(GXBool compare_enable, GXCompare func, GXBool update_enable);
void GXSetZCompLoc(GXBool before_tex);
void GXSetPixelFmt(GXPixelFmt pix_fmt, GXZFmt16 z_fmt);
void GXSetDither(GXBool dither);
void GXSetDstAlpha(GXBool enable, u8 alpha);
// ? GXSetFieldMask();
// ? GXSetFieldMode();
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,100 @@
#ifndef DOLPHIN_GXSTRUCT_H
#define DOLPHIN_GXSTRUCT_H
#include <dolphin/types.h>
#include <dolphin/gx/GXEnum.h>
#ifdef __cplusplus
extern "C" {
#endif
#define VI_TVMODE(format, interlace) (((format) << 2) + (interlace))
#define VI_INTERLACE 0
#define VI_NON_INTERLACE 1
#define VI_PROGRESSIVE 2
#define VI_NTSC 0
#define VI_PAL 1
#define VI_MPAL 2
#define VI_DEBUG 3
#define VI_DEBUG_PAL 4
#define VI_EURGB60 5
typedef enum {
VI_TVMODE_NTSC_INT = VI_TVMODE(VI_NTSC, VI_INTERLACE),
VI_TVMODE_NTSC_DS = VI_TVMODE(VI_NTSC, VI_NON_INTERLACE),
VI_TVMODE_NTSC_PROG = VI_TVMODE(VI_NTSC, VI_PROGRESSIVE),
VI_TVMODE_PAL_INT = VI_TVMODE(VI_PAL, VI_INTERLACE),
VI_TVMODE_PAL_DS = VI_TVMODE(VI_PAL, VI_NON_INTERLACE),
VI_TVMODE_EURGB60_INT = VI_TVMODE(VI_EURGB60, VI_INTERLACE),
VI_TVMODE_EURGB60_DS = VI_TVMODE(VI_EURGB60, VI_NON_INTERLACE),
VI_TVMODE_MPAL_INT = VI_TVMODE(VI_MPAL, VI_INTERLACE),
VI_TVMODE_MPAL_DS = VI_TVMODE(VI_MPAL, VI_NON_INTERLACE),
VI_TVMODE_DEBUG_INT = VI_TVMODE(VI_DEBUG, VI_INTERLACE),
VI_TVMODE_DEBUG_PAL_INT = VI_TVMODE(VI_DEBUG_PAL, VI_INTERLACE),
VI_TVMODE_DEBUG_PAL_DS = VI_TVMODE(VI_DEBUG_PAL, VI_NON_INTERLACE)
} VITVMode;
typedef enum { VI_XFBMODE_SF = 0, VI_XFBMODE_DF } VIXFBMode;
typedef struct {
/*0x00*/ VITVMode viTVmode;
/*0x04*/ u16 fbWidth;
/*0x06*/ u16 efbHeight;
/*0x08*/ u16 xfbHeight;
/*0x0A*/ u16 viXOrigin;
/*0x0C*/ u16 viYOrigin;
/*0x0E*/ u16 viWidth;
/*0x10*/ u16 viHeight;
/*0x14*/ VIXFBMode xFBmode;
/*0x18*/ u8 field_rendering;
u8 aa;
u8 sample_pattern[12][2];
u8 vfilter[7];
} GXRenderModeObj;
typedef struct {
u8 r;
u8 g;
u8 b;
u8 a;
} GXColor;
typedef struct {
#ifdef TARGET_PC
u32 dummy[22];
#else
u32 dummy[8];
#endif
} GXTexObj;
typedef struct {
#ifdef TARGET_PC
u32 dummy[4];
#else
u32 dummy[3];
#endif
} GXTlutObj;
typedef struct {
u32 dummy[16];
} GXLightObj;
typedef struct {
GXAttr attr;
GXAttrType type;
} GXVtxDescList;
typedef struct {
s16 r;
s16 g;
s16 b;
s16 a;
} GXColorS10;
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,35 @@
#ifndef DOLPHIN_GXTEV_H
#define DOLPHIN_GXTEV_H
#include <dolphin/gx/GXEnum.h>
#include <dolphin/gx/GXStruct.h>
#ifdef __cplusplus
extern "C" {
#endif
void GXSetTevOp(GXTevStageID id, GXTevMode mode);
void GXSetTevColorIn(GXTevStageID stage, GXTevColorArg a, GXTevColorArg b, GXTevColorArg c, GXTevColorArg d);
void GXSetTevAlphaIn(GXTevStageID stage, GXTevAlphaArg a, GXTevAlphaArg b, GXTevAlphaArg c, GXTevAlphaArg d);
void GXSetTevColorOp(GXTevStageID stage, GXTevOp op, GXTevBias bias, GXTevScale scale, GXBool clamp,
GXTevRegID out_reg);
void GXSetTevAlphaOp(GXTevStageID stage, GXTevOp op, GXTevBias bias, GXTevScale scale, GXBool clamp,
GXTevRegID out_reg);
void GXSetTevColor(GXTevRegID id, GXColor color);
void GXSetTevColorS10(GXTevRegID id, GXColorS10 color);
void GXSetTevKColor(GXTevKColorID id, GXColor color);
void GXSetTevKColorSel(GXTevStageID stage, GXTevKColorSel sel);
void GXSetTevKAlphaSel(GXTevStageID stage, GXTevKAlphaSel sel);
void GXSetTevSwapMode(GXTevStageID stage, GXTevSwapSel ras_sel, GXTevSwapSel tex_sel);
void GXSetTevSwapModeTable(GXTevSwapSel table, GXTevColorChan red, GXTevColorChan green, GXTevColorChan blue,
GXTevColorChan alpha);
void GXSetAlphaCompare(GXCompare comp0, u8 ref0, GXAlphaOp op, GXCompare comp1, u8 ref1);
void GXSetZTexture(GXZTexOp op, GXTexFmt fmt, u32 bias);
void GXSetTevOrder(GXTevStageID stage, GXTexCoordID coord, GXTexMapID map, GXChannelID color);
void GXSetNumTevStages(u8 nStages);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,29 @@
#ifndef DOLPHIN_GXTEXTURE_H
#define DOLPHIN_GXTEXTURE_H
#include <dolphin/gx/GXEnum.h>
#include <dolphin/gx/GXStruct.h>
#ifdef __cplusplus
extern "C" {
#endif
void GXInitTexObj(GXTexObj* obj, const void* data, u16 width, u16 height, u32 format, GXTexWrapMode wrapS,
GXTexWrapMode wrapT, GXBool mipmap);
void GXInitTexObjCI(GXTexObj* obj, const void* data, u16 width, u16 height, GXCITexFmt format, GXTexWrapMode wrapS,
GXTexWrapMode wrapT, GXBool mipmap, u32 tlut);
void GXInitTexObjData(GXTexObj* obj, const void* data);
void GXInitTexObjLOD(GXTexObj* obj, GXTexFilter min_filt, GXTexFilter mag_filt, f32 min_lod, f32 max_lod, f32 lod_bias,
GXBool bias_clamp, GXBool do_edge_lod, GXAnisotropy max_aniso);
void GXLoadTexObj(GXTexObj* obj, GXTexMapID id);
u32 GXGetTexBufferSize(u16 width, u16 height, u32 format, GXBool mipmap, u8 max_lod);
void GXInvalidateTexAll();
void GXInitTexObjWrapMode(GXTexObj* obj, GXTexWrapMode s, GXTexWrapMode t);
void GXInitTlutObj(GXTlutObj* obj, const void* data, GXTlutFmt format, u16 entries);
void GXLoadTlut(const GXTlutObj* obj, GXTlut idx);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,33 @@
#ifndef DOLPHIN_GXTRANSFORM_H
#define DOLPHIN_GXTRANSFORM_H
#include <dolphin/gx/GXEnum.h>
#ifdef __cplusplus
extern "C" {
#endif
#define GX_PROJECTION_SZ 7
#ifdef TARGET_PC
void GXSetProjection(const void* mtx, GXProjectionType type);
void GXLoadPosMtxImm(const void* mtx, u32 id);
void GXLoadNrmMtxImm(const void* mtx, u32 id);
void GXLoadTexMtxImm(const void* mtx, u32 id, GXTexMtxType type);
#else
void GXSetProjection(f32 mtx[4][4], GXProjectionType type);
void GXLoadPosMtxImm(f32 mtx[3][4], u32 id);
void GXLoadNrmMtxImm(f32 mtx[3][4], u32 id);
void GXLoadTexMtxImm(f32 mtx[][4], u32 id, GXTexMtxType type);
#endif
void GXSetViewport(f32 left, f32 top, f32 wd, f32 ht, f32 nearz, f32 farz);
void GXSetCurrentMtx(u32 id);
void GXSetViewportJitter(f32 left, f32 top, f32 wd, f32 ht, f32 nearz, f32 farz, u32 field);
void GXSetScissorBoxOffset(s32 x_off, s32 y_off);
void GXSetClipMode(GXClipMode mode);
#ifdef __cplusplus
}
#endif
#endif

132
include/dolphin/gx/GXVert.h Normal file
View File

@ -0,0 +1,132 @@
#ifndef DOLPHIN_GXVERT_H
#define DOLPHIN_GXVERT_H
#include <dolphin/types.h>
#ifdef __cplusplus
extern "C" {
#endif
#define GXFIFO_ADDR 0xCC008000
typedef union {
u8 u8;
u16 u16;
u32 u32;
u64 u64;
s8 s8;
s16 s16;
s32 s32;
s64 s64;
f32 f32;
f64 f64;
} PPCWGPipe;
#ifdef __MWERKS__
volatile PPCWGPipe GXWGFifo : GXFIFO_ADDR;
#else
#define GXWGFifo (*(volatile PPCWGPipe*)GXFIFO_ADDR)
#endif
#ifdef TARGET_PC
void GXPosition3f32(f32 x, f32 y, f32 z);
void GXPosition3u16(u16 x, u16 y, u16 z);
void GXPosition3s16(s16 x, s16 y, s16 z);
void GXPosition3u8(u8 x, u8 y, u8 z);
void GXPosition3s8(s8 x, s8 y, s8 z);
void GXPosition2f32(f32 x, f32 y);
void GXPosition2u16(u16 x, u16 y);
void GXPosition2s16(s16 x, s16 y);
void GXPosition2u8(u8 x, u8 y);
void GXPosition2s8(s8 x, s8 y);
void GXPosition1x16(u16 index);
void GXPosition1x8(u8 index);
void GXNormal3f32(f32 x, f32 y, f32 z);
void GXNormal3s16(s16 x, s16 y, s16 z);
void GXNormal3s8(s8 x, s8 y, s8 z);
void GXNormal1x16(u16 index);
void GXNormal1x8(u8 index);
void GXColor4u8(u8 r, u8 g, u8 b, u8 a);
void GXColor3u8(u8 r, u8 g, u8 b);
void GXColor1u32(u32 clr);
void GXColor1u16(u16 clr);
void GXColor1x16(u16 index);
void GXColor1x8(u8 index);
void GXTexCoord2f32(f32 s, f32 t);
void GXTexCoord2u16(u16 s, u16 t);
void GXTexCoord2s16(s16 s, s16 t);
void GXTexCoord2u8(u8 s, u8 t);
void GXTexCoord2s8(s8 s, s8 t);
void GXTexCoord1f32(f32 s, f32 t);
void GXTexCoord1u16(u16 s, u16 t);
void GXTexCoord1s16(s16 s, s16 t);
void GXTexCoord1u8(u8 s, u8 t);
void GXTexCoord1s8(s8 s, s8 t);
void GXTexCoord1x16(u16 index);
void GXTexCoord1x8(u8 index);
extern void GXEnd(void);
#else
static inline void GXPosition2f32(const f32 x, const f32 y) {
GXWGFifo.f32 = x;
GXWGFifo.f32 = y;
}
static inline void GXPosition3s16(const s16 x, const s16 y, const s16 z) {
GXWGFifo.s16 = x;
GXWGFifo.s16 = y;
GXWGFifo.s16 = z;
}
static inline void GXPosition3f32(const f32 x, const f32 y, const f32 z) {
GXWGFifo.f32 = x;
GXWGFifo.f32 = y;
GXWGFifo.f32 = z;
}
static inline void GXNormal3f32(const f32 x, const f32 y, const f32 z) {
GXWGFifo.f32 = x;
GXWGFifo.f32 = y;
GXWGFifo.f32 = z;
}
static inline void GXColor4u8(const u8 r, const u8 g, const u8 b, const u8 a) {
GXWGFifo.u8 = r;
GXWGFifo.u8 = g;
GXWGFifo.u8 = b;
GXWGFifo.u8 = a;
}
static inline void GXTexCoord2s16(const s16 u, const s16 v) {
GXWGFifo.s16 = u;
GXWGFifo.s16 = v;
}
static inline void GXTexCoord2f32(const f32 u, const f32 v) {
GXWGFifo.f32 = u;
GXWGFifo.f32 = v;
}
static inline void GXEnd(void) {}
#endif
#ifdef __cplusplus
}
#endif
#endif

114
include/dolphin/pad.h Normal file
View File

@ -0,0 +1,114 @@
#ifndef DOLPHIN_PAD_H
#define DOLPHIN_PAD_H
#include <dolphin/types.h>
#define PAD_CHAN0 0
#define PAD_CHAN1 1
#define PAD_CHAN2 2
#define PAD_CHAN3 3
#define PAD_CHANMAX 4
#define PAD_MOTOR_STOP 0
#define PAD_MOTOR_RUMBLE 1
#define PAD_MOTOR_STOP_HARD 2
#define PAD_ERR_NONE 0
#define PAD_ERR_NO_CONTROLLER -1
#define PAD_ERR_NOT_READY -2
#define PAD_ERR_TRANSFER -3
#define PAD_BUTTON_LEFT 0x0001
#define PAD_BUTTON_RIGHT 0x0002
#define PAD_BUTTON_DOWN 0x0004
#define PAD_BUTTON_UP 0x0008
#define PAD_TRIGGER_Z 0x0010
#define PAD_TRIGGER_R 0x0020
#define PAD_TRIGGER_L 0x0040
#define PAD_BUTTON_A 0x0100
#define PAD_BUTTON_B 0x0200
#define PAD_BUTTON_X 0x0400
#define PAD_BUTTON_Y 0x0800
#define PAD_BUTTON_MENU 0x1000
#define PAD_BUTTON_START 0x1000
#define PAD_CHAN0_BIT 0x80000000
#define PAD_CHAN1_BIT 0x40000000
#define PAD_CHAN2_BIT 0x20000000
#define PAD_CHAN3_BIT 0x10000000
#define PADButtonDown(buttonLast, button) (((buttonLast) ^ (button)) & (button))
#define PADButtonUp(buttonLast, button) (((buttonLast) ^ (button)) & (buttonLast))
#ifdef __cplusplus
extern "C" {
#endif
typedef struct PADStatus {
u16 button;
s8 stickX;
s8 stickY;
s8 substickX;
s8 substickY;
u8 triggerL;
u8 triggerR;
u8 analogA;
u8 analogB;
s8 err;
} PADStatus;
BOOL PADInit();
u32 PADRead(PADStatus* status);
BOOL PADReset(u32 mask);
BOOL PADRecalibrate(u32 mask);
void PADClamp(PADStatus* status);
void PADClampCircle(PADStatus* status);
void PADControlMotor(s32 chan, u32 cmd);
void PADSetSpec(u32 spec);
void PADControlAllMotors(const u32* cmdArr);
#ifdef TARGET_PC
/* New API to facilitate controller interactions */
typedef struct PADDeadZones {
bool emulateTriggers;
bool useDeadzones;
u16 stickDeadZone;
u16 substickDeadZone;
u16 leftTriggerActivationZone;
u16 rightTriggerActivationZone;
} PADDeadZones;
typedef u16 PADButton;
typedef struct PADButtonMapping {
u32 nativeButton;
PADButton padButton;
} PADButtonMapping;
/* Returns the total number of controllers */
u32 PADCount();
/* Returns the controller name for the given index into the controller map */
const char* PADGetNameForControllerIndex(u32 idx);
void PADSetPortForIndex(u32 index, s32 port);
s32 PADGetIndexForPort(u32 port);
void PADGetVidPid(u32 port, u32* vid, u32* pid);
void PADClearPort(u32 port);
const char* PADGetName(u32 port);
void PADSetButtonMapping(u32 port, PADButtonMapping mapping);
void PADSetAllButtonMappings(u32 port, PADButtonMapping buttons[12]);
PADButtonMapping* PADGetButtonMappings(u32 port, u32* buttonCount);
void PADSerializeMappings();
PADDeadZones* PADGetDeadZones(u32 port);
const char* PADGetButtonName(PADButton);
const char* PADGetNativeButtonName(u32 button);
/* Returns any pressed native button */
s32 PADGetNativeButtonPressed(u32 port);
void PADRestoreDefaultMapping(u32 port);
void PADBlockInput(bool block);
#endif
#ifdef __cplusplus
}
#endif
#endif

72
include/dolphin/si.h Normal file
View File

@ -0,0 +1,72 @@
#ifndef DOLPHIN_SI_H
#define DOLPHIN_SI_H
#include <dolphin/types.h>
#define SI_CHAN0 0
#define SI_CHAN1 1
#define SI_CHAN2 2
#define SI_CHAN3 3
#define SI_MAX_CHAN 4
#define SI_CHAN0_BIT 0x80000000
#define SI_CHAN1_BIT 0x40000000
#define SI_CHAN2_BIT 0x20000000
#define SI_CHAN3_BIT 0x10000000
#define SI_CHAN_BIT(chn) (SI_CHAN0_BIT >> (chn))
#define SI_ERROR_UNDER_RUN 0x0001
#define SI_ERROR_OVER_RUN 0x0002
#define SI_ERROR_COLLISION 0x0004
#define SI_ERROR_NO_RESPONSE 0x0008
#define SI_ERROR_WRST 0x0010
#define SI_ERROR_RDST 0x0020
#define SI_ERROR_UNKNOWN 0x0040
#define SI_ERROR_BUSY 0x0080
#define SI_TYPE_MASK 0x18000000u
#define SI_TYPE_N64 0x00000000u
#define SI_TYPE_DOLPHIN 0x08000000u
#define SI_TYPE_GC SI_TYPE_DOLPHIN
// GameCube specific
#define SI_GC_WIRELESS 0x80000000u
#define SI_GC_NOMOTOR 0x20000000u // no rumble motor
#define SI_GC_STANDARD 0x01000000u // dolphin standard controller
// WaveBird specific
#define SI_WIRELESS_RECEIVED 0x40000000u // 0: no wireless unit
#define SI_WIRELESS_IR 0x04000000u // 0: IR 1: RF
#define SI_WIRELESS_STATE 0x02000000u // 0: variable 1: fixed
#define SI_WIRELESS_ORIGIN 0x00200000u // 0: invalid 1: valid
#define SI_WIRELESS_FIX_ID 0x00100000u // 0: not fixed 1: fixed
#define SI_WIRELESS_TYPE 0x000f0000u
#define SI_WIRELESS_LITE_MASK 0x000c0000u // 0: normal 1: lite controller
#define SI_WIRELESS_LITE 0x00040000u // 0: normal 1: lite controller
#define SI_WIRELESS_CONT_MASK 0x00080000u // 0: non-controller 1: non-controller
#define SI_WIRELESS_CONT 0x00000000u
#define SI_WIRELESS_ID 0x00c0ff00u
#define SI_WIRELESS_TYPE_ID (SI_WIRELESS_TYPE | SI_WIRELESS_ID)
#define SI_N64_CONTROLLER (SI_TYPE_N64 | 0x05000000)
#define SI_N64_MIC (SI_TYPE_N64 | 0x00010000)
#define SI_N64_KEYBOARD (SI_TYPE_N64 | 0x00020000)
#define SI_N64_MOUSE (SI_TYPE_N64 | 0x02000000)
#define SI_GBA (SI_TYPE_N64 | 0x00040000)
#define SI_GC_CONTROLLER (SI_TYPE_GC | SI_GC_STANDARD)
#define SI_GC_RECEIVER (SI_TYPE_GC | SI_GC_WIRELESS)
#define SI_GC_WAVEBIRD (SI_TYPE_GC | SI_GC_WIRELESS | SI_GC_STANDARD | SI_WIRELESS_STATE | SI_WIRELESS_FIX_ID)
#define SI_GC_KEYBOARD (SI_TYPE_GC | 0x00200000)
#define SI_GC_STEERING (SI_TYPE_GC | 0x00000000)
#ifdef __cplusplus
extern "C" {
#endif
u32 SIProbe(s32 chan);
#ifdef __cplusplus
}
#endif
#endif

75
include/dolphin/types.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef DOLPHIN_TYPES_H
#define DOLPHIN_TYPES_H
#ifdef TARGET_PC
#include <bits/wordsize.h>
#endif
typedef signed char s8;
typedef signed short int s16;
typedef signed int s32;
#if __WORDSIZE == 64
typedef signed long int s64;
#else
typedef signed long long int s64;
#endif
typedef unsigned char u8;
typedef unsigned short int u16;
typedef unsigned int u32;
#if __WORDSIZE == 64
typedef unsigned long int u64;
#else
typedef unsigned long long int u64;
#endif
typedef volatile u8 vu8;
typedef volatile u16 vu16;
typedef volatile u32 vu32;
typedef volatile u64 vu64;
typedef volatile s8 vs8;
typedef volatile s16 vs16;
typedef volatile s32 vs32;
typedef volatile s64 vs64;
typedef float f32;
typedef double f64;
typedef volatile f32 vf32;
typedef volatile f64 vf64;
#ifdef TARGET_PC
#include <stdbool.h>
typedef bool BOOL;
#define FALSE false
#define TRUE true
#else
typedef int BOOL;
#define FALSE 0
#define TRUE 1
#endif
#ifdef TARGET_PC
#include <stddef.h>
#else
#define NULL 0
#endif
#ifndef __cplusplus
#define nullptr NULL
#endif
#if defined(__MWERKS__)
#define AT_ADDRESS(addr) : (addr)
#define ATTRIBUTE_ALIGN(num) __attribute__((aligned(num)))
#elif defined(__GNUC__)
#define AT_ADDRESS(addr) // was removed in GCC. define in linker script instead.
#define ATTRIBUTE_ALIGN(num) __attribute__((aligned(num)))
#elif defined(_MSC_VER)
#define AT_ADDRESS(addr)
#define ATTRIBUTE_ALIGN(num)
#else
#error unknown compiler
#endif
#endif

23
include/dolphin/vi.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef DOLPHIN_VI_H
#define DOLPHIN_VI_H
#include <dolphin/gx/GXStruct.h>
#include <dolphin/vifuncs.h>
#ifdef __cplusplus
extern "C" {
#endif
void VIInit(void);
void VIConfigure(GXRenderModeObj *rm);
void VIFlush(void);
u32 VIGetTvFormat(void);
void VISetNextFrameBuffer(void *fb);
void VIWaitForRetrace(void);
void VISetBlack(BOOL black);
#ifdef __cplusplus
}
#endif
#endif

16
include/dolphin/vifuncs.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef DOLPHIN_VIFUNCS_H
#define DOLPHIN_VIFUNCS_H
#include <dolphin/types.h>
#ifdef __cplusplus
extern "C" {
#endif
u32 VIGetNextField(void);
#ifdef __cplusplus
}
#endif
#endif

1135
include/magic_enum.hpp Normal file

File diff suppressed because it is too large Load Diff

210
lib/aurora.cpp Normal file
View File

@ -0,0 +1,210 @@
#include <aurora/aurora.h>
#include "gfx/common.hpp"
#include "imgui.hpp"
#include "internal.hpp"
#include "webgpu/gpu.hpp"
#include "window.hpp"
#include <SDL_filesystem.h>
#include <imgui.h>
namespace aurora {
static Module Log("aurora");
AuroraConfig g_config;
// GPU
using webgpu::g_device;
using webgpu::g_queue;
using webgpu::g_swapChain;
constexpr std::array PreferredBackendOrder{
#ifdef DAWN_ENABLE_BACKEND_D3D12
// BACKEND_D3D12,
#endif
#ifdef DAWN_ENABLE_BACKEND_METAL
BACKEND_METAL,
#endif
#ifdef DAWN_ENABLE_BACKEND_VULKAN
BACKEND_VULKAN,
#endif
#ifdef DAWN_ENABLE_BACKEND_DESKTOP_GL
BACKEND_OPENGL,
#endif
#ifdef DAWN_ENABLE_BACKEND_OPENGLES
BACKEND_OPENGLES,
#endif
#ifdef DAWN_ENABLE_BACKEND_NULL
BACKEND_NULL,
#endif
};
static bool g_initialFrame = false;
static AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexcept {
g_config = config;
if (g_config.appName == nullptr) {
g_config.appName = "Aurora";
}
if (g_config.configPath == nullptr) {
g_config.configPath = SDL_GetPrefPath(nullptr, g_config.appName);
}
if (g_config.msaa == 0) {
g_config.msaa = 1;
}
if (g_config.maxTextureAnisotropy == 0) {
g_config.maxTextureAnisotropy = 16;
}
window::initialize();
/* Attempt to create a window using the calling application's desired backend */
AuroraBackend selectedBackend = config.desiredBackend;
bool windowCreated = false;
if (selectedBackend != BACKEND_AUTO && window::create_window(selectedBackend)) {
if (webgpu::initialize(selectedBackend)) {
windowCreated = true;
} else {
window::destroy_window();
}
}
if (!windowCreated) {
for (const auto backendType : PreferredBackendOrder) {
selectedBackend = backendType;
if (!window::create_window(selectedBackend)) {
continue;
}
if (webgpu::initialize(selectedBackend)) {
windowCreated = true;
break;
} else {
window::destroy_window();
}
}
}
if (!windowCreated) {
Log.report(LOG_FATAL, FMT_STRING("Error creating window: {}"), SDL_GetError());
unreachable();
}
// Initialize SDL_Renderer for ImGui when we can't use a Dawn backend
if (webgpu::g_backendType == WGPUBackendType_Null) {
if (!window::create_renderer()) {
Log.report(LOG_FATAL, FMT_STRING("Failed to initialize SDL renderer: {}"), SDL_GetError());
unreachable();
}
}
window::show_window();
gfx::initialize();
imgui::create_context();
const auto size = window::get_window_size();
Log.report(LOG_INFO, FMT_STRING("Using framebuffer size {}x{} scale {}"), size.fb_width, size.fb_height, size.scale);
if (g_config.imGuiInitCallback != nullptr) {
g_config.imGuiInitCallback(&size);
}
imgui::initialize();
if (aurora_begin_frame()) {
g_initialFrame = true;
}
return {
.backend = selectedBackend,
.configPath = g_config.configPath,
.windowSize = size,
};
}
static WGPUTextureView g_currentView = nullptr;
static void shutdown() noexcept {
if (g_currentView != nullptr) {
wgpuTextureViewRelease(g_currentView);
g_currentView = nullptr;
}
imgui::shutdown();
gfx::shutdown();
webgpu::shutdown();
window::shutdown();
}
static const AuroraEvent* update() noexcept {
if (g_initialFrame) {
aurora_end_frame();
g_initialFrame = false;
}
const auto* events = window::poll_events();
imgui::new_frame(window::get_window_size());
return events;
}
static bool begin_frame() noexcept {
g_currentView = wgpuSwapChainGetCurrentTextureView(g_swapChain);
if (!g_currentView) {
ImGui::EndFrame();
// Force swapchain recreation
const auto size = window::get_window_size();
webgpu::resize_swapchain(size.fb_width, size.fb_height, true);
return false;
}
gfx::begin_frame();
return true;
}
static void end_frame() noexcept {
const auto encoderDescriptor = WGPUCommandEncoderDescriptor{
.label = "Redraw encoder",
};
auto encoder = wgpuDeviceCreateCommandEncoder(g_device, &encoderDescriptor);
gfx::end_frame(encoder);
gfx::render(encoder);
{
const std::array attachments{
WGPURenderPassColorAttachment{
.view = g_currentView,
.loadOp = WGPULoadOp_Clear,
.storeOp = WGPUStoreOp_Store,
},
};
const WGPURenderPassDescriptor renderPassDescriptor{
.label = "Post render pass",
.colorAttachmentCount = attachments.size(),
.colorAttachments = attachments.data(),
};
auto pass = wgpuCommandEncoderBeginRenderPass(encoder, &renderPassDescriptor);
// Copy EFB -> XFB (swapchain)
wgpuRenderPassEncoderSetPipeline(pass, webgpu::g_CopyPipeline);
wgpuRenderPassEncoderSetBindGroup(pass, 0, webgpu::g_CopyBindGroup, 0, nullptr);
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
if (!g_initialFrame) {
// Render ImGui
imgui::render(pass);
}
wgpuRenderPassEncoderEnd(pass);
wgpuRenderPassEncoderRelease(pass);
}
const WGPUCommandBufferDescriptor cmdBufDescriptor{.label = "Redraw command buffer"};
const auto buffer = wgpuCommandEncoderFinish(encoder, &cmdBufDescriptor);
wgpuQueueSubmit(g_queue, 1, &buffer);
wgpuCommandBufferRelease(buffer);
wgpuCommandEncoderRelease(encoder);
wgpuSwapChainPresent(g_swapChain);
wgpuTextureViewRelease(g_currentView);
g_currentView = nullptr;
if (!g_initialFrame) {
ImGui::EndFrame();
}
}
} // namespace aurora
// C API bindings
AuroraInfo aurora_initialize(int argc, char* argv[], const AuroraConfig* config) {
return aurora::initialize(argc, argv, *config);
}
void aurora_shutdown() { aurora::shutdown(); }
const AuroraEvent* aurora_update() { return aurora::update(); }
bool aurora_begin_frame() { return aurora::begin_frame(); }
void aurora_end_frame() { aurora::end_frame(); }

127
lib/dawn/BackendBinding.cpp Normal file
View File

@ -0,0 +1,127 @@
#include "BackendBinding.hpp"
#if defined(DAWN_ENABLE_BACKEND_D3D12)
#include <dawn/native/D3D12Backend.h>
#endif
#if defined(DAWN_ENABLE_BACKEND_METAL)
#include <dawn/native/MetalBackend.h>
#endif
#if defined(DAWN_ENABLE_BACKEND_VULKAN)
#include <dawn/native/VulkanBackend.h>
#endif
#if defined(DAWN_ENABLE_BACKEND_OPENGL)
#include <SDL_video.h>
#include <dawn/native/OpenGLBackend.h>
#endif
#if defined(DAWN_ENABLE_BACKEND_NULL)
#include <dawn/native/NullBackend.h>
#endif
namespace aurora::webgpu::utils {
#if defined(DAWN_ENABLE_BACKEND_D3D12)
BackendBinding* CreateD3D12Binding(SDL_Window* window, WGPUDevice device);
#endif
#if defined(DAWN_ENABLE_BACKEND_METAL)
BackendBinding* CreateMetalBinding(SDL_Window* window, WGPUDevice device);
#endif
#if defined(DAWN_ENABLE_BACKEND_NULL)
BackendBinding* CreateNullBinding(SDL_Window* window, WGPUDevice device);
#endif
#if defined(DAWN_ENABLE_BACKEND_OPENGL)
BackendBinding* CreateOpenGLBinding(SDL_Window* window, WGPUDevice device);
#endif
#if defined(DAWN_ENABLE_BACKEND_VULKAN)
BackendBinding* CreateVulkanBinding(SDL_Window* window, WGPUDevice device);
#endif
BackendBinding::BackendBinding(SDL_Window* window, WGPUDevice device) : m_window(window), m_device(device) {}
bool DiscoverAdapter(dawn::native::Instance* instance, SDL_Window* window, WGPUBackendType type) {
switch (type) {
#if defined(DAWN_ENABLE_BACKEND_D3D12)
case WGPUBackendType_D3D12: {
dawn::native::d3d12::AdapterDiscoveryOptions options;
return instance->DiscoverAdapters(&options);
}
#endif
#if defined(DAWN_ENABLE_BACKEND_METAL)
case WGPUBackendType_Metal: {
dawn::native::metal::AdapterDiscoveryOptions options;
return instance->DiscoverAdapters(&options);
}
#endif
#if defined(DAWN_ENABLE_BACKEND_VULKAN)
case WGPUBackendType_Vulkan: {
dawn::native::vulkan::AdapterDiscoveryOptions options;
return instance->DiscoverAdapters(&options);
}
#endif
#if defined(DAWN_ENABLE_BACKEND_DESKTOP_GL)
case WGPUBackendType_OpenGL: {
SDL_GL_ResetAttributes();
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 4);
SDL_GL_CreateContext(window);
auto getProc = reinterpret_cast<void* (*)(const char*)>(SDL_GL_GetProcAddress);
dawn::native::opengl::AdapterDiscoveryOptions adapterOptions;
adapterOptions.getProc = getProc;
return instance->DiscoverAdapters(&adapterOptions);
}
#endif
#if defined(DAWN_ENABLE_BACKEND_OPENGLES)
case WGPUBackendType_OpenGLES: {
SDL_GL_ResetAttributes();
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_CreateContext(window);
auto getProc = reinterpret_cast<void* (*)(const char*)>(SDL_GL_GetProcAddress);
dawn::native::opengl::AdapterDiscoveryOptionsES adapterOptions;
adapterOptions.getProc = getProc;
return instance->DiscoverAdapters(&adapterOptions);
}
#endif
#if defined(DAWN_ENABLE_BACKEND_NULL)
case WGPUBackendType_Null:
instance->DiscoverDefaultAdapters();
return true;
#endif
default:
return false;
}
}
BackendBinding* CreateBinding(WGPUBackendType type, SDL_Window* window, WGPUDevice device) {
switch (type) {
#if defined(DAWN_ENABLE_BACKEND_D3D12)
case WGPUBackendType_D3D12:
return CreateD3D12Binding(window, device);
#endif
#if defined(DAWN_ENABLE_BACKEND_METAL)
case WGPUBackendType_Metal:
return CreateMetalBinding(window, device);
#endif
#if defined(DAWN_ENABLE_BACKEND_NULL)
case WGPUBackendType_Null:
return CreateNullBinding(window, device);
#endif
#if defined(DAWN_ENABLE_BACKEND_DESKTOP_GL)
case WGPUBackendType_OpenGL:
return CreateOpenGLBinding(window, device);
#endif
#if defined(DAWN_ENABLE_BACKEND_OPENGLES)
case WGPUBackendType_OpenGLES:
return CreateOpenGLBinding(window, device);
#endif
#if defined(DAWN_ENABLE_BACKEND_VULKAN)
case WGPUBackendType_Vulkan:
return CreateVulkanBinding(window, device);
#endif
default:
return nullptr;
}
}
} // namespace aurora::webgpu::utils

View File

@ -0,0 +1,27 @@
#pragma once
#include <dawn/native/DawnNative.h>
#include <webgpu/webgpu.h>
struct SDL_Window;
namespace aurora::webgpu::utils {
class BackendBinding {
public:
virtual ~BackendBinding() = default;
virtual uint64_t GetSwapChainImplementation() = 0;
virtual WGPUTextureFormat GetPreferredSwapChainTextureFormat() = 0;
protected:
BackendBinding(SDL_Window* window, WGPUDevice device);
SDL_Window* m_window = nullptr;
WGPUDevice m_device = nullptr;
};
bool DiscoverAdapter(dawn::native::Instance* instance, SDL_Window* window, WGPUBackendType type);
BackendBinding* CreateBinding(WGPUBackendType type, SDL_Window* window, WGPUDevice device);
} // namespace aurora::webgpu::utils

37
lib/dawn/D3D12Binding.cpp Normal file
View File

@ -0,0 +1,37 @@
#include "BackendBinding.hpp"
#include <SDL_syswm.h>
#include <dawn/native/D3D12Backend.h>
namespace aurora::webgpu::utils {
class D3D12Binding : public BackendBinding {
public:
D3D12Binding(SDL_Window* window, WGPUDevice device) : BackendBinding(window, device) {}
uint64_t GetSwapChainImplementation() override {
if (m_swapChainImpl.userData == nullptr) {
CreateSwapChainImpl();
}
return reinterpret_cast<uint64_t>(&m_swapChainImpl);
}
WGPUTextureFormat GetPreferredSwapChainTextureFormat() override {
if (m_swapChainImpl.userData == nullptr) {
CreateSwapChainImpl();
}
return dawn::native::d3d12::GetNativeSwapChainPreferredFormat(&m_swapChainImpl);
}
private:
DawnSwapChainImplementation m_swapChainImpl{};
void CreateSwapChainImpl() {
SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
SDL_GetWindowWMInfo(m_window, &wmInfo);
m_swapChainImpl = dawn::native::d3d12::CreateNativeSwapChainImpl(m_device, wmInfo.info.win.window);
}
};
BackendBinding* CreateD3D12Binding(SDL_Window* window, WGPUDevice device) { return new D3D12Binding(window, device); }
} // namespace aurora::webgpu::utils

108
lib/dawn/MetalBinding.mm Normal file
View File

@ -0,0 +1,108 @@
#include "BackendBinding.hpp"
#include <SDL_metal.h>
#include <dawn/native/MetalBackend.h>
#import <QuartzCore/CAMetalLayer.h>
template <typename T> DawnSwapChainImplementation CreateSwapChainImplementation(T *swapChain) {
DawnSwapChainImplementation impl = {};
impl.userData = swapChain;
impl.Init = [](void *userData, void *wsiContext) {
auto *ctx = static_cast<typename T::WSIContext *>(wsiContext);
reinterpret_cast<T *>(userData)->Init(ctx);
};
impl.Destroy = [](void *userData) { delete reinterpret_cast<T *>(userData); };
impl.Configure = [](void *userData, WGPUTextureFormat format, WGPUTextureUsage allowedUsage, uint32_t width,
uint32_t height) {
return static_cast<T *>(userData)->Configure(format, allowedUsage, width, height);
};
impl.GetNextTexture = [](void *userData, DawnSwapChainNextTexture *nextTexture) {
return static_cast<T *>(userData)->GetNextTexture(nextTexture);
};
impl.Present = [](void *userData) { return static_cast<T *>(userData)->Present(); };
return impl;
}
namespace aurora::webgpu::utils {
class SwapChainImplMTL {
public:
using WSIContext = DawnWSIContextMetal;
explicit SwapChainImplMTL(SDL_Window *window) : m_view(SDL_Metal_CreateView(window)) {}
~SwapChainImplMTL() { SDL_Metal_DestroyView(m_view); }
void Init(DawnWSIContextMetal *ctx) {
mMtlDevice = ctx->device;
mCommandQueue = ctx->queue;
}
DawnSwapChainError Configure(WGPUTextureFormat format, WGPUTextureUsage usage, uint32_t width, uint32_t height) {
if (format != WGPUTextureFormat_BGRA8Unorm) {
return "unsupported format";
}
assert(width > 0);
assert(height > 0);
CGSize size = {};
size.width = width;
size.height = height;
mLayer = (__bridge CAMetalLayer *)(SDL_Metal_GetLayer(m_view));
[mLayer setDevice:mMtlDevice];
[mLayer setPixelFormat:MTLPixelFormatBGRA8Unorm];
[mLayer setDrawableSize:size];
constexpr uint32_t kFramebufferOnlyTextureUsages = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_Present;
bool hasOnlyFramebufferUsages = (usage & (~kFramebufferOnlyTextureUsages)) == 0u;
if (hasOnlyFramebufferUsages) {
[mLayer setFramebufferOnly:YES];
}
return DAWN_SWAP_CHAIN_NO_ERROR;
}
DawnSwapChainError GetNextTexture(DawnSwapChainNextTexture *nextTexture) {
mCurrentDrawable = [mLayer nextDrawable];
mCurrentTexture = mCurrentDrawable.texture;
nextTexture->texture.ptr = (__bridge void *)(mCurrentTexture);
return DAWN_SWAP_CHAIN_NO_ERROR;
}
DawnSwapChainError Present() {
id<MTLCommandBuffer> commandBuffer = [mCommandQueue commandBuffer];
[commandBuffer presentDrawable:mCurrentDrawable];
[commandBuffer commit];
return DAWN_SWAP_CHAIN_NO_ERROR;
}
private:
SDL_MetalView m_view = nil;
id<MTLDevice> mMtlDevice = nil;
id<MTLCommandQueue> mCommandQueue = nil;
CAMetalLayer *mLayer = nullptr;
id<CAMetalDrawable> mCurrentDrawable = nil;
id<MTLTexture> mCurrentTexture = nil;
};
class MetalBinding : public BackendBinding {
public:
MetalBinding(SDL_Window *window, WGPUDevice device) : BackendBinding(window, device) {}
uint64_t GetSwapChainImplementation() override {
if (m_swapChainImpl.userData == nullptr) {
m_swapChainImpl = CreateSwapChainImplementation(new SwapChainImplMTL(m_window));
}
return reinterpret_cast<uint64_t>(&m_swapChainImpl);
}
WGPUTextureFormat GetPreferredSwapChainTextureFormat() override { return WGPUTextureFormat_BGRA8Unorm; }
private:
DawnSwapChainImplementation m_swapChainImpl{};
};
BackendBinding *CreateMetalBinding(SDL_Window *window, WGPUDevice device) { return new MetalBinding(window, device); }
} // namespace aurora::webgpu::utils

26
lib/dawn/NullBinding.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "BackendBinding.hpp"
#include <dawn/native/NullBackend.h>
namespace aurora::webgpu::utils {
class NullBinding : public BackendBinding {
public:
NullBinding(SDL_Window* window, WGPUDevice device) : BackendBinding(window, device) {}
uint64_t GetSwapChainImplementation() override {
if (m_swapChainImpl.userData == nullptr) {
m_swapChainImpl = dawn::native::null::CreateNativeSwapChainImpl();
}
return reinterpret_cast<uint64_t>(&m_swapChainImpl);
}
WGPUTextureFormat GetPreferredSwapChainTextureFormat() override {
return WGPUTextureFormat_RGBA8Unorm;
}
private:
DawnSwapChainImplementation m_swapChainImpl{};
};
BackendBinding* CreateNullBinding(SDL_Window* window, WGPUDevice device) { return new NullBinding(window, device); }
} // namespace aurora::webgpu::utils

View File

@ -0,0 +1,35 @@
#include "BackendBinding.hpp"
#include <SDL_video.h>
#include <dawn/native/OpenGLBackend.h>
namespace aurora::webgpu::utils {
class OpenGLBinding : public BackendBinding {
public:
OpenGLBinding(SDL_Window* window, WGPUDevice device) : BackendBinding(window, device) {}
uint64_t GetSwapChainImplementation() override {
if (m_swapChainImpl.userData == nullptr) {
CreateSwapChainImpl();
}
return reinterpret_cast<uint64_t>(&m_swapChainImpl);
}
WGPUTextureFormat GetPreferredSwapChainTextureFormat() override {
if (m_swapChainImpl.userData == nullptr) {
CreateSwapChainImpl();
}
return dawn::native::opengl::GetNativeSwapChainPreferredFormat(&m_swapChainImpl);
}
private:
DawnSwapChainImplementation m_swapChainImpl{};
void CreateSwapChainImpl() {
m_swapChainImpl = dawn::native::opengl::CreateNativeSwapChainImpl(
m_device, [](void* userdata) { SDL_GL_SwapWindow(static_cast<SDL_Window*>(userdata)); }, m_window);
}
};
BackendBinding* CreateOpenGLBinding(SDL_Window* window, WGPUDevice device) { return new OpenGLBinding(window, device); }
} // namespace aurora::webgpu::utils

View File

@ -0,0 +1,42 @@
#include "BackendBinding.hpp"
#include "../internal.hpp"
#include <SDL_vulkan.h>
#include <dawn/native/VulkanBackend.h>
namespace aurora::webgpu::utils {
static Module Log("aurora::webgpu::utils::VulkanBinding");
class VulkanBinding : public BackendBinding {
public:
VulkanBinding(SDL_Window* window, WGPUDevice device) : BackendBinding(window, device) {}
uint64_t GetSwapChainImplementation() override {
if (m_swapChainImpl.userData == nullptr) {
CreateSwapChainImpl();
}
return reinterpret_cast<uint64_t>(&m_swapChainImpl);
}
WGPUTextureFormat GetPreferredSwapChainTextureFormat() override {
if (m_swapChainImpl.userData == nullptr) {
CreateSwapChainImpl();
}
return dawn::native::vulkan::GetNativeSwapChainPreferredFormat(&m_swapChainImpl);
}
private:
DawnSwapChainImplementation m_swapChainImpl{};
void CreateSwapChainImpl() {
VkSurfaceKHR surface = VK_NULL_HANDLE;
if (SDL_Vulkan_CreateSurface(m_window, dawn::native::vulkan::GetInstance(m_device), &surface) != SDL_TRUE) {
Log.report(LOG_FATAL, FMT_STRING("Failed to create Vulkan surface: {}"), SDL_GetError());
}
m_swapChainImpl = dawn::native::vulkan::CreateNativeSwapChainImpl(m_device, surface);
}
};
BackendBinding* CreateVulkanBinding(SDL_Window* window, WGPUDevice device) { return new VulkanBinding(window, device); }
} // namespace aurora::webgpu::utils

63
lib/dolphin/GXBump.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "gx.hpp"
void GXSetNumIndStages(u8 num) { update_gx_state(g_gxState.numIndStages, num); }
void GXSetIndTexOrder(GXIndTexStageID indStage, GXTexCoordID texCoord, GXTexMapID texMap) {
auto& stage = g_gxState.indStages[indStage];
update_gx_state(stage.texCoordId, texCoord);
update_gx_state(stage.texMapId, texMap);
}
void GXSetIndTexCoordScale(GXIndTexStageID indStage, GXIndTexScale scaleS, GXIndTexScale scaleT) {
auto& stage = g_gxState.indStages[indStage];
update_gx_state(stage.scaleS, scaleS);
update_gx_state(stage.scaleT, scaleT);
}
void GXSetIndTexMtx(GXIndTexMtxID id, const void* offset, s8 scaleExp) {
if (id < GX_ITM_0 || id > GX_ITM_2) {
Log.report(LOG_FATAL, FMT_STRING("invalid ind tex mtx ID {}"), id);
}
update_gx_state(g_gxState.indTexMtxs[id - 1], {*reinterpret_cast<const aurora::Mat3x2<float>*>(offset), scaleExp});
}
void GXSetTevIndirect(GXTevStageID tevStage, GXIndTexStageID indStage, GXIndTexFormat fmt, GXIndTexBiasSel biasSel,
GXIndTexMtxID matrixSel, GXIndTexWrap wrapS, GXIndTexWrap wrapT, GXBool addPrev, GXBool indLod,
GXIndTexAlphaSel alphaSel) {
auto& stage = g_gxState.tevStages[tevStage];
update_gx_state(stage.indTexStage, indStage);
update_gx_state(stage.indTexFormat, fmt);
update_gx_state(stage.indTexBiasSel, biasSel);
update_gx_state(stage.indTexAlphaSel, alphaSel);
update_gx_state(stage.indTexMtxId, matrixSel);
update_gx_state(stage.indTexWrapS, wrapS);
update_gx_state(stage.indTexWrapT, wrapT);
update_gx_state(stage.indTexAddPrev, addPrev);
update_gx_state(stage.indTexUseOrigLOD, indLod);
}
void GXSetTevDirect(GXTevStageID stageId) {
auto& stage = g_gxState.tevStages[stageId];
// TODO is this right?
update_gx_state(stage.indTexStage, GX_INDTEXSTAGE0);
update_gx_state(stage.indTexFormat, GX_ITF_8);
update_gx_state(stage.indTexBiasSel, GX_ITB_NONE);
update_gx_state(stage.indTexAlphaSel, GX_ITBA_OFF);
update_gx_state(stage.indTexMtxId, GX_ITM_OFF);
update_gx_state(stage.indTexWrapS, GX_ITW_OFF);
update_gx_state(stage.indTexWrapT, GX_ITW_OFF);
update_gx_state(stage.indTexUseOrigLOD, false);
update_gx_state(stage.indTexAddPrev, false);
}
void GXSetTevIndWarp(GXTevStageID tevStage, GXIndTexStageID indStage, GXBool signedOffsets, GXBool replaceMode,
GXIndTexMtxID matrixSel) {
const auto wrap = replaceMode ? GX_ITW_0 : GX_ITW_OFF;
const auto biasSel = signedOffsets ? GX_ITB_STU : GX_ITB_NONE;
GXSetTevIndirect(tevStage, indStage, GX_ITF_8, biasSel, matrixSel, wrap, wrap, false, false, GX_ITBA_OFF);
}
// TODO GXSetTevIndTile
// TODO GXSetTevIndBumpST
// TODO GXSetTevIndBumpXYZ
// TODO GXSetTevIndRepeat

7
lib/dolphin/GXCull.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "gx.hpp"
void GXSetScissor(u32 left, u32 top, u32 width, u32 height) { aurora::gfx::set_scissor(left, top, width, height); }
void GXSetCullMode(GXCullMode mode) { update_gx_state(g_gxState.cullMode, mode); }
// TODO GXSetCoPlanar

View File

@ -0,0 +1,23 @@
#include "gx.hpp"
#include "../gfx/model/shader.hpp"
void GXBeginDisplayList(void* list, u32 size) {
// TODO
}
u32 GXEndDisplayList() {
// TODO
return 0;
}
void GXCallDisplayList(const void* data, u32 nbytes) {
// TODO CElementGen needs fixing
for (const auto& type : aurora::gfx::gx::g_gxState.vtxDesc) {
if (type == GX_DIRECT) {
Log.report(LOG_WARNING, FMT_STRING("Direct attributes in surface config!"));
return;
}
}
aurora::gfx::model::queue_surface(static_cast<const u8*>(data), nbytes);
}

13
lib/dolphin/GXDraw.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "gx.hpp"
// TODO GXDrawCylinder
// TODO GXDrawTorus
void GXDrawSphere(u8 numMajor, u8 numMinor) { puts("GXDrawSphere is a stub"); }
// TODO GXDrawCube
// TODO GXDrawDodeca
// TODO GXDrawOctahedron
// TODO GXDrawIcosahedron
// TODO GXDrawSphere1
// TODO GXGenNormalTable

6
lib/dolphin/GXExtra.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "gx.hpp"
void GXDestroyTexObj(GXTexObj* obj_) {
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
obj->ref.reset();
}

47
lib/dolphin/GXFifo.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "gx.hpp"
static GXFifoObj* GPFifo;
static GXFifoObj* CPUFifo;
void GXGetGPStatus(GXBool* overhi, GXBool* underlow, GXBool* readIdle, GXBool* cmdIdle, GXBool* brkpt) {
*overhi = *underlow = *readIdle = *cmdIdle = *brkpt = false;
*readIdle = true;
}
// TODO GXGetFifoStatus
void GXGetFifoPtrs(GXFifoObj* fifo, void** readPtr, void** writePtr) {
*readPtr = NULL;
*writePtr = NULL;
}
GXFifoObj* GXGetCPUFifo() { return CPUFifo; }
GXFifoObj* GXGetGPFifo() { return GPFifo; }
// TODO GXGetFifoBase
// TODO GXGetFifoSize
// TODO GXGetFifoLimits
// TODO GXSetBreakPtCallback
// TODO GXEnableBreakPt
// TODO GXDisableBreakPt
void GXInitFifoBase(GXFifoObj* fifo, void* base, u32 size) {}
void GXInitFifoPtrs(GXFifoObj* fifo, void* readPtr, void* writePtr) {}
// TODO GXInitFifoLimits
void GXSetCPUFifo(GXFifoObj* fifo) { CPUFifo = fifo; }
void GXSetGPFifo(GXFifoObj* fifo) { GPFifo = fifo; }
void GXSaveCPUFifo(GXFifoObj* fifo) {}
// TODO GXSaveGPFifo
// TODO GXRedirectWriteGatherPipe
// TODO GXRestoreWriteGatherPipe
// TODO GXSetCurrentGXThread
// TODO GXGetCurrentGXThread
// TODO GXGetOverflowCount
// TODO GXResetOverflowCount

View File

@ -0,0 +1,55 @@
#include "gx.hpp"
#include "../window.hpp"
extern "C" {
GXRenderModeObj GXNtsc480IntDf = {
VI_TVMODE_NTSC_INT, 640, 480, 480, 40, 0, 640, 480, VI_XFBMODE_DF, 0, 0,
};
GXRenderModeObj GXPal528IntDf = {
VI_TVMODE_PAL_INT, 704, 528, 480, 40, 0, 640, 480, VI_XFBMODE_DF, 0, 0,
};
GXRenderModeObj GXMpal480IntDf = {
VI_TVMODE_PAL_INT, 640, 480, 480, 40, 0, 640, 480, VI_XFBMODE_DF, 0, 0,
};
}
void GXAdjustForOverscan(GXRenderModeObj* rmin, GXRenderModeObj* rmout, u16 hor, u16 ver) {
*rmout = *rmin;
const auto size = aurora::window::get_window_size();
rmout->fbWidth = size.fb_width;
rmout->efbHeight = size.fb_height;
rmout->xfbHeight = size.fb_height;
}
void GXSetDispCopySrc(u16 left, u16 top, u16 wd, u16 ht) {}
void GXSetTexCopySrc(u16 left, u16 top, u16 wd, u16 ht) {
// TODO
}
void GXSetDispCopyDst(u16 wd, u16 ht) {}
void GXSetTexCopyDst(u16 wd, u16 ht, GXTexFmt fmt, GXBool mipmap) {
// TODO
}
// TODO GXSetDispCopyFrame2Field
// TODO GXSetCopyClamp
u32 GXSetDispCopyYScale(f32 vscale) { return 0; }
void GXSetCopyClear(GXColor color, u32 depth) { update_gx_state(g_gxState.clearColor, from_gx_color(color)); }
void GXSetCopyFilter(GXBool aa, u8 sample_pattern[12][2], GXBool vf, u8 vfilter[7]) {}
void GXSetDispCopyGamma(GXGamma gamma) {}
void GXCopyDisp(void* dest, GXBool clear) {}
// TODO move GXCopyTex here
// TODO GXGetYScaleFactor
// TODO GXGetNumXfbLines
// TODO GXClearBoundingBox
// TODO GXReadBoundingBox

View File

@ -0,0 +1,62 @@
#include "gx.hpp"
#include <optional>
void GXSetVtxDesc(GXAttr attr, GXAttrType type) { update_gx_state(g_gxState.vtxDesc[attr], type); }
void GXSetVtxDescv(GXVtxDescList* list) {
g_gxState.vtxDesc.fill({});
while (list->attr != GX_VA_NULL) {
update_gx_state(g_gxState.vtxDesc[list->attr], list->type);
++list;
}
}
void GXClearVtxDesc() { g_gxState.vtxDesc.fill({}); }
void GXSetVtxAttrFmt(GXVtxFmt vtxfmt, GXAttr attr, GXCompCnt cnt, GXCompType type, u8 frac) {
if (vtxfmt < GX_VTXFMT0 || vtxfmt >= GX_MAX_VTXFMT) {
Log.report(LOG_FATAL, FMT_STRING("invalid vtxfmt {}"), vtxfmt);
unreachable();
}
if (attr < GX_VA_PNMTXIDX || attr >= GX_VA_MAX_ATTR) {
Log.report(LOG_FATAL, FMT_STRING("invalid attr {}"), attr);
unreachable();
}
auto& fmt = g_gxState.vtxFmts[vtxfmt].attrs[attr];
update_gx_state(fmt.cnt, cnt);
update_gx_state(fmt.type, type);
update_gx_state(fmt.frac, frac);
}
// TODO GXSetVtxAttrFmtv
void GXSetArray(GXAttr attr, const void* data, u32 size, u8 stride) {
auto& array = g_gxState.arrays[attr];
array.data = data;
array.size = size;
array.stride = stride;
array.cachedRange = {};
}
// TODO move GXBegin, GXEnd here
void GXSetTexCoordGen2(GXTexCoordID dst, GXTexGenType type, GXTexGenSrc src, u32 mtx, GXBool normalize, u32 postMtx) {
if (dst < GX_TEXCOORD0 || dst > GX_TEXCOORD7) {
Log.report(LOG_FATAL, FMT_STRING("invalid tex coord {}"), dst);
unreachable();
}
update_gx_state(g_gxState.tcgs[dst],
{type, src, static_cast<GXTexMtx>(mtx), static_cast<GXPTTexMtx>(postMtx), normalize});
}
void GXSetNumTexGens(u8 num) { update_gx_state(g_gxState.numTexGens, num); }
// TODO GXInvalidateVtxCache
void GXSetLineWidth(u8 width, GXTexOffset offs) {
// TODO
}
// TODO GXSetPointSize
// TODO GXEnableTexOffsets

106
lib/dolphin/GXGet.cpp Normal file
View File

@ -0,0 +1,106 @@
#include "gx.hpp"
#include "../gfx/texture.hpp"
// TODO GXGetVtxDesc
// TODO GXGetVtxDescv
// TODO GXGetVtxAttrFmtv
// TODO GXGetLineWidth
// TODO GXGetPointSize
void GXGetVtxAttrFmt(GXVtxFmt idx, GXAttr attr, GXCompCnt* compCnt, GXCompType* compType, u8* shift) {
const auto& fmt = g_gxState.vtxFmts[idx].attrs[attr];
*compCnt = fmt.cnt;
*compType = fmt.type;
*shift = fmt.frac;
}
// TODO GXGetViewportv
void GXGetProjectionv(f32* p) {
const auto& mtx = g_gxState.origProj;
p[0] = static_cast<float>(g_gxState.projType);
p[1] = mtx.m0[0];
p[3] = mtx.m1[1];
p[5] = mtx.m2[2];
p[6] = mtx.m2[3];
if (g_gxState.projType == GX_ORTHOGRAPHIC) {
p[2] = mtx.m0[3];
p[4] = mtx.m1[3];
} else {
p[2] = mtx.m0[2];
p[4] = mtx.m1[2];
}
}
// TODO GXGetScissor
// TODO GXGetCullMode
void GXGetLightAttnA(GXLightObj* light_, float* a0, float* a1, float* a2) {
auto* light = reinterpret_cast<const GXLightObj_*>(light_);
*a0 = light->a0;
*a1 = light->a1;
*a2 = light->a2;
}
void GXGetLightAttnK(GXLightObj* light_, float* k0, float* k1, float* k2) {
auto* light = reinterpret_cast<const GXLightObj_*>(light_);
*k0 = light->k0;
*k1 = light->k1;
*k2 = light->k2;
}
void GXGetLightPos(GXLightObj* light_, float* x, float* y, float* z) {
auto* light = reinterpret_cast<const GXLightObj_*>(light_);
*x = light->px;
*z = light->py;
*z = light->pz;
}
void GXGetLightDir(GXLightObj* light_, float* nx, float* ny, float* nz) {
auto* light = reinterpret_cast<const GXLightObj_*>(light_);
*nx = -light->nx;
*ny = -light->ny;
*nz = -light->nz;
}
void GXGetLightColor(GXLightObj* light_, GXColor* col) {
auto* light = reinterpret_cast<const GXLightObj_*>(light_);
*col = light->color;
}
void* GXGetTexObjData(GXTexObj* tex_obj) {
return const_cast<void*>(reinterpret_cast<const GXTexObj_*>(tex_obj)->data);
}
u16 GXGetTexObjWidth(GXTexObj* tex_obj) { return reinterpret_cast<const GXTexObj_*>(tex_obj)->width; }
u16 GXGetTexObjHeight(GXTexObj* tex_obj) { return reinterpret_cast<const GXTexObj_*>(tex_obj)->height; }
GXTexFmt GXGetTexObjFmt(GXTexObj* tex_obj) {
return static_cast<GXTexFmt>(reinterpret_cast<const GXTexObj_*>(tex_obj)->fmt);
}
GXTexWrapMode GXGetTexObjWrapS(GXTexObj* tex_obj) { return reinterpret_cast<const GXTexObj_*>(tex_obj)->wrapS; }
GXTexWrapMode GXGetTexObjWrapT(GXTexObj* tex_obj) { return reinterpret_cast<const GXTexObj_*>(tex_obj)->wrapT; }
GXBool GXGetTexObjMipMap(GXTexObj* tex_obj) { return reinterpret_cast<const GXTexObj_*>(tex_obj)->hasMips; }
// TODO GXGetTexObjAll
// TODO GXGetTexObjMinFilt
// TODO GXGetTexObjMagFilt
// TODO GXGetTexObjMinLOD
// TODO GXGetTexObjMaxLOD
// TODO GXGetTexObjLODBias
// TODO GXGetTexObjBiasClamp
// TODO GXGetTexObjEdgeLOD
// TODO GXGetTexObjMaxAniso
// TODO GXGetTexObjLODAll
// TODO GXGetTexObjTlut
// TODO GXGetTlutObjData
// TODO GXGetTlutObjFmt
// TODO GXGetTlutObjNumEntries
// TODO GXGetTlutObjAll
// TODO GXGetTexRegionAll
// TODO GXGetTlutRegionAll

238
lib/dolphin/GXLighting.cpp Normal file
View File

@ -0,0 +1,238 @@
#include "gx.hpp"
void GXInitLightAttn(GXLightObj* light_, float a0, float a1, float a2, float k0, float k1, float k2) {
auto* light = reinterpret_cast<GXLightObj_*>(light_);
light->a0 = a0;
light->a1 = a1;
light->a2 = a2;
light->k0 = k0;
light->k1 = k1;
light->k2 = k2;
}
void GXInitLightAttnA(GXLightObj* light_, float a0, float a1, float a2) {
auto* light = reinterpret_cast<GXLightObj_*>(light_);
light->a0 = a0;
light->a1 = a1;
light->a2 = a2;
}
void GXInitLightAttnK(GXLightObj* light_, float k0, float k1, float k2) {
auto* light = reinterpret_cast<GXLightObj_*>(light_);
light->k0 = k0;
light->k1 = k1;
light->k2 = k2;
}
void GXInitLightSpot(GXLightObj* light_, float cutoff, GXSpotFn spotFn) {
if (cutoff <= 0.f || cutoff > 90.f) {
spotFn = GX_SP_OFF;
}
float cr = std::cos((cutoff * M_PIF) / 180.f);
float a0 = 1.f;
float a1 = 0.f;
float a2 = 0.f;
switch (spotFn) {
default:
break;
case GX_SP_FLAT:
a0 = -1000.f * cr;
a1 = 1000.f;
a2 = 0.f;
break;
case GX_SP_COS:
a0 = -cr / (1.f - cr);
a1 = 1.f / (1.f - cr);
a2 = 0.f;
break;
case GX_SP_COS2:
a0 = 0.f;
a1 = -cr / (1.f - cr);
a2 = 1.f / (1.f - cr);
break;
case GX_SP_SHARP: {
const float d = (1.f - cr) * (1.f - cr);
a0 = cr * (cr - 2.f);
a1 = 2.f / d;
a2 = -1.f / d;
break;
}
case GX_SP_RING1: {
const float d = (1.f - cr) * (1.f - cr);
a0 = 4.f * cr / d;
a1 = 4.f * (1.f + cr) / d;
a2 = -4.f / d;
break;
}
case GX_SP_RING2: {
const float d = (1.f - cr) * (1.f - cr);
a0 = 1.f - 2.f * cr * cr / d;
a1 = 4.f * cr / d;
a2 = -2.f / d;
break;
}
}
auto* light = reinterpret_cast<GXLightObj_*>(light_);
light->a0 = a0;
light->a1 = a1;
light->a2 = a2;
}
void GXInitLightDistAttn(GXLightObj* light_, float refDistance, float refBrightness, GXDistAttnFn distFunc) {
if (refDistance < 0.f || refBrightness < 0.f || refBrightness >= 1.f) {
distFunc = GX_DA_OFF;
}
float k0 = 1.f;
float k1 = 0.f;
float k2 = 0.f;
switch (distFunc) {
case GX_DA_GENTLE:
k0 = 1.0f;
k1 = (1.0f - refBrightness) / (refBrightness * refDistance);
k2 = 0.0f;
break;
case GX_DA_MEDIUM:
k0 = 1.0f;
k1 = 0.5f * (1.0f - refBrightness) / (refBrightness * refDistance);
k2 = 0.5f * (1.0f - refBrightness) / (refBrightness * refDistance * refDistance);
break;
case GX_DA_STEEP:
k0 = 1.0f;
k1 = 0.0f;
k2 = (1.0f - refBrightness) / (refBrightness * refDistance * refDistance);
break;
case GX_DA_OFF:
k0 = 1.0f;
k1 = 0.0f;
k2 = 0.0f;
break;
}
auto* light = reinterpret_cast<GXLightObj_*>(light_);
light->k0 = k0;
light->k1 = k1;
light->k2 = k2;
}
void GXInitLightPos(GXLightObj* light_, float x, float y, float z) {
auto* light = reinterpret_cast<GXLightObj_*>(light_);
light->px = x;
light->py = y;
light->pz = z;
}
void GXInitLightColor(GXLightObj* light_, GXColor col) {
auto* light = reinterpret_cast<GXLightObj_*>(light_);
light->color = col;
}
void GXLoadLightObjImm(GXLightObj* light_, GXLightID id) {
u32 idx = std::log2<u32>(id);
aurora::gfx::gx::Light realLight;
auto* light = reinterpret_cast<const GXLightObj_*>(light_);
realLight.pos = {light->px, light->py, light->pz};
realLight.dir = {light->nx, light->ny, light->nz};
realLight.cosAtt = {light->a0, light->a1, light->a2};
realLight.distAtt = {light->k0, light->k1, light->k2};
realLight.color = from_gx_color(light->color);
update_gx_state(g_gxState.lights[idx], realLight);
}
// TODO GXLoadLightObjIndx
void GXSetChanAmbColor(GXChannelID id, GXColor color) {
if (id == GX_COLOR0A0) {
GXSetChanAmbColor(GX_COLOR0, color);
GXSetChanAmbColor(GX_ALPHA0, color);
return;
} else if (id == GX_COLOR1A1) {
GXSetChanAmbColor(GX_COLOR1, color);
GXSetChanAmbColor(GX_ALPHA1, color);
return;
}
if (id < GX_COLOR0 || id > GX_ALPHA1) {
Log.report(LOG_FATAL, FMT_STRING("bad channel {}"), id);
unreachable();
}
update_gx_state(g_gxState.colorChannelState[id].ambColor, from_gx_color(color));
}
void GXSetChanMatColor(GXChannelID id, GXColor color) {
if (id == GX_COLOR0A0) {
GXSetChanMatColor(GX_COLOR0, color);
GXSetChanMatColor(GX_ALPHA0, color);
return;
} else if (id == GX_COLOR1A1) {
GXSetChanMatColor(GX_COLOR1, color);
GXSetChanMatColor(GX_ALPHA1, color);
return;
}
if (id < GX_COLOR0 || id > GX_ALPHA1) {
Log.report(LOG_FATAL, FMT_STRING("bad channel {}"), id);
unreachable();
}
update_gx_state(g_gxState.colorChannelState[id].matColor, from_gx_color(color));
}
void GXSetNumChans(u8 num) { update_gx_state(g_gxState.numChans, num); }
void GXInitLightDir(GXLightObj* light_, float nx, float ny, float nz) {
auto* light = reinterpret_cast<GXLightObj_*>(light_);
light->nx = -nx;
light->ny = -ny;
light->nz = -nz;
}
void GXInitSpecularDir(GXLightObj* light_, float nx, float ny, float nz) {
float hx = -nx;
float hy = -ny;
float hz = (-nz + 1.0f);
float mag = ((hx * hx) + (hy * hy) + (hz * hz));
if (mag != 0.0f) {
mag = 1.0f / sqrtf(mag);
}
auto* light = reinterpret_cast<GXLightObj_*>(light_);
light->px = (nx * GX_LARGE_NUMBER);
light->py = (ny * GX_LARGE_NUMBER);
light->pz = (nz * GX_LARGE_NUMBER);
light->nx = hx * mag;
light->ny = hy * mag;
light->nz = hz * mag;
}
void GXInitSpecularDirHA(GXLightObj* light_, float nx, float ny, float nz, float hx, float hy, float hz) {
auto* light = reinterpret_cast<GXLightObj_*>(light_);
light->px = (nx * GX_LARGE_NUMBER);
light->py = (ny * GX_LARGE_NUMBER);
light->pz = (nz * GX_LARGE_NUMBER);
light->nx = hx;
light->ny = hy;
light->nz = hz;
}
void GXSetChanCtrl(GXChannelID id, bool lightingEnabled, GXColorSrc ambSrc, GXColorSrc matSrc, u32 lightState,
GXDiffuseFn diffFn, GXAttnFn attnFn) {
if (id == GX_COLOR0A0) {
GXSetChanCtrl(GX_COLOR0, lightingEnabled, ambSrc, matSrc, lightState, diffFn, attnFn);
GXSetChanCtrl(GX_ALPHA0, lightingEnabled, ambSrc, matSrc, lightState, diffFn, attnFn);
return;
} else if (id == GX_COLOR1A1) {
GXSetChanCtrl(GX_COLOR1, lightingEnabled, ambSrc, matSrc, lightState, diffFn, attnFn);
GXSetChanCtrl(GX_ALPHA1, lightingEnabled, ambSrc, matSrc, lightState, diffFn, attnFn);
return;
}
if (id < GX_COLOR0 || id > GX_ALPHA1) {
Log.report(LOG_FATAL, FMT_STRING("bad channel {}"), id);
unreachable();
}
auto& chan = g_gxState.colorChannelConfig[id];
update_gx_state(chan.lightingEnabled, lightingEnabled);
update_gx_state(chan.ambSrc, ambSrc);
update_gx_state(chan.matSrc, matSrc);
update_gx_state(chan.diffFn, diffFn);
update_gx_state(chan.attnFn, attnFn);
update_gx_state(g_gxState.colorChannelState[id].lightMask, GX::LightMask{lightState});
}

35
lib/dolphin/GXManage.cpp Normal file
View File

@ -0,0 +1,35 @@
#include "gx.hpp"
static GXDrawDoneCallback DrawDoneCB = nullptr;
GXFifoObj* GXInit(void* base, u32 size) { return NULL; }
// TODO GXAbortFrame
// TODO GXSetDrawSync
// TODO GXReadDrawSync
// TODO GXSetDrawSyncCallback
void GXDrawDone() { DrawDoneCB(); }
void GXSetDrawDone() { DrawDoneCB(); }
// TODO GXWaitDrawDone
GXDrawDoneCallback GXSetDrawDoneCallback(GXDrawDoneCallback cb) {
GXDrawDoneCallback old = DrawDoneCB;
DrawDoneCB = cb;
return old;
}
// TODO GXSetResetWritePipe
void GXFlush() {}
// TODO GXResetWriteGatherPipe
void GXPixModeSync() {}
void GXTexModeSync() {}
// TODO IsWriteGatherBufferEmpty
// TODO GXSetMisc

17
lib/dolphin/GXPerf.cpp Normal file
View File

@ -0,0 +1,17 @@
#include "gx.hpp"
// TODO GXSetGPMetric
// TODO GXClearGPMetric
// TODO GXReadGPMetric
// TODO GXReadGP0Metric
// TODO GXReadGP1Metric
// TODO GXReadMemMetric
// TODO GXClearMemMetric
// TODO GXReadPixMetric
// TODO GXClearPixMetric
// TODO GXSetVCacheMetric
// TODO GXReadVCacheMetric
// TODO GXClearVCacheMetric
// TODO GXReadXfRasMetric
// TODO GXInitXfRasMetric
// TODO GXReadClksPerVtx

46
lib/dolphin/GXPixel.cpp Normal file
View File

@ -0,0 +1,46 @@
#include "gx.hpp"
void GXSetFog(GXFogType type, float startZ, float endZ, float nearZ, float farZ, GXColor color) {
update_gx_state(g_gxState.fog, {type, startZ, endZ, nearZ, farZ, from_gx_color(color)});
}
void GXSetFogColor(GXColor color) { update_gx_state(g_gxState.fog.color, from_gx_color(color)); }
// TODO GXInitFogAdjTable
// TODO GXSetFogRangeAdj
void GXSetBlendMode(GXBlendMode mode, GXBlendFactor src, GXBlendFactor dst, GXLogicOp op) {
update_gx_state(g_gxState.blendMode, mode);
update_gx_state(g_gxState.blendFacSrc, src);
update_gx_state(g_gxState.blendFacDst, dst);
update_gx_state(g_gxState.blendOp, op);
}
void GXSetColorUpdate(GXBool enabled) { update_gx_state(g_gxState.colorUpdate, enabled); }
void GXSetAlphaUpdate(bool enabled) { update_gx_state(g_gxState.alphaUpdate, enabled); }
void GXSetZMode(bool compare_enable, GXCompare func, bool update_enable) {
update_gx_state(g_gxState.depthCompare, compare_enable);
update_gx_state(g_gxState.depthFunc, func);
update_gx_state(g_gxState.depthUpdate, update_enable);
}
void GXSetZCompLoc(GXBool before_tex) {
// TODO
}
void GXSetPixelFmt(GXPixelFmt pix_fmt, GXZFmt16 z_fmt) {}
void GXSetDither(GXBool dither) {}
void GXSetDstAlpha(bool enabled, u8 value) {
if (enabled) {
update_gx_state<u32>(g_gxState.dstAlpha, value);
} else {
update_gx_state(g_gxState.dstAlpha, UINT32_MAX);
}
}
// TODO GXSetFieldMask
// TODO GXSetFieldMode

111
lib/dolphin/GXTev.cpp Normal file
View File

@ -0,0 +1,111 @@
#include "gx.hpp"
void GXSetTevOp(GXTevStageID id, GXTevMode mode) {
GXTevColorArg inputColor = GX_CC_RASC;
GXTevAlphaArg inputAlpha = GX_CA_RASA;
if (id != GX_TEVSTAGE0) {
inputColor = GX_CC_CPREV;
inputAlpha = GX_CA_APREV;
}
switch (mode) {
case GX_MODULATE:
GXSetTevColorIn(id, GX_CC_ZERO, GX_CC_TEXC, inputColor, GX_CC_ZERO);
GXSetTevAlphaIn(id, GX_CA_ZERO, GX_CA_TEXA, inputAlpha, GX_CA_ZERO);
break;
case GX_DECAL:
GXSetTevColorIn(id, inputColor, GX_CC_TEXC, GX_CC_TEXA, GX_CC_ZERO);
GXSetTevAlphaIn(id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, inputAlpha);
break;
case GX_BLEND:
GXSetTevColorIn(id, inputColor, GX_CC_ONE, GX_CC_TEXC, GX_CC_ZERO);
GXSetTevAlphaIn(id, GX_CA_ZERO, GX_CA_TEXA, inputAlpha, GX_CA_ZERO);
break;
case GX_REPLACE:
GXSetTevColorIn(id, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC);
GXSetTevAlphaIn(id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA);
break;
case GX_PASSCLR:
GXSetTevColorIn(id, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, inputColor);
GXSetTevAlphaIn(id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, inputAlpha);
break;
}
GXSetTevColorOp(id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
GXSetTevAlphaOp(id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV);
}
void GXSetTevColorIn(GXTevStageID stageId, GXTevColorArg a, GXTevColorArg b, GXTevColorArg c, GXTevColorArg d) {
update_gx_state(g_gxState.tevStages[stageId].colorPass, {a, b, c, d});
}
void GXSetTevAlphaIn(GXTevStageID stageId, GXTevAlphaArg a, GXTevAlphaArg b, GXTevAlphaArg c, GXTevAlphaArg d) {
update_gx_state(g_gxState.tevStages[stageId].alphaPass, {a, b, c, d});
}
void GXSetTevColorOp(GXTevStageID stageId, GXTevOp op, GXTevBias bias, GXTevScale scale, bool clamp,
GXTevRegID outReg) {
update_gx_state(g_gxState.tevStages[stageId].colorOp, {op, bias, scale, outReg, clamp});
}
void GXSetTevAlphaOp(GXTevStageID stageId, GXTevOp op, GXTevBias bias, GXTevScale scale, bool clamp,
GXTevRegID outReg) {
update_gx_state(g_gxState.tevStages[stageId].alphaOp, {op, bias, scale, outReg, clamp});
}
void GXSetTevColor(GXTevRegID id, GXColor color) {
if (id < GX_TEVPREV || id > GX_TEVREG2) {
Log.report(LOG_FATAL, FMT_STRING("bad tevreg {}"), id);
unreachable();
}
update_gx_state(g_gxState.colorRegs[id], from_gx_color(color));
}
void GXSetTevColorS10(GXTevRegID id, GXColorS10 color) {
update_gx_state(g_gxState.colorRegs[id], aurora::Vec4<float>{
static_cast<float>(color.r) / 1023.f,
static_cast<float>(color.g) / 1023.f,
static_cast<float>(color.b) / 1023.f,
static_cast<float>(color.a) / 1023.f,
});
}
void GXSetAlphaCompare(GXCompare comp0, u8 ref0, GXAlphaOp op, GXCompare comp1, u8 ref1) {
update_gx_state(g_gxState.alphaCompare, {comp0, ref0, op, comp1, ref1});
}
void GXSetTevOrder(GXTevStageID id, GXTexCoordID tcid, GXTexMapID tmid, GXChannelID cid) {
auto& stage = g_gxState.tevStages[id];
update_gx_state(stage.texCoordId, tcid);
update_gx_state(stage.texMapId, tmid);
update_gx_state(stage.channelId, cid);
}
// TODO GXSetZTexture
void GXSetNumTevStages(u8 num) { update_gx_state(g_gxState.numTevStages, num); }
void GXSetTevKColor(GXTevKColorID id, GXColor color) {
if (id >= GX_MAX_KCOLOR) {
Log.report(LOG_FATAL, FMT_STRING("bad kcolor {}"), id);
unreachable();
}
update_gx_state(g_gxState.kcolors[id], from_gx_color(color));
}
void GXSetTevKColorSel(GXTevStageID id, GXTevKColorSel sel) { update_gx_state(g_gxState.tevStages[id].kcSel, sel); }
void GXSetTevKAlphaSel(GXTevStageID id, GXTevKAlphaSel sel) { update_gx_state(g_gxState.tevStages[id].kaSel, sel); }
void GXSetTevSwapMode(GXTevStageID stageId, GXTevSwapSel rasSel, GXTevSwapSel texSel) {
auto& stage = g_gxState.tevStages[stageId];
update_gx_state(stage.tevSwapRas, rasSel);
update_gx_state(stage.tevSwapTex, texSel);
}
void GXSetTevSwapModeTable(GXTevSwapSel id, GXTevColorChan red, GXTevColorChan green, GXTevColorChan blue,
GXTevColorChan alpha) {
if (id < GX_TEV_SWAP0 || id >= GX_MAX_TEVSWAP) {
Log.report(LOG_FATAL, FMT_STRING("invalid tev swap sel {}"), id);
unreachable();
}
update_gx_state(g_gxState.tevSwapTable[id], {red, green, blue, alpha});
}

231
lib/dolphin/GXTexture.cpp Normal file
View File

@ -0,0 +1,231 @@
#include "gx.hpp"
#include "../gfx/texture.hpp"
#include <absl/container/flat_hash_map.h>
static absl::flat_hash_map<void*, int> g_resolvedTexMap;
void GXInitTexObj(GXTexObj* obj_, const void* data, u16 width, u16 height, u32 format, GXTexWrapMode wrapS,
GXTexWrapMode wrapT, GXBool mipmap) {
memset(obj_, 0, sizeof(GXTexObj));
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
obj->data = data;
obj->width = width;
obj->height = height;
obj->fmt = format;
obj->wrapS = wrapS;
obj->wrapT = wrapT;
obj->hasMips = mipmap;
// TODO default values?
obj->minFilter = GX_LINEAR;
obj->magFilter = GX_LINEAR;
obj->minLod = 0.f;
obj->maxLod = 0.f;
obj->lodBias = 0.f;
obj->biasClamp = false;
obj->doEdgeLod = false;
obj->maxAniso = GX_ANISO_4;
obj->tlut = GX_TLUT0;
if (g_resolvedTexMap.contains(data)) {
obj->dataInvalidated = false; // TODO hack
} else {
obj->dataInvalidated = true;
}
}
void GXInitTexObjCI(GXTexObj* obj_, const void* data, u16 width, u16 height, GXCITexFmt format, GXTexWrapMode wrapS,
GXTexWrapMode wrapT, GXBool mipmap, u32 tlut) {
memset(obj_, 0, sizeof(GXTexObj));
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
obj->data = data;
obj->width = width;
obj->height = height;
obj->fmt = static_cast<GXTexFmt>(format);
obj->wrapS = wrapS;
obj->wrapT = wrapT;
obj->hasMips = mipmap;
obj->tlut = static_cast<GXTlut>(tlut);
// TODO default values?
obj->minFilter = GX_LINEAR;
obj->magFilter = GX_LINEAR;
obj->minLod = 0.f;
obj->maxLod = 0.f;
obj->lodBias = 0.f;
obj->biasClamp = false;
obj->doEdgeLod = false;
obj->maxAniso = GX_ANISO_4;
obj->dataInvalidated = true;
}
void GXInitTexObjLOD(GXTexObj* obj_, GXTexFilter minFilt, GXTexFilter magFilt, float minLod, float maxLod,
float lodBias, GXBool biasClamp, GXBool doEdgeLod, GXAnisotropy maxAniso) {
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
obj->minFilter = minFilt;
obj->magFilter = magFilt;
obj->minLod = minLod;
obj->maxLod = maxLod;
obj->lodBias = lodBias;
obj->biasClamp = biasClamp;
obj->doEdgeLod = doEdgeLod;
obj->maxAniso = maxAniso;
}
void GXInitTexObjData(GXTexObj* obj_, const void* data) {
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
obj->data = data;
obj->dataInvalidated = true;
}
void GXInitTexObjWrapMode(GXTexObj* obj_, GXTexWrapMode wrapS, GXTexWrapMode wrapT) {
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
obj->wrapS = wrapS;
obj->wrapT = wrapT;
}
void GXInitTexObjTlut(GXTexObj* obj_, u32 tlut) {
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
obj->tlut = static_cast<GXTlut>(tlut);
}
// TODO GXInitTexObjFilter
// TODO GXInitTexObjMaxLOD
// TODO GXInitTexObjMinLOD
// TODO GXInitTexObjLODBias
// TODO GXInitTexObjBiasClamp
// TODO GXInitTexObjEdgeLOD
// TODO GXInitTexObjMaxAniso
// TODO GXInitTexObjUserData
// TODO GXGetTexObjUserData
void GXLoadTexObj(GXTexObj* obj_, GXTexMapID id) {
auto* obj = reinterpret_cast<GXTexObj_*>(obj_);
if (!obj->ref) {
obj->ref = aurora::gfx::new_dynamic_texture_2d(obj->width, obj->height, u32(obj->maxLod) + 1, obj->fmt,
fmt::format(FMT_STRING("GXLoadTexObj_{}"), obj->fmt).c_str());
}
if (obj->dataInvalidated) {
aurora::gfx::write_texture(*obj->ref, {static_cast<const u8*>(obj->data), UINT32_MAX /* TODO */});
obj->dataInvalidated = false;
}
g_gxState.textures[id] = {*obj};
// TODO stateDirty?
}
u32 GXGetTexBufferSize(u16 width, u16 height, u32 fmt, GXBool mips, u8 maxLod) {
s32 shiftX = 0;
s32 shiftY = 0;
switch (fmt) {
case GX_TF_I4:
case GX_TF_C4:
case GX_TF_CMPR:
case GX_CTF_R4:
case GX_CTF_Z4:
shiftX = 3;
shiftY = 3;
break;
case GX_TF_I8:
case GX_TF_IA4:
case GX_TF_C8:
case GX_TF_Z8:
case GX_CTF_RA4:
case GX_CTF_A8:
case GX_CTF_R8:
case GX_CTF_G8:
case GX_CTF_B8:
case GX_CTF_Z8M:
case GX_CTF_Z8L:
shiftX = 3;
shiftY = 2;
break;
case GX_TF_IA8:
case GX_TF_RGB565:
case GX_TF_RGB5A3:
case GX_TF_RGBA8:
case GX_TF_C14X2:
case GX_TF_Z16:
case GX_TF_Z24X8:
case GX_CTF_RA8:
case GX_CTF_RG8:
case GX_CTF_GB8:
case GX_CTF_Z16L:
shiftX = 2;
shiftY = 2;
break;
default:
break;
}
u32 bitSize = fmt == GX_TF_RGBA8 || fmt == GX_TF_Z24X8 ? 64 : 32;
u32 bufLen = 0;
if (mips) {
while (maxLod != 0) {
const u32 tileX = ((width + (1 << shiftX) - 1) >> shiftX);
const u32 tileY = ((height + (1 << shiftY) - 1) >> shiftY);
bufLen += bitSize * tileX * tileY;
if (width == 1 && height == 1) {
return bufLen;
}
width = (width < 2) ? 1 : width / 2;
height = (height < 2) ? 1 : height / 2;
--maxLod;
};
} else {
const u32 tileX = ((width + (1 << shiftX) - 1) >> shiftX);
const u32 tileY = ((height + (1 << shiftY) - 1) >> shiftY);
bufLen = bitSize * tileX * tileY;
}
return bufLen;
}
void GXInitTlutObj(GXTlutObj* obj_, const void* data, GXTlutFmt format, u16 entries) {
memset(obj_, 0, sizeof(GXTlutObj));
GXTexFmt texFmt;
switch (format) {
case GX_TL_IA8:
texFmt = GX_TF_IA8;
break;
case GX_TL_RGB565:
texFmt = GX_TF_RGB565;
break;
case GX_TL_RGB5A3:
texFmt = GX_TF_RGB5A3;
break;
default:
Log.report(LOG_FATAL, FMT_STRING("invalid tlut format {}"), format);
unreachable();
}
auto* obj = reinterpret_cast<GXTlutObj_*>(obj_);
obj->ref = aurora::gfx::new_static_texture_2d(
entries, 1, 1, texFmt, aurora::ArrayRef{static_cast<const u8*>(data), static_cast<size_t>(entries) * 2},
"GXInitTlutObj");
}
void GXLoadTlut(const GXTlutObj* obj_, GXTlut idx) {
g_gxState.tluts[idx] = *reinterpret_cast<const GXTlutObj_*>(obj_);
// TODO stateDirty?
}
// TODO GXInitTexCacheRegion
// TODO GXInitTexPreLoadRegion
// TODO GXInitTlutRegion
// TODO GXInvalidateTexRegion
void GXInvalidateTexAll() {
// no-op?
}
// TODO GXPreLoadEntireTexture
// TODO GXSetTexRegionCallback
// TODO GXSetTlutRegionCallback
// TODO GXLoadTexObjPreLoaded
// TODO GXSetTexCoordScaleManually
// TODO GXSetTexCoordCylWrap
// TODO GXSetTexCoordBias
void GXCopyTex(void* dest, GXBool clear) {
// TODO
g_resolvedTexMap.emplace(dest, 0);
}

123
lib/dolphin/GXTransform.cpp Normal file
View File

@ -0,0 +1,123 @@
#include "gx.hpp"
constexpr aurora::Mat4x4<float> DepthCorrect{
{1.f, 0.f, 0.f, 0.f},
{0.f, 1.f, 0.f, 0.f},
{0.f, 0.f, 1.f, 0.f},
{0.f, 0.f, 1.f, 1.f},
};
void GXSetProjection(const void* mtx_, GXProjectionType type) {
const auto& mtx = *reinterpret_cast<const aurora::Mat4x4<float>*>(mtx_);
g_gxState.origProj = mtx;
g_gxState.projType = type;
#ifdef AURORA_NATIVE_MATRIX
update_gx_state(g_gxState.proj, DepthCorrect * mtx);
#else
update_gx_state(g_gxState.proj, DepthCorrect * mtx.transpose());
#endif
}
// TODO GXSetProjectionv
void GXLoadPosMtxImm(const void* mtx_, u32 id) {
if (id < GX_PNMTX0 || id > GX_PNMTX9) {
Log.report(LOG_FATAL, FMT_STRING("invalid pn mtx {}"), id);
unreachable();
}
auto& state = g_gxState.pnMtx[id / 3];
#ifdef AURORA_NATIVE_MATRIX
const auto& mtx = *reinterpret_cast<const aurora::Mat4x4<float>*>(mtx_);
update_gx_state(state.pos, mtx);
#else
const auto* mtx = reinterpret_cast<const aurora::Mat3x4<float>*>(mtx_);
update_gx_state(state.pos, mtx->toTransposed4x4());
#endif
}
// TODO GXLoadPosMtxIndx
void GXLoadNrmMtxImm(const void* mtx_, u32 id) {
if (id < GX_PNMTX0 || id > GX_PNMTX9) {
Log.report(LOG_FATAL, FMT_STRING("invalid pn mtx {}"), id);
unreachable();
}
auto& state = g_gxState.pnMtx[id / 3];
#ifdef AURORA_NATIVE_MATRIX
const auto& mtx = *reinterpret_cast<const aurora::Mat4x4<float>*>(mtx_);
update_gx_state(state.nrm, mtx);
#else
const auto* mtx = reinterpret_cast<const aurora::Mat3x4<float>*>(mtx_);
update_gx_state(state.nrm, mtx->toTransposed4x4());
#endif
}
// TODO GXLoadNrmMtxImm3x3
// TODO GXLoadNrmMtxIndx3x3
void GXSetCurrentMtx(u32 id) {
if (id < GX_PNMTX0 || id > GX_PNMTX9) {
Log.report(LOG_FATAL, FMT_STRING("invalid pn mtx {}"), id);
unreachable();
}
update_gx_state(g_gxState.currentPnMtx, id / 3);
}
void GXLoadTexMtxImm(const void* mtx_, u32 id, GXTexMtxType type) {
if ((id < GX_TEXMTX0 || id > GX_IDENTITY) && (id < GX_PTTEXMTX0 || id > GX_PTIDENTITY)) {
Log.report(LOG_FATAL, FMT_STRING("invalid tex mtx {}"), id);
unreachable();
}
if (id >= GX_PTTEXMTX0) {
if (type != GX_MTX3x4) {
Log.report(LOG_FATAL, FMT_STRING("invalid pt mtx type {}"), type);
unreachable();
}
const auto idx = (id - GX_PTTEXMTX0) / 3;
#ifdef AURORA_NATIVE_MATRIX
const auto& mtx = *reinterpret_cast<const aurora::Mat4x4<float>*>(mtx_);
update_gx_state<aurora::Mat4x4<float>>(g_gxState.ptTexMtxs[idx], mtx);
#else
const auto& mtx = *reinterpret_cast<const aurora::Mat3x4<float>*>(mtx_);
update_gx_state<aurora::Mat4x4<float>>(g_gxState.ptTexMtxs[idx], mtx.toTransposed4x4());
#endif
} else {
const auto idx = (id - GX_TEXMTX0) / 3;
switch (type) {
case GX_MTX3x4: {
#ifdef AURORA_NATIVE_MATRIX
const auto& mtx = *reinterpret_cast<const aurora::Mat4x4<float>*>(mtx_);
update_gx_state<aurora::gfx::gx::TexMtxVariant>(g_gxState.texMtxs[idx], mtx);
#else
const auto& mtx = *reinterpret_cast<const aurora::Mat3x4<float>*>(mtx_);
update_gx_state<aurora::gfx::gx::TexMtxVariant>(g_gxState.texMtxs[idx], mtx.toTransposed4x4());
#endif
break;
}
case GX_MTX2x4: {
const auto& mtx = *reinterpret_cast<const aurora::Mat4x2<float>*>(mtx_);
#ifdef AURORA_NATIVE_MATRIX
update_gx_state<aurora::gfx::gx::TexMtxVariant>(g_gxState.texMtxs[idx], mtx);
#else
update_gx_state<aurora::gfx::gx::TexMtxVariant>(g_gxState.texMtxs[idx], mtx.transpose());
#endif
break;
}
}
}
}
// TODO GXLoadTexMtxIndx
// TODO GXProject
void GXSetViewport(float left, float top, float width, float height, float nearZ, float farZ) {
aurora::gfx::set_viewport(left, top, width, height, nearZ, farZ);
}
void GXSetViewportJitter(float left, float top, float width, float height, float nearZ, float farZ, u32 field) {
aurora::gfx::set_viewport(left, top, width, height, nearZ, farZ);
}
// TODO GXSetZScaleOffset
// TODO GXSetScissorBoxOffset
// TODO GXSetClipMode

188
lib/dolphin/GXVert.cpp Normal file
View File

@ -0,0 +1,188 @@
#include "gx.hpp"
#include "../gfx/stream/shader.hpp"
#include <algorithm>
#include <optional>
#ifndef NDEBUG
static inline GXAttr next_attr(size_t begin) {
auto iter = std::find_if(g_gxState.vtxDesc.begin() + begin, g_gxState.vtxDesc.end(),
[](const auto type) { return type != GX_NONE; });
if (begin > 0 && iter == g_gxState.vtxDesc.end()) {
// wrap around
iter = std::find_if(g_gxState.vtxDesc.begin(), g_gxState.vtxDesc.end(),
[](const auto type) { return type != GX_NONE; });
}
return GXAttr(iter - g_gxState.vtxDesc.begin());
}
#endif
struct SStreamState {
GXPrimitive primitive;
u16 vertexCount = 0;
aurora::ByteBuffer vertexBuffer;
std::vector<u16> indices;
#ifndef NDEBUG
GXAttr nextAttr;
#endif
explicit SStreamState(GXPrimitive primitive, u16 numVerts, u16 vertexSize) noexcept : primitive(primitive) {
vertexBuffer.reserve_extra(size_t(numVerts) * vertexSize);
if (numVerts > 3 && (primitive == GX_TRIANGLEFAN || primitive == GX_TRIANGLESTRIP)) {
indices.reserve((u32(numVerts) - 3) * 3 + 3);
} else if (numVerts > 4 && primitive == GX_QUADS) {
indices.reserve(u32(numVerts) / 4 * 6);
} else {
indices.reserve(numVerts);
}
#ifndef NDEBUG
nextAttr = next_attr(0);
#endif
}
};
static std::optional<SStreamState> sStreamState;
void GXBegin(GXPrimitive primitive, GXVtxFmt vtxFmt, u16 nVerts) {
#ifndef NDEBUG
if (sStreamState) {
Log.report(LOG_FATAL, FMT_STRING("Stream began twice!"));
unreachable();
}
#endif
uint16_t vertexSize = 0;
for (GXAttr attr{}; const auto type : g_gxState.vtxDesc) {
if (type == GX_DIRECT) {
if (attr == GX_VA_POS || attr == GX_VA_NRM) {
vertexSize += 12;
} else if (attr == GX_VA_CLR0 || attr == GX_VA_CLR1) {
vertexSize += 16;
} else if (attr >= GX_VA_TEX0 && attr <= GX_VA_TEX7) {
vertexSize += 8;
} else {
Log.report(LOG_FATAL, FMT_STRING("don't know how to handle attr {}"), attr);
unreachable();
}
} else if (type == GX_INDEX8 || type == GX_INDEX16) {
vertexSize += 2;
}
attr = GXAttr(attr + 1);
}
if (vertexSize == 0) {
Log.report(LOG_FATAL, FMT_STRING("no vtx attributes enabled?"));
unreachable();
}
sStreamState.emplace(primitive, nVerts, vertexSize);
}
static inline void check_attr_order(GXAttr attr) noexcept {
#ifndef NDEBUG
if (!sStreamState) {
Log.report(LOG_FATAL, FMT_STRING("Stream not started!"));
unreachable();
}
if (sStreamState->nextAttr != attr) {
Log.report(LOG_FATAL, FMT_STRING("bad attribute order: {}, expected {}"), attr, sStreamState->nextAttr);
unreachable();
}
sStreamState->nextAttr = next_attr(attr + 1);
#endif
}
void GXPosition3f32(float x, float y, float z) {
check_attr_order(GX_VA_POS);
auto& state = *sStreamState;
state.vertexBuffer.append(&x, sizeof(float));
state.vertexBuffer.append(&y, sizeof(float));
state.vertexBuffer.append(&z, sizeof(float));
if (state.primitive == GX_TRIANGLES || state.vertexCount < 3) {
// pass
} else if (state.primitive == GX_TRIANGLEFAN) {
state.indices.push_back(0);
state.indices.push_back(state.vertexCount - 1);
} else if (state.primitive == GX_TRIANGLESTRIP) {
if ((state.vertexCount & 1) == 0) {
state.indices.push_back(state.vertexCount - 2);
state.indices.push_back(state.vertexCount - 1);
} else {
state.indices.push_back(state.vertexCount - 1);
state.indices.push_back(state.vertexCount - 2);
}
} else if (state.primitive == GX_QUADS) {
if ((state.vertexCount & 3) == 3) {
state.indices.push_back(state.vertexCount - 3);
state.indices.push_back(state.vertexCount - 1);
}
}
state.indices.push_back(state.vertexCount);
++state.vertexCount;
}
void GXPosition3s16(s16 x, s16 y, s16 z) {
// TODO frac
GXPosition3f32(x, y, z);
}
void GXNormal3f32(float x, float y, float z) {
check_attr_order(GX_VA_NRM);
sStreamState->vertexBuffer.append(&x, 4);
sStreamState->vertexBuffer.append(&y, 4);
sStreamState->vertexBuffer.append(&z, 4);
}
void GXColor4f32(float r, float g, float b, float a) {
check_attr_order(GX_VA_CLR0);
sStreamState->vertexBuffer.append(&r, 4);
sStreamState->vertexBuffer.append(&g, 4);
sStreamState->vertexBuffer.append(&b, 4);
sStreamState->vertexBuffer.append(&a, 4);
}
void GXColor4u8(u8 r, u8 g, u8 b, u8 a) {
GXColor4f32(static_cast<float>(r) / 255.f, static_cast<float>(g) / 255.f, static_cast<float>(b) / 255.f,
static_cast<float>(a) / 255.f);
}
void GXTexCoord2f32(float u, float v) {
check_attr_order(GX_VA_TEX0);
sStreamState->vertexBuffer.append(&u, 4);
sStreamState->vertexBuffer.append(&v, 4);
}
void GXTexCoord2s16(s16 s, s16 t) {
// TODO frac
GXTexCoord2f32(s, t);
}
void GXPosition1x16(u16 idx) {
check_attr_order(GX_VA_POS);
// keep aligned
if (sStreamState->vertexBuffer.size() % 4 != 0) {
sStreamState->vertexBuffer.append_zeroes(4 - (sStreamState->vertexBuffer.size() % 4));
}
sStreamState->vertexBuffer.append(&idx, 2);
}
void GXEnd() {
if (sStreamState->vertexCount == 0) {
sStreamState.reset();
return;
}
const auto vertRange = aurora::gfx::push_verts(sStreamState->vertexBuffer.data(), sStreamState->vertexBuffer.size());
const auto indexRange = aurora::gfx::push_indices(aurora::ArrayRef{sStreamState->indices});
aurora::gfx::stream::PipelineConfig config{};
populate_pipeline_config(config, GX_TRIANGLES);
const auto info = aurora::gfx::gx::build_shader_info(config.shaderConfig);
const auto pipeline = aurora::gfx::pipeline_ref(config);
aurora::gfx::push_draw_command(aurora::gfx::stream::DrawData{
.pipeline = pipeline,
.vertRange = vertRange,
.uniformRange = build_uniform(info),
.indexRange = indexRange,
.indexCount = static_cast<uint32_t>(sStreamState->indices.size()),
.bindGroups = aurora::gfx::gx::build_bind_groups(info, config.shaderConfig, {}),
.dstAlpha = g_gxState.dstAlpha,
});
sStreamState.reset();
}

25
lib/dolphin/gx.hpp Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include "../internal.hpp"
#include "../gfx/gx.hpp"
static aurora::Module Log("aurora::gx");
using aurora::gfx::gx::g_gxState;
template <typename T>
static inline void update_gx_state(T& val, T newVal) {
if (val != newVal) {
val = std::move(newVal);
g_gxState.stateDirty = true;
}
}
static inline aurora::Vec4<float> from_gx_color(GXColor color) {
return {
static_cast<float>(color.r) / 255.f,
static_cast<float>(color.g) / 255.f,
static_cast<float>(color.b) / 255.f,
static_cast<float>(color.a) / 255.f,
};
}

7
lib/dolphin/vi.cpp Normal file
View File

@ -0,0 +1,7 @@
extern "C" {
#include <dolphin/vi.h>
}
void VIInit() {}
u32 VIGetTvFormat() { return 0; }
void VIFlush() {}

813
lib/gfx/common.cpp Normal file
View File

@ -0,0 +1,813 @@
#include "common.hpp"
#include "../internal.hpp"
#include "../webgpu/gpu.hpp"
#include "model/shader.hpp"
#include "stream/shader.hpp"
#include "texture.hpp"
#include <absl/container/flat_hash_map.h>
#include <condition_variable>
#include <deque>
#include <fstream>
#include <thread>
#include <mutex>
#include <magic_enum.hpp>
namespace aurora::gfx {
static Module Log("aurora::gfx");
using webgpu::g_device;
using webgpu::g_queue;
#ifdef AURORA_GFX_DEBUG_GROUPS
std::vector<std::string> g_debugGroupStack;
#endif
constexpr uint64_t UniformBufferSize = 3145728; // 3mb
constexpr uint64_t VertexBufferSize = 3145728; // 3mb
constexpr uint64_t IndexBufferSize = 1048576; // 1mb
constexpr uint64_t StorageBufferSize = 8388608; // 8mb
constexpr uint64_t TextureUploadSize = 25165824; // 24mb
constexpr uint64_t StagingBufferSize =
UniformBufferSize + VertexBufferSize + IndexBufferSize + StorageBufferSize + TextureUploadSize;
struct ShaderState {
stream::State stream;
model::State model;
};
struct ShaderDrawCommand {
ShaderType type;
union {
stream::DrawData stream;
model::DrawData model;
};
};
enum class CommandType {
SetViewport,
SetScissor,
Draw,
};
struct Command {
CommandType type;
#ifdef AURORA_GFX_DEBUG_GROUPS
std::vector<std::string> debugGroupStack;
#endif
union Data {
struct SetViewportCommand {
float left;
float top;
float width;
float height;
float znear;
float zfar;
bool operator==(const SetViewportCommand& rhs) const {
return left == rhs.left && top == rhs.top && width == rhs.width && height == rhs.height && znear == rhs.znear &&
zfar == rhs.zfar;
}
bool operator!=(const SetViewportCommand& rhs) const { return !(*this == rhs); }
} setViewport;
struct SetScissorCommand {
uint32_t x;
uint32_t y;
uint32_t w;
uint32_t h;
bool operator==(const SetScissorCommand& rhs) const {
return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h;
}
bool operator!=(const SetScissorCommand& rhs) const { return !(*this == rhs); }
} setScissor;
ShaderDrawCommand draw;
} data;
};
} // namespace aurora::gfx
namespace aurora {
// For types that we can't ensure are safe to hash with has_unique_object_representations,
// we create specialized methods to handle them. Note that these are highly dependent on
// the structure definition, which could easily change with Dawn updates.
template <>
inline HashType xxh3_hash(const WGPUBindGroupDescriptor& input, HashType seed) {
constexpr auto offset = sizeof(void*) * 2; // skip nextInChain, label
const auto hash = xxh3_hash_s(reinterpret_cast<const u8*>(&input) + offset,
sizeof(WGPUBindGroupDescriptor) - offset - sizeof(void*) /* skip entries */, seed);
return xxh3_hash_s(input.entries, sizeof(WGPUBindGroupEntry) * input.entryCount, hash);
}
template <>
inline HashType xxh3_hash(const WGPUSamplerDescriptor& input, HashType seed) {
constexpr auto offset = sizeof(void*) * 2; // skip nextInChain, label
return xxh3_hash_s(reinterpret_cast<const u8*>(&input) + offset,
sizeof(WGPUSamplerDescriptor) - offset - 2 /* skip padding */, seed);
}
} // namespace aurora
namespace aurora::gfx {
using NewPipelineCallback = std::function<WGPURenderPipeline()>;
std::mutex g_pipelineMutex;
static bool g_hasPipelineThread = false;
static std::thread g_pipelineThread;
static std::atomic_bool g_pipelineThreadEnd;
static std::condition_variable g_pipelineCv;
static absl::flat_hash_map<PipelineRef, WGPURenderPipeline> g_pipelines;
static std::deque<std::pair<PipelineRef, NewPipelineCallback>> g_queuedPipelines;
static absl::flat_hash_map<BindGroupRef, WGPUBindGroup> g_cachedBindGroups;
static absl::flat_hash_map<SamplerRef, WGPUSampler> g_cachedSamplers;
std::atomic_uint32_t queuedPipelines;
std::atomic_uint32_t createdPipelines;
static ByteBuffer g_verts;
static ByteBuffer g_uniforms;
static ByteBuffer g_indices;
static ByteBuffer g_storage;
static ByteBuffer g_staticStorage;
static ByteBuffer g_textureUpload;
WGPUBuffer g_vertexBuffer;
WGPUBuffer g_uniformBuffer;
WGPUBuffer g_indexBuffer;
WGPUBuffer g_storageBuffer;
size_t g_staticStorageLastSize = 0;
static std::array<WGPUBuffer, 3> g_stagingBuffers;
static WGPUSupportedLimits g_cachedLimits;
static ShaderState g_state;
static PipelineRef g_currentPipeline;
using CommandList = std::vector<Command>;
struct ClipRect {
int32_t x;
int32_t y;
int32_t width;
int32_t height;
};
struct RenderPass {
u32 resolveTarget = UINT32_MAX;
ClipRect resolveRect;
Vec4<float> clearColor{0.f, 0.f, 0.f, 0.f};
CommandList commands;
bool clear = true;
};
static std::vector<RenderPass> g_renderPasses;
static u32 g_currentRenderPass = UINT32_MAX;
std::vector<TextureHandle> g_resolvedTextures;
std::vector<TextureUpload> g_textureUploads;
static ByteBuffer g_serializedPipelines{};
static u32 g_serializedPipelineCount = 0;
template <typename PipelineConfig>
static void serialize_pipeline_config(ShaderType type, const PipelineConfig& config) {
static_assert(std::has_unique_object_representations_v<PipelineConfig>);
g_serializedPipelines.append(&type, sizeof(type));
const u32 configSize = sizeof(config);
g_serializedPipelines.append(&configSize, sizeof(configSize));
g_serializedPipelines.append(&config, configSize);
++g_serializedPipelineCount;
}
template <typename PipelineConfig>
static PipelineRef find_pipeline(ShaderType type, const PipelineConfig& config, NewPipelineCallback&& cb,
bool serialize = true) {
PipelineRef hash = xxh3_hash(config, static_cast<HashType>(type));
bool found = false;
{
std::scoped_lock guard{g_pipelineMutex};
found = g_pipelines.contains(hash);
if (!found) {
if (g_hasPipelineThread) {
const auto ref =
std::find_if(g_queuedPipelines.begin(), g_queuedPipelines.end(), [=](auto v) { return v.first == hash; });
if (ref != g_queuedPipelines.end()) {
found = true;
}
} else {
g_pipelines.try_emplace(hash, cb());
if (serialize) {
serialize_pipeline_config(type, config);
}
found = true;
}
}
if (!found) {
g_queuedPipelines.emplace_back(std::pair{hash, std::move(cb)});
if (serialize) {
serialize_pipeline_config(type, config);
}
}
}
if (!found) {
g_pipelineCv.notify_one();
queuedPipelines++;
}
return hash;
}
static inline void push_command(CommandType type, const Command::Data& data) {
if (g_currentRenderPass == UINT32_MAX) {
Log.report(LOG_WARNING, FMT_STRING("Dropping command {}"), magic_enum::enum_name(type));
return;
}
g_renderPasses[g_currentRenderPass].commands.push_back({
.type = type,
#ifdef AURORA_GFX_DEBUG_GROUPS
.debugGroupStack = g_debugGroupStack,
#endif
.data = data,
});
}
static void push_draw_command(ShaderDrawCommand data) { push_command(CommandType::Draw, Command::Data{.draw = data}); }
static Command::Data::SetViewportCommand g_cachedViewport;
void set_viewport(float left, float top, float width, float height, float znear, float zfar) noexcept {
Command::Data::SetViewportCommand cmd{left, top, width, height, znear, zfar};
if (cmd != g_cachedViewport) {
push_command(CommandType::SetViewport, Command::Data{.setViewport = cmd});
g_cachedViewport = cmd;
}
}
static Command::Data::SetScissorCommand g_cachedScissor;
void set_scissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) noexcept {
Command::Data::SetScissorCommand cmd{x, y, w, h};
if (cmd != g_cachedScissor) {
push_command(CommandType::SetScissor, Command::Data{.setScissor = cmd});
g_cachedScissor = cmd;
}
}
static inline bool operator==(const WGPUExtent3D& lhs, const WGPUExtent3D& rhs) {
return lhs.width == rhs.width && lhs.height == rhs.height && lhs.depthOrArrayLayers == rhs.depthOrArrayLayers;
}
static inline bool operator!=(const WGPUExtent3D& lhs, const WGPUExtent3D& rhs) { return !(lhs == rhs); }
void resolve_color(const ClipRect& rect, uint32_t bind, GXTexFmt fmt, bool clear_depth) noexcept {
if (g_resolvedTextures.size() < bind + 1) {
g_resolvedTextures.resize(bind + 1);
}
const WGPUExtent3D size{
.width = static_cast<uint32_t>(rect.width),
.height = static_cast<uint32_t>(rect.height),
.depthOrArrayLayers = 1,
};
if (!g_resolvedTextures[bind] || g_resolvedTextures[bind]->size != size) {
g_resolvedTextures[bind] = new_render_texture(rect.width, rect.height, fmt, "Resolved Texture");
}
auto& currentPass = g_renderPasses[g_currentRenderPass];
currentPass.resolveTarget = bind;
currentPass.resolveRect = rect;
auto& newPass = g_renderPasses.emplace_back();
newPass.clearColor = gx::g_gxState.clearColor;
newPass.clear = false; // TODO
++g_currentRenderPass;
}
template <>
const stream::State& get_state() {
return g_state.stream;
}
template <>
void push_draw_command(stream::DrawData data) {
push_draw_command(ShaderDrawCommand{.type = ShaderType::Stream, .stream = data});
}
template <>
PipelineRef pipeline_ref(stream::PipelineConfig config) {
return find_pipeline(ShaderType::Stream, config, [=]() { return create_pipeline(g_state.stream, config); });
}
template <>
void push_draw_command(model::DrawData data) {
push_draw_command(ShaderDrawCommand{.type = ShaderType::Model, .model = data});
}
template <>
PipelineRef pipeline_ref(model::PipelineConfig config) {
return find_pipeline(ShaderType::Model, config, [=]() { return create_pipeline(g_state.model, config); });
}
static void pipeline_worker() {
bool hasMore = false;
while (true) {
std::pair<PipelineRef, NewPipelineCallback> cb;
{
std::unique_lock lock{g_pipelineMutex};
if (!hasMore) {
g_pipelineCv.wait(lock, [] { return !g_queuedPipelines.empty() || g_pipelineThreadEnd; });
}
if (g_pipelineThreadEnd) {
break;
}
cb = std::move(g_queuedPipelines.front());
}
auto result = cb.second();
// std::this_thread::sleep_for(std::chrono::milliseconds{1500});
{
std::scoped_lock lock{g_pipelineMutex};
if (!g_pipelines.try_emplace(cb.first, std::move(result)).second) {
Log.report(LOG_FATAL, FMT_STRING("Duplicate pipeline {}"), cb.first);
unreachable();
}
g_queuedPipelines.pop_front();
hasMore = !g_queuedPipelines.empty();
}
createdPipelines++;
queuedPipelines--;
}
}
void initialize() {
// No async pipelines for OpenGL (ES)
if (webgpu::g_backendType == WGPUBackendType_OpenGL || webgpu::g_backendType == WGPUBackendType_OpenGLES) {
g_hasPipelineThread = false;
} else {
g_pipelineThreadEnd = false;
g_pipelineThread = std::thread(pipeline_worker);
g_hasPipelineThread = true;
}
// For uniform & storage buffer offset alignments
wgpuDeviceGetLimits(g_device, &g_cachedLimits);
const auto createBuffer = [](WGPUBuffer& out, WGPUBufferUsageFlags usage, uint64_t size, const char* label) {
if (size <= 0) {
return;
}
const WGPUBufferDescriptor descriptor{
.label = label,
.usage = usage,
.size = size,
};
out = wgpuDeviceCreateBuffer(g_device, &descriptor);
};
createBuffer(g_uniformBuffer, WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, UniformBufferSize,
"Shared Uniform Buffer");
createBuffer(g_vertexBuffer, WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst, VertexBufferSize,
"Shared Vertex Buffer");
createBuffer(g_indexBuffer, WGPUBufferUsage_Index | WGPUBufferUsage_CopyDst, IndexBufferSize, "Shared Index Buffer");
createBuffer(g_storageBuffer, WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst, StorageBufferSize,
"Shared Storage Buffer");
for (int i = 0; i < g_stagingBuffers.size(); ++i) {
const auto label = fmt::format(FMT_STRING("Staging Buffer {}"), i);
createBuffer(g_stagingBuffers[i], WGPUBufferUsage_MapWrite | WGPUBufferUsage_CopySrc, StagingBufferSize,
label.c_str());
}
map_staging_buffer();
g_state.stream = stream::construct_state();
g_state.model = model::construct_state();
{
// Load serialized pipeline cache
std::string path = std::string{g_config.configPath} + "/pipeline_cache.bin";
std::ifstream file(path, std::ios::in | std::ios::binary | std::ios::ate);
if (file) {
const auto size = file.tellg();
file.seekg(0, std::ios::beg);
constexpr size_t headerSize = sizeof(g_serializedPipelineCount);
if (size != -1 && size > headerSize) {
g_serializedPipelines.append_zeroes(size_t(size) - headerSize);
file.read(reinterpret_cast<char*>(&g_serializedPipelineCount), headerSize);
file.read(reinterpret_cast<char*>(g_serializedPipelines.data()), size_t(size) - headerSize);
}
}
}
if (g_serializedPipelineCount > 0) {
size_t offset = 0;
while (offset < g_serializedPipelines.size()) {
ShaderType type = *reinterpret_cast<const ShaderType*>(g_serializedPipelines.data() + offset);
offset += sizeof(ShaderType);
u32 size = *reinterpret_cast<const u32*>(g_serializedPipelines.data() + offset);
offset += sizeof(u32);
switch (type) {
case ShaderType::Stream: {
if (size != sizeof(stream::PipelineConfig)) {
break;
}
const auto config = *reinterpret_cast<const stream::PipelineConfig*>(g_serializedPipelines.data() + offset);
if (config.version != gx::GXPipelineConfigVersion) {
break;
}
find_pipeline(
type, config, [=]() { return stream::create_pipeline(g_state.stream, config); }, false);
} break;
case ShaderType::Model: {
if (size != sizeof(model::PipelineConfig)) {
break;
}
const auto config = *reinterpret_cast<const model::PipelineConfig*>(g_serializedPipelines.data() + offset);
if (config.version != gx::GXPipelineConfigVersion) {
break;
}
find_pipeline(
type, config, [=]() { return model::create_pipeline(g_state.model, config); }, false);
} break;
default:
Log.report(LOG_WARNING, FMT_STRING("Unknown pipeline type {}"), static_cast<int>(type));
break;
}
offset += size;
}
}
}
void shutdown() {
if (g_hasPipelineThread) {
g_pipelineThreadEnd = true;
g_pipelineCv.notify_all();
g_pipelineThread.join();
}
{
// Write serialized pipelines to file
const auto path = std::string{g_config.configPath} + "pipeline_cache.bin";
std::ofstream file(path, std::ios::out | std::ios::trunc | std::ios::binary);
if (file) {
file.write(reinterpret_cast<const char*>(&g_serializedPipelineCount), sizeof(g_serializedPipelineCount));
file.write(reinterpret_cast<const char*>(g_serializedPipelines.data()), g_serializedPipelines.size());
}
g_serializedPipelines.clear();
g_serializedPipelineCount = 0;
}
gx::shutdown();
g_resolvedTextures.clear();
g_textureUploads.clear();
for (const auto& item : g_cachedBindGroups) {
wgpuBindGroupRelease(item.second);
}
g_cachedBindGroups.clear();
for (const auto& item : g_cachedSamplers) {
wgpuSamplerRelease(item.second);
}
g_cachedSamplers.clear();
for (const auto& item : g_pipelines) {
wgpuRenderPipelineRelease(item.second);
}
g_pipelines.clear();
g_queuedPipelines.clear();
if (g_vertexBuffer != nullptr) {
wgpuBufferDestroy(g_vertexBuffer);
g_vertexBuffer = nullptr;
}
if (g_uniformBuffer != nullptr) {
wgpuBufferDestroy(g_uniformBuffer);
g_uniformBuffer = nullptr;
}
if (g_indexBuffer != nullptr) {
wgpuBufferDestroy(g_indexBuffer);
g_indexBuffer = nullptr;
}
if (g_storageBuffer != nullptr) {
wgpuBufferDestroy(g_storageBuffer);
g_storageBuffer = nullptr;
}
for (auto& item : g_stagingBuffers) {
if (item != nullptr) {
wgpuBufferDestroy(item);
}
item = nullptr;
}
g_renderPasses.clear();
g_currentRenderPass = UINT32_MAX;
g_state = {};
queuedPipelines = 0;
createdPipelines = 0;
}
static size_t currentStagingBuffer = 0;
static bool bufferMapped = false;
void map_staging_buffer() {
bufferMapped = false;
wgpuBufferMapAsync(
g_stagingBuffers[currentStagingBuffer], WGPUMapMode_Write, 0, StagingBufferSize,
[](WGPUBufferMapAsyncStatus status, void* userdata) {
if (status == WGPUBufferMapAsyncStatus_DestroyedBeforeCallback) {
return;
} else if (status != WGPUBufferMapAsyncStatus_Success) {
Log.report(LOG_FATAL, FMT_STRING("Buffer mapping failed: {}"), status);
unreachable();
}
*static_cast<bool*>(userdata) = true;
},
&bufferMapped);
}
void begin_frame() {
while (!bufferMapped) {
wgpuDeviceTick(g_device);
}
size_t bufferOffset = 0;
auto& stagingBuf = g_stagingBuffers[currentStagingBuffer];
const auto mapBuffer = [&](ByteBuffer& buf, uint64_t size) {
if (size <= 0) {
return;
}
buf = ByteBuffer{static_cast<u8*>(wgpuBufferGetMappedRange(stagingBuf, bufferOffset, size)),
static_cast<size_t>(size)};
bufferOffset += size;
};
mapBuffer(g_verts, VertexBufferSize);
mapBuffer(g_uniforms, UniformBufferSize);
mapBuffer(g_indices, IndexBufferSize);
mapBuffer(g_storage, StorageBufferSize);
mapBuffer(g_textureUpload, TextureUploadSize);
g_renderPasses.emplace_back();
g_renderPasses[0].clearColor = gx::g_gxState.clearColor;
g_currentRenderPass = 0;
// push_command(CommandType::SetViewport, Command::Data{.setViewport = g_cachedViewport});
// push_command(CommandType::SetScissor, Command::Data{.setScissor = g_cachedScissor});
}
// for imgui debug
size_t g_lastVertSize;
size_t g_lastUniformSize;
size_t g_lastIndexSize;
size_t g_lastStorageSize;
void end_frame(WGPUCommandEncoder cmd) {
uint64_t bufferOffset = 0;
const auto writeBuffer = [&](ByteBuffer& buf, WGPUBuffer& out, uint64_t size, std::string_view label) {
const auto writeSize = buf.size(); // Only need to copy this many bytes
if (writeSize > 0) {
wgpuCommandEncoderCopyBufferToBuffer(cmd, g_stagingBuffers[currentStagingBuffer], bufferOffset, out, 0,
writeSize);
buf.clear();
}
bufferOffset += size;
return writeSize;
};
wgpuBufferUnmap(g_stagingBuffers[currentStagingBuffer]);
g_lastVertSize = writeBuffer(g_verts, g_vertexBuffer, VertexBufferSize, "Vertex");
g_lastUniformSize = writeBuffer(g_uniforms, g_uniformBuffer, UniformBufferSize, "Uniform");
g_lastIndexSize = writeBuffer(g_indices, g_indexBuffer, IndexBufferSize, "Index");
g_lastStorageSize = writeBuffer(g_storage, g_storageBuffer, StorageBufferSize, "Storage");
{
// Perform texture copies
for (const auto& item : g_textureUploads) {
const WGPUImageCopyBuffer buf{
.layout =
WGPUTextureDataLayout{
.offset = item.layout.offset + bufferOffset,
.bytesPerRow = ALIGN(item.layout.bytesPerRow, 256),
.rowsPerImage = item.layout.rowsPerImage,
},
.buffer = g_stagingBuffers[currentStagingBuffer],
};
wgpuCommandEncoderCopyBufferToTexture(cmd, &buf, &item.tex, &item.size);
}
g_textureUploads.clear();
g_textureUpload.clear();
}
currentStagingBuffer = (currentStagingBuffer + 1) % g_stagingBuffers.size();
map_staging_buffer();
g_currentRenderPass = UINT32_MAX;
}
void render(WGPUCommandEncoder cmd) {
for (u32 i = 0; i < g_renderPasses.size(); ++i) {
const auto& passInfo = g_renderPasses[i];
bool finalPass = i == g_renderPasses.size() - 1;
if (finalPass && passInfo.resolveTarget != UINT32_MAX) {
Log.report(LOG_FATAL, FMT_STRING("Final render pass must not have resolve target"));
unreachable();
}
const std::array attachments{
WGPURenderPassColorAttachment{
.view = webgpu::g_frameBuffer.view,
.resolveTarget = webgpu::g_graphicsConfig.msaaSamples > 1 ? webgpu::g_frameBufferResolved.view : nullptr,
.loadOp = passInfo.clear ? WGPULoadOp_Clear : WGPULoadOp_Load,
.storeOp = WGPUStoreOp_Store,
.clearColor = {NAN, NAN, NAN, NAN},
.clearValue =
{
.r = passInfo.clearColor.x(),
.g = passInfo.clearColor.y(),
.b = passInfo.clearColor.z(),
.a = passInfo.clearColor.w(),
},
},
};
const WGPURenderPassDepthStencilAttachment depthStencilAttachment{
.view = webgpu::g_depthBuffer.view,
.depthLoadOp = passInfo.clear ? WGPULoadOp_Clear : WGPULoadOp_Load,
.depthStoreOp = WGPUStoreOp_Store,
.clearDepth = NAN,
.depthClearValue = 1.f,
};
const auto label = fmt::format(FMT_STRING("Render pass {}"), i);
const WGPURenderPassDescriptor renderPassDescriptor{
.label = label.c_str(),
.colorAttachmentCount = attachments.size(),
.colorAttachments = attachments.data(),
.depthStencilAttachment = &depthStencilAttachment,
};
auto pass = wgpuCommandEncoderBeginRenderPass(cmd, &renderPassDescriptor);
render_pass(pass, i);
wgpuRenderPassEncoderEnd(pass);
wgpuRenderPassEncoderRelease(pass);
if (passInfo.resolveTarget != UINT32_MAX) {
WGPUImageCopyTexture src{
.origin =
WGPUOrigin3D{
.x = static_cast<uint32_t>(passInfo.resolveRect.x),
.y = static_cast<uint32_t>(passInfo.resolveRect.y),
},
};
if (webgpu::g_graphicsConfig.msaaSamples > 1) {
src.texture = webgpu::g_frameBufferResolved.texture;
} else {
src.texture = webgpu::g_frameBuffer.texture;
}
auto& target = g_resolvedTextures[passInfo.resolveTarget];
const WGPUImageCopyTexture dst{
.texture = target->texture,
};
const WGPUExtent3D size{
.width = static_cast<uint32_t>(passInfo.resolveRect.width),
.height = static_cast<uint32_t>(passInfo.resolveRect.height),
.depthOrArrayLayers = 1,
};
wgpuCommandEncoderCopyTextureToTexture(cmd, &src, &dst, &size);
}
}
g_renderPasses.clear();
}
void render_pass(WGPURenderPassEncoder pass, u32 idx) {
g_currentPipeline = UINTPTR_MAX;
#ifdef AURORA_GFX_DEBUG_GROUPS
std::vector<std::string> lastDebugGroupStack;
#endif
for (const auto& cmd : g_renderPasses[idx].commands) {
#ifdef AURORA_GFX_DEBUG_GROUPS
{
size_t firstDiff = lastDebugGroupStack.size();
for (size_t i = 0; i < lastDebugGroupStack.size(); ++i) {
if (i >= cmd.debugGroupStack.size() || cmd.debugGroupStack[i] != lastDebugGroupStack[i]) {
firstDiff = i;
break;
}
}
for (size_t i = firstDiff; i < lastDebugGroupStack.size(); ++i) {
wgpuRenderPassEncoderPopDebugGroup(pass);
}
for (size_t i = firstDiff; i < cmd.debugGroupStack.size(); ++i) {
wgpuRenderPassEncoderPushDebugGroup(pass, cmd.debugGroupStack[i].c_str());
}
lastDebugGroupStack = cmd.debugGroupStack;
}
#endif
switch (cmd.type) {
case CommandType::SetViewport: {
const auto& vp = cmd.data.setViewport;
wgpuRenderPassEncoderSetViewport(pass, vp.left, vp.top, vp.width, vp.height, vp.znear, vp.zfar);
} break;
case CommandType::SetScissor: {
const auto& sc = cmd.data.setScissor;
wgpuRenderPassEncoderSetScissorRect(pass, sc.x, sc.y, sc.w, sc.h);
} break;
case CommandType::Draw: {
const auto& draw = cmd.data.draw;
switch (draw.type) {
case ShaderType::Stream:
stream::render(g_state.stream, draw.stream, pass);
break;
case ShaderType::Model:
model::render(g_state.model, draw.model, pass);
break;
}
} break;
}
}
#ifdef AURORA_GFX_DEBUG_GROUPS
for (size_t i = 0; i < lastDebugGroupStack.size(); ++i) {
wgpuRenderPassEncoderPopDebugGroup(pass);
}
#endif
}
bool bind_pipeline(PipelineRef ref, WGPURenderPassEncoder pass) {
if (ref == g_currentPipeline) {
return true;
}
std::lock_guard guard{g_pipelineMutex};
const auto it = g_pipelines.find(ref);
if (it == g_pipelines.end()) {
return false;
}
wgpuRenderPassEncoderSetPipeline(pass, it->second);
g_currentPipeline = ref;
return true;
}
static inline Range push(ByteBuffer& target, const uint8_t* data, size_t length, size_t alignment) {
size_t padding = 0;
if (alignment != 0) {
padding = alignment - length % alignment;
}
auto begin = target.size();
if (length == 0) {
length = alignment;
target.append_zeroes(alignment);
} else {
target.append(data, length);
if (padding > 0) {
target.append_zeroes(padding);
}
}
return {static_cast<uint32_t>(begin), static_cast<uint32_t>(length + padding)};
}
static inline Range map(ByteBuffer& target, size_t length, size_t alignment) {
size_t padding = 0;
if (alignment != 0) {
padding = alignment - length % alignment;
}
if (length == 0) {
length = alignment;
}
auto begin = target.size();
target.append_zeroes(length + padding);
return {static_cast<uint32_t>(begin), static_cast<uint32_t>(length + padding)};
}
Range push_verts(const uint8_t* data, size_t length) { return push(g_verts, data, length, 4); }
Range push_indices(const uint8_t* data, size_t length) { return push(g_indices, data, length, 4); }
Range push_uniform(const uint8_t* data, size_t length) {
return push(g_uniforms, data, length, g_cachedLimits.limits.minUniformBufferOffsetAlignment);
}
Range push_storage(const uint8_t* data, size_t length) {
return push(g_storage, data, length, g_cachedLimits.limits.minStorageBufferOffsetAlignment);
}
Range push_texture_data(const uint8_t* data, size_t length, u32 bytesPerRow, u32 rowsPerImage) {
// For CopyBufferToTexture, we need an alignment of 256 per row (see Dawn kTextureBytesPerRowAlignment)
const auto copyBytesPerRow = ALIGN(bytesPerRow, 256);
const auto range = map(g_textureUpload, copyBytesPerRow * rowsPerImage, 0);
u8* dst = g_textureUpload.data() + range.offset;
for (u32 i = 0; i < rowsPerImage; ++i) {
memcpy(dst, data, bytesPerRow);
data += bytesPerRow;
dst += copyBytesPerRow;
}
return range;
}
std::pair<ByteBuffer, Range> map_verts(size_t length) {
const auto range = map(g_verts, length, 4);
return {ByteBuffer{g_verts.data() + range.offset, range.size}, range};
}
std::pair<ByteBuffer, Range> map_indices(size_t length) {
const auto range = map(g_indices, length, 4);
return {ByteBuffer{g_indices.data() + range.offset, range.size}, range};
}
std::pair<ByteBuffer, Range> map_uniform(size_t length) {
const auto range = map(g_uniforms, length, g_cachedLimits.limits.minUniformBufferOffsetAlignment);
return {ByteBuffer{g_uniforms.data() + range.offset, range.size}, range};
}
std::pair<ByteBuffer, Range> map_storage(size_t length) {
const auto range = map(g_storage, length, g_cachedLimits.limits.minStorageBufferOffsetAlignment);
return {ByteBuffer{g_storage.data() + range.offset, range.size}, range};
}
BindGroupRef bind_group_ref(const WGPUBindGroupDescriptor& descriptor) {
const auto id = xxh3_hash(descriptor);
if (!g_cachedBindGroups.contains(id)) {
g_cachedBindGroups.try_emplace(id, wgpuDeviceCreateBindGroup(g_device, &descriptor));
}
return id;
}
WGPUBindGroup find_bind_group(BindGroupRef id) {
const auto it = g_cachedBindGroups.find(id);
if (it == g_cachedBindGroups.end()) {
Log.report(LOG_FATAL, FMT_STRING("get_bind_group: failed to locate {}"), id);
unreachable();
}
return it->second;
}
WGPUSampler sampler_ref(const WGPUSamplerDescriptor& descriptor) {
const auto id = xxh3_hash(descriptor);
auto it = g_cachedSamplers.find(id);
if (it == g_cachedSamplers.end()) {
it = g_cachedSamplers.try_emplace(id, wgpuDeviceCreateSampler(g_device, &descriptor)).first;
}
return it->second;
}
uint32_t align_uniform(uint32_t value) { return ALIGN(value, g_cachedLimits.limits.minUniformBufferOffsetAlignment); }
} // namespace aurora::gfx
void push_debug_group(const char* label) {
#ifdef AURORA_GFX_DEBUG_GROUPS
aurora::gfx::g_debugGroupStack.emplace_back(label);
#endif
}
void pop_debug_group() {
#ifdef AURORA_GFX_DEBUG_GROUPS
aurora::gfx::g_debugGroupStack.pop_back();
#endif
}

205
lib/gfx/common.hpp Normal file
View File

@ -0,0 +1,205 @@
#pragma once
#include "../internal.hpp"
#include <type_traits>
#include <utility>
#include <cstring>
#include <webgpu/webgpu.h>
#include <xxhash_impl.h>
namespace aurora {
#if INTPTR_MAX == INT32_MAX
using HashType = XXH32_hash_t;
#else
using HashType = XXH64_hash_t;
#endif
static inline HashType xxh3_hash_s(const void* input, size_t len, HashType seed = 0) {
return static_cast<HashType>(XXH3_64bits_withSeed(input, len, seed));
}
template <typename T>
static inline HashType xxh3_hash(const T& input, HashType seed = 0) {
// Validate that the type has no padding bytes, which can easily cause
// hash mismatches. This also disallows floats, but that's okay for us.
static_assert(std::has_unique_object_representations_v<T>);
return xxh3_hash_s(&input, sizeof(T), seed);
}
class ByteBuffer {
public:
ByteBuffer() noexcept = default;
explicit ByteBuffer(size_t size) noexcept
: m_data(static_cast<uint8_t*>(calloc(1, size))), m_length(size), m_capacity(size) {}
explicit ByteBuffer(uint8_t* data, size_t size) noexcept
: m_data(data), m_length(0), m_capacity(size), m_owned(false) {}
~ByteBuffer() noexcept {
if (m_data != nullptr && m_owned) {
free(m_data);
}
}
ByteBuffer(ByteBuffer&& rhs) noexcept
: m_data(rhs.m_data), m_length(rhs.m_length), m_capacity(rhs.m_capacity), m_owned(rhs.m_owned) {
rhs.m_data = nullptr;
rhs.m_length = 0;
rhs.m_capacity = 0;
rhs.m_owned = true;
}
ByteBuffer& operator=(ByteBuffer&& rhs) noexcept {
if (m_data != nullptr && m_owned) {
free(m_data);
}
m_data = rhs.m_data;
m_length = rhs.m_length;
m_capacity = rhs.m_capacity;
m_owned = rhs.m_owned;
rhs.m_data = nullptr;
rhs.m_length = 0;
rhs.m_capacity = 0;
rhs.m_owned = true;
return *this;
}
ByteBuffer(ByteBuffer const&) = delete;
ByteBuffer& operator=(ByteBuffer const&) = delete;
[[nodiscard]] uint8_t* data() noexcept { return m_data; }
[[nodiscard]] const uint8_t* data() const noexcept { return m_data; }
[[nodiscard]] size_t size() const noexcept { return m_length; }
[[nodiscard]] bool empty() const noexcept { return m_length == 0; }
void append(const void* data, size_t size) {
resize(m_length + size, false);
memcpy(m_data + m_length, data, size);
m_length += size;
}
void append_zeroes(size_t size) {
resize(m_length + size, true);
m_length += size;
}
void clear() {
if (m_data != nullptr && m_owned) {
free(m_data);
}
m_data = nullptr;
m_length = 0;
m_capacity = 0;
m_owned = true;
}
void reserve_extra(size_t size) { resize(m_length + size, true); }
private:
uint8_t* m_data = nullptr;
size_t m_length = 0;
size_t m_capacity = 0;
bool m_owned = true;
void resize(size_t size, bool zeroed) {
if (size == 0) {
clear();
} else if (m_data == nullptr) {
if (zeroed) {
m_data = static_cast<uint8_t*>(calloc(1, size));
} else {
m_data = static_cast<uint8_t*>(malloc(size));
}
m_owned = true;
} else if (size > m_capacity) {
if (!m_owned) {
abort();
}
m_data = static_cast<uint8_t*>(realloc(m_data, size));
if (zeroed) {
memset(m_data + m_capacity, 0, size - m_capacity);
}
} else {
return;
}
m_capacity = size;
}
};
} // namespace aurora
namespace aurora::gfx {
extern WGPUBuffer g_vertexBuffer;
extern WGPUBuffer g_uniformBuffer;
extern WGPUBuffer g_indexBuffer;
extern WGPUBuffer g_storageBuffer;
extern size_t g_staticStorageLastSize;
using BindGroupRef = HashType;
using PipelineRef = HashType;
using SamplerRef = HashType;
using ShaderRef = HashType;
struct Range {
uint32_t offset = 0;
uint32_t size = 0;
inline bool operator==(const Range& rhs) { return offset == rhs.offset && size == rhs.size; }
};
enum class ShaderType {
Stream,
Model,
};
void initialize();
void shutdown();
void begin_frame();
void end_frame(WGPUCommandEncoder cmd);
void render(WGPUCommandEncoder cmd);
void render_pass(WGPURenderPassEncoder pass, uint32_t idx);
void map_staging_buffer();
Range push_verts(const uint8_t* data, size_t length);
template <typename T>
static inline Range push_verts(ArrayRef<T> data) {
return push_verts(reinterpret_cast<const uint8_t*>(data.data()), data.size() * sizeof(T));
}
Range push_indices(const uint8_t* data, size_t length);
template <typename T>
static inline Range push_indices(ArrayRef<T> data) {
return push_indices(reinterpret_cast<const uint8_t*>(data.data()), data.size() * sizeof(T));
}
Range push_uniform(const uint8_t* data, size_t length);
template <typename T>
static inline Range push_uniform(const T& data) {
return push_uniform(reinterpret_cast<const uint8_t*>(&data), sizeof(T));
}
Range push_storage(const uint8_t* data, size_t length);
template <typename T>
static inline Range push_storage(ArrayRef<T> data) {
return push_storage(reinterpret_cast<const uint8_t*>(data.data()), data.size() * sizeof(T));
}
template <typename T>
static inline Range push_storage(const T& data) {
return push_storage(reinterpret_cast<const uint8_t*>(&data), sizeof(T));
}
Range push_texture_data(const uint8_t* data, size_t length, uint32_t bytesPerRow, uint32_t rowsPerImage);
std::pair<ByteBuffer, Range> map_verts(size_t length);
std::pair<ByteBuffer, Range> map_indices(size_t length);
std::pair<ByteBuffer, Range> map_uniform(size_t length);
std::pair<ByteBuffer, Range> map_storage(size_t length);
template <typename State>
const State& get_state();
template <typename DrawData>
void push_draw_command(DrawData data);
template <typename PipelineConfig>
PipelineRef pipeline_ref(PipelineConfig config);
bool bind_pipeline(PipelineRef ref, WGPURenderPassEncoder pass);
BindGroupRef bind_group_ref(const WGPUBindGroupDescriptor& descriptor);
WGPUBindGroup find_bind_group(BindGroupRef id);
WGPUSampler sampler_ref(const WGPUSamplerDescriptor& descriptor);
uint32_t align_uniform(uint32_t value);
void set_viewport(float left, float top, float width, float height, float znear, float zfar) noexcept;
void set_scissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) noexcept;
} // namespace aurora::gfx

794
lib/gfx/gx.cpp Normal file
View File

@ -0,0 +1,794 @@
#include "gx.hpp"
#include "../webgpu/gpu.hpp"
#include "../window.hpp"
#include "../internal.hpp"
#include "common.hpp"
#include <absl/container/flat_hash_map.h>
#include <cfloat>
#include <cmath>
using aurora::gfx::gx::g_gxState;
static aurora::Module Log("aurora::gx");
namespace aurora::gfx {
static Module Log("aurora::gfx::gx");
namespace gx {
using webgpu::g_device;
using webgpu::g_graphicsConfig;
GXState g_gxState{};
const TextureBind& get_texture(GXTexMapID id) noexcept { return g_gxState.textures[static_cast<size_t>(id)]; }
static inline WGPUBlendFactor to_blend_factor(GXBlendFactor fac, bool isDst) {
switch (fac) {
case GX_BL_ZERO:
return WGPUBlendFactor_Zero;
case GX_BL_ONE:
return WGPUBlendFactor_One;
case GX_BL_SRCCLR: // + GX_BL_DSTCLR
if (isDst) {
return WGPUBlendFactor_Src;
} else {
return WGPUBlendFactor_Dst;
}
case GX_BL_INVSRCCLR: // + GX_BL_INVDSTCLR
if (isDst) {
return WGPUBlendFactor_OneMinusSrc;
} else {
return WGPUBlendFactor_OneMinusDst;
}
case GX_BL_SRCALPHA:
return WGPUBlendFactor_SrcAlpha;
case GX_BL_INVSRCALPHA:
return WGPUBlendFactor_OneMinusSrcAlpha;
case GX_BL_DSTALPHA:
return WGPUBlendFactor_DstAlpha;
case GX_BL_INVDSTALPHA:
return WGPUBlendFactor_OneMinusDstAlpha;
default:
Log.report(LOG_FATAL, FMT_STRING("invalid blend factor {}"), fac);
unreachable();
}
}
static inline WGPUCompareFunction to_compare_function(GXCompare func) {
switch (func) {
case GX_NEVER:
return WGPUCompareFunction_Never;
case GX_LESS:
return WGPUCompareFunction_Less;
case GX_EQUAL:
return WGPUCompareFunction_Equal;
case GX_LEQUAL:
return WGPUCompareFunction_LessEqual;
case GX_GREATER:
return WGPUCompareFunction_Greater;
case GX_NEQUAL:
return WGPUCompareFunction_NotEqual;
case GX_GEQUAL:
return WGPUCompareFunction_GreaterEqual;
case GX_ALWAYS:
return WGPUCompareFunction_Always;
default:
Log.report(LOG_FATAL, FMT_STRING("invalid depth fn {}"), func);
unreachable();
}
}
static inline WGPUBlendState to_blend_state(GXBlendMode mode, GXBlendFactor srcFac, GXBlendFactor dstFac, GXLogicOp op,
u32 dstAlpha) {
WGPUBlendComponent colorBlendComponent;
switch (mode) {
case GX_BM_NONE:
colorBlendComponent = {
.operation = WGPUBlendOperation_Add,
.srcFactor = WGPUBlendFactor_One,
.dstFactor = WGPUBlendFactor_Zero,
};
break;
case GX_BM_BLEND:
colorBlendComponent = {
.operation = WGPUBlendOperation_Add,
.srcFactor = to_blend_factor(srcFac, false),
.dstFactor = to_blend_factor(dstFac, true),
};
break;
case GX_BM_SUBTRACT:
colorBlendComponent = {
.operation = WGPUBlendOperation_ReverseSubtract,
.srcFactor = WGPUBlendFactor_One,
.dstFactor = WGPUBlendFactor_One,
};
break;
case GX_BM_LOGIC:
switch (op) {
case GX_LO_CLEAR:
colorBlendComponent = {
.operation = WGPUBlendOperation_Add,
.srcFactor = WGPUBlendFactor_Zero,
.dstFactor = WGPUBlendFactor_Zero,
};
break;
case GX_LO_COPY:
colorBlendComponent = {
.operation = WGPUBlendOperation_Add,
.srcFactor = WGPUBlendFactor_One,
.dstFactor = WGPUBlendFactor_Zero,
};
break;
case GX_LO_NOOP:
colorBlendComponent = {
.operation = WGPUBlendOperation_Add,
.srcFactor = WGPUBlendFactor_Zero,
.dstFactor = WGPUBlendFactor_One,
};
break;
default:
Log.report(LOG_FATAL, FMT_STRING("unsupported logic op {}"), op);
unreachable();
}
break;
default:
Log.report(LOG_FATAL, FMT_STRING("unsupported blend mode {}"), mode);
unreachable();
}
WGPUBlendComponent alphaBlendComponent{
.operation = WGPUBlendOperation_Add,
.srcFactor = WGPUBlendFactor_One,
.dstFactor = WGPUBlendFactor_Zero,
};
if (dstAlpha != UINT32_MAX) {
alphaBlendComponent = WGPUBlendComponent{
.operation = WGPUBlendOperation_Add,
.srcFactor = WGPUBlendFactor_Constant,
.dstFactor = WGPUBlendFactor_Zero,
};
}
return {
.color = colorBlendComponent,
.alpha = alphaBlendComponent,
};
}
static inline WGPUColorWriteMaskFlags to_write_mask(bool colorUpdate, bool alphaUpdate) {
WGPUColorWriteMaskFlags writeMask = WGPUColorWriteMask_None;
if (colorUpdate) {
writeMask |= WGPUColorWriteMask_Red | WGPUColorWriteMask_Green | WGPUColorWriteMask_Blue;
}
if (alphaUpdate) {
writeMask |= WGPUColorWriteMask_Alpha;
}
return writeMask;
}
static inline WGPUPrimitiveState to_primitive_state(GXPrimitive gx_prim, GXCullMode gx_cullMode) {
WGPUPrimitiveTopology primitive = WGPUPrimitiveTopology_TriangleList;
switch (gx_prim) {
case GX_TRIANGLES:
break;
case GX_TRIANGLESTRIP:
primitive = WGPUPrimitiveTopology_TriangleStrip;
break;
default:
Log.report(LOG_FATAL, FMT_STRING("Unsupported primitive type {}"), gx_prim);
unreachable();
}
WGPUCullMode cullMode = WGPUCullMode_None;
switch (gx_cullMode) {
case GX_CULL_FRONT:
cullMode = WGPUCullMode_Front;
break;
case GX_CULL_BACK:
cullMode = WGPUCullMode_Back;
break;
case GX_CULL_NONE:
break;
default:
Log.report(LOG_FATAL, FMT_STRING("Unsupported cull mode {}"), gx_cullMode);
unreachable();
}
return {
.topology = primitive,
.frontFace = WGPUFrontFace_CW,
.cullMode = cullMode,
};
}
WGPURenderPipeline build_pipeline(const PipelineConfig& config, const ShaderInfo& info,
ArrayRef<WGPUVertexBufferLayout> vtxBuffers, WGPUShaderModule shader,
const char* label) noexcept {
const WGPUDepthStencilState depthStencil{
.format = g_graphicsConfig.depthFormat,
.depthWriteEnabled = config.depthUpdate,
.depthCompare = to_compare_function(config.depthFunc),
.stencilFront =
WGPUStencilFaceState{
.compare = WGPUCompareFunction_Always,
},
.stencilBack =
WGPUStencilFaceState{
.compare = WGPUCompareFunction_Always,
},
};
const auto blendState =
to_blend_state(config.blendMode, config.blendFacSrc, config.blendFacDst, config.blendOp, config.dstAlpha);
const std::array colorTargets{WGPUColorTargetState{
.format = g_graphicsConfig.colorFormat,
.blend = &blendState,
.writeMask = to_write_mask(config.colorUpdate, config.alphaUpdate),
}};
const WGPUFragmentState fragmentState{
.module = shader,
.entryPoint = "fs_main",
.targetCount = colorTargets.size(),
.targets = colorTargets.data(),
};
auto layouts = build_bind_group_layouts(info, config.shaderConfig);
const std::array bindGroupLayouts{
layouts.uniformLayout,
layouts.samplerLayout,
layouts.textureLayout,
};
const WGPUPipelineLayoutDescriptor pipelineLayoutDescriptor{
.label = "GX Pipeline Layout",
.bindGroupLayoutCount = static_cast<uint32_t>(info.sampledTextures.any() ? bindGroupLayouts.size() : 1),
.bindGroupLayouts = bindGroupLayouts.data(),
};
auto pipelineLayout = wgpuDeviceCreatePipelineLayout(g_device, &pipelineLayoutDescriptor);
const WGPURenderPipelineDescriptor descriptor{
.label = label,
.layout = pipelineLayout,
.vertex =
{
.module = shader,
.entryPoint = "vs_main",
.bufferCount = static_cast<uint32_t>(vtxBuffers.size()),
.buffers = vtxBuffers.data(),
},
.primitive = to_primitive_state(config.primitive, config.cullMode),
.depthStencil = &depthStencil,
.multisample =
WGPUMultisampleState{
.count = g_graphicsConfig.msaaSamples,
.mask = UINT32_MAX,
},
.fragment = &fragmentState,
};
auto pipeline = wgpuDeviceCreateRenderPipeline(g_device, &descriptor);
wgpuPipelineLayoutRelease(pipelineLayout);
return pipeline;
}
void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive) noexcept {
config.shaderConfig.fogType = g_gxState.fog.type;
config.shaderConfig.vtxAttrs = g_gxState.vtxDesc;
int lastIndexedAttr = -1;
for (int i = 0; i < GX_VA_MAX_ATTR; ++i) {
const auto type = g_gxState.vtxDesc[i];
if (type != GX_INDEX8 && type != GX_INDEX16) {
config.shaderConfig.attrMapping[i] = GX_VA_NULL;
continue;
}
const auto& array = g_gxState.arrays[i];
if (lastIndexedAttr >= 0 && array == g_gxState.arrays[lastIndexedAttr]) {
// Map attribute to previous attribute
config.shaderConfig.attrMapping[i] = config.shaderConfig.attrMapping[lastIndexedAttr];
} else {
// Map attribute to its own storage
config.shaderConfig.attrMapping[i] = static_cast<GXAttr>(i);
}
lastIndexedAttr = i;
}
config.shaderConfig.tevSwapTable = g_gxState.tevSwapTable;
for (u8 i = 0; i < g_gxState.numTevStages; ++i) {
config.shaderConfig.tevStages[i] = g_gxState.tevStages[i];
}
config.shaderConfig.tevStageCount = g_gxState.numTevStages;
for (u8 i = 0; i < g_gxState.numChans * 2; ++i) {
const auto& cc = g_gxState.colorChannelConfig[i];
if (cc.lightingEnabled) {
config.shaderConfig.colorChannels[i] = cc;
} else {
// Only matSrc matters when lighting disabled
config.shaderConfig.colorChannels[i] = {
.matSrc = cc.matSrc,
};
}
}
for (u8 i = 0; i < g_gxState.numTexGens; ++i) {
config.shaderConfig.tcgs[i] = g_gxState.tcgs[i];
}
if (g_gxState.alphaCompare) {
config.shaderConfig.alphaCompare = g_gxState.alphaCompare;
}
config.shaderConfig.indexedAttributeCount =
std::count_if(config.shaderConfig.vtxAttrs.begin(), config.shaderConfig.vtxAttrs.end(),
[](const auto type) { return type == GX_INDEX8 || type == GX_INDEX16; });
for (u8 i = 0; i < MaxTextures; ++i) {
const auto& bind = g_gxState.textures[i];
TextureConfig texConfig{};
if (bind.texObj.ref) {
if (requires_copy_conversion(bind.texObj)) {
texConfig.copyFmt = bind.texObj.ref->gxFormat;
}
if (requires_load_conversion(bind.texObj)) {
texConfig.loadFmt = bind.texObj.fmt;
}
texConfig.renderTex = bind.texObj.ref->isRenderTexture;
}
config.shaderConfig.textureConfig[i] = texConfig;
}
config = {
.shaderConfig = config.shaderConfig,
.primitive = primitive,
.depthFunc = g_gxState.depthFunc,
.cullMode = g_gxState.cullMode,
.blendMode = g_gxState.blendMode,
.blendFacSrc = g_gxState.blendFacSrc,
.blendFacDst = g_gxState.blendFacDst,
.blendOp = g_gxState.blendOp,
.dstAlpha = g_gxState.dstAlpha,
.depthCompare = g_gxState.depthCompare,
.depthUpdate = g_gxState.depthUpdate,
.alphaUpdate = g_gxState.alphaUpdate,
.colorUpdate = g_gxState.colorUpdate,
};
}
Range build_uniform(const ShaderInfo& info) noexcept {
auto [buf, range] = map_uniform(info.uniformSize);
{
buf.append(&g_gxState.pnMtx[g_gxState.currentPnMtx], 128);
buf.append(&g_gxState.proj, 64);
}
for (int i = 0; i < info.loadsTevReg.size(); ++i) {
if (!info.loadsTevReg.test(i)) {
continue;
}
buf.append(&g_gxState.colorRegs[i], 16);
}
bool lightingEnabled = false;
for (int i = 0; i < info.sampledColorChannels.size(); ++i) {
if (!info.sampledColorChannels.test(i)) {
continue;
}
const auto& ccc = g_gxState.colorChannelConfig[i * 2];
const auto& ccca = g_gxState.colorChannelConfig[i * 2 + 1];
if (ccc.lightingEnabled || ccca.lightingEnabled) {
lightingEnabled = true;
break;
}
}
if (lightingEnabled) {
// Lights
static_assert(sizeof(g_gxState.lights) == 80 * GX::MaxLights);
buf.append(&g_gxState.lights, 80 * GX::MaxLights);
// Light state for all channels
for (int i = 0; i < 4; ++i) {
u32 lightState = g_gxState.colorChannelState[i].lightMask.to_ulong();
buf.append(&lightState, 4);
}
}
for (int i = 0; i < info.sampledColorChannels.size(); ++i) {
if (!info.sampledColorChannels.test(i)) {
continue;
}
const auto& ccc = g_gxState.colorChannelConfig[i * 2];
const auto& ccs = g_gxState.colorChannelState[i * 2];
if (ccc.lightingEnabled && ccc.ambSrc == GX_SRC_REG) {
buf.append(&ccs.ambColor, 16);
}
if (ccc.matSrc == GX_SRC_REG) {
buf.append(&ccs.matColor, 16);
}
const auto& ccca = g_gxState.colorChannelConfig[i * 2 + 1];
const auto& ccsa = g_gxState.colorChannelState[i * 2 + 1];
if (ccca.lightingEnabled && ccca.ambSrc == GX_SRC_REG) {
buf.append(&ccsa.ambColor, 16);
}
if (ccca.matSrc == GX_SRC_REG) {
buf.append(&ccsa.matColor, 16);
}
}
for (int i = 0; i < info.sampledKColors.size(); ++i) {
if (!info.sampledKColors.test(i)) {
continue;
}
buf.append(&g_gxState.kcolors[i], 16);
}
for (int i = 0; i < info.usesTexMtx.size(); ++i) {
if (!info.usesTexMtx.test(i)) {
continue;
}
const auto& state = g_gxState;
switch (info.texMtxTypes[i]) {
case GX_TG_MTX2x4:
if (std::holds_alternative<Mat4x2<float>>(state.texMtxs[i])) {
buf.append(&std::get<Mat4x2<float>>(state.texMtxs[i]), 32);
} else if (std::holds_alternative<Mat4x4<float>>(g_gxState.texMtxs[i])) {
// TODO: SMB hits this?
Mat4x2<float> mtx{
{1.f, 0.f},
{0.f, 1.f},
{0.f, 0.f},
{0.f, 0.f},
};
buf.append(&mtx, 32);
} else {
Log.report(LOG_FATAL, FMT_STRING("expected 2x4 mtx in idx {}"), i);
unreachable();
}
break;
case GX_TG_MTX3x4:
if (std::holds_alternative<Mat4x4<float>>(g_gxState.texMtxs[i])) {
const auto& mat = std::get<Mat4x4<float>>(g_gxState.texMtxs[i]);
buf.append(&mat, 64);
} else {
Log.report(LOG_FATAL, FMT_STRING("expected 3x4 mtx in idx {}"), i);
buf.append(&Mat4x4_Identity, 64);
}
break;
default:
Log.report(LOG_FATAL, FMT_STRING("unhandled tex mtx type {}"), info.texMtxTypes[i]);
unreachable();
}
}
for (int i = 0; i < info.usesPTTexMtx.size(); ++i) {
if (!info.usesPTTexMtx.test(i)) {
continue;
}
buf.append(&g_gxState.ptTexMtxs[i], 64);
}
if (info.usesFog) {
const auto& state = g_gxState.fog;
struct Fog {
Vec4<float> color = state.color;
float a = 0.f;
float b = 0.5f;
float c = 0.f;
float pad = FLT_MAX;
} fog{};
static_assert(sizeof(Fog) == 32);
if (state.nearZ != state.farZ && state.startZ != state.endZ) {
const float depthRange = state.farZ - state.nearZ;
const float fogRange = state.endZ - state.startZ;
fog.a = (state.farZ * state.nearZ) / (depthRange * fogRange);
fog.b = state.farZ / depthRange;
fog.c = state.startZ / fogRange;
}
buf.append(&fog, 32);
}
for (int i = 0; i < info.sampledTextures.size(); ++i) {
if (!info.sampledTextures.test(i)) {
continue;
}
const auto& tex = get_texture(static_cast<GXTexMapID>(i));
if (!tex) {
Log.report(LOG_FATAL, FMT_STRING("unbound texture {}"), i);
unreachable();
}
buf.append(&tex.texObj.lodBias, 4);
}
return range;
}
static absl::flat_hash_map<u32, WGPUBindGroupLayout> sUniformBindGroupLayouts;
static absl::flat_hash_map<u32, std::pair<WGPUBindGroupLayout, WGPUBindGroupLayout>> sTextureBindGroupLayouts;
GXBindGroups build_bind_groups(const ShaderInfo& info, const ShaderConfig& config,
const BindGroupRanges& ranges) noexcept {
const auto layouts = build_bind_group_layouts(info, config);
std::array<WGPUBindGroupEntry, GX_VA_MAX_ATTR + 1> uniformEntries{
WGPUBindGroupEntry{
.binding = 0,
.buffer = g_uniformBuffer,
.size = info.uniformSize,
},
};
u32 uniformBindIdx = 1;
for (u32 i = 0; i < GX_VA_MAX_ATTR; ++i) {
const Range& range = ranges.vaRanges[i];
if (range.size <= 0) {
continue;
}
uniformEntries[uniformBindIdx] = WGPUBindGroupEntry{
.binding = uniformBindIdx,
.buffer = g_storageBuffer,
.size = range.size,
};
++uniformBindIdx;
}
std::array<WGPUBindGroupEntry, MaxTextures> samplerEntries;
std::array<WGPUBindGroupEntry, MaxTextures * 2> textureEntries;
u32 samplerCount = 0;
u32 textureCount = 0;
for (u32 i = 0; i < info.sampledTextures.size(); ++i) {
if (!info.sampledTextures.test(i)) {
continue;
}
const auto& tex = g_gxState.textures[i];
if (!tex) {
Log.report(LOG_FATAL, FMT_STRING("unbound texture {}"), i);
unreachable();
}
samplerEntries[samplerCount] = {
.binding = samplerCount,
.sampler = sampler_ref(tex.get_descriptor()),
};
++samplerCount;
textureEntries[textureCount] = {
.binding = textureCount,
.textureView = tex.texObj.ref->view,
};
++textureCount;
// Load palette
const auto& texConfig = config.textureConfig[i];
if (is_palette_format(texConfig.loadFmt)) {
u32 tlut = tex.texObj.tlut;
if (tlut < GX_TLUT0 || tlut > GX_TLUT7) {
Log.report(LOG_FATAL, FMT_STRING("tlut out of bounds {}"), tlut);
unreachable();
} else if (!g_gxState.tluts[tlut].ref) {
Log.report(LOG_FATAL, FMT_STRING("tlut unbound {}"), tlut);
unreachable();
}
textureEntries[textureCount] = {
.binding = textureCount,
.textureView = g_gxState.tluts[tlut].ref->view,
};
++textureCount;
}
}
return {
.uniformBindGroup = bind_group_ref(WGPUBindGroupDescriptor{
.label = "GX Uniform Bind Group",
.layout = layouts.uniformLayout,
.entryCount = uniformBindIdx,
.entries = uniformEntries.data(),
}),
.samplerBindGroup = bind_group_ref(WGPUBindGroupDescriptor{
.label = "GX Sampler Bind Group",
.layout = layouts.samplerLayout,
.entryCount = samplerCount,
.entries = samplerEntries.data(),
}),
.textureBindGroup = bind_group_ref(WGPUBindGroupDescriptor{
.label = "GX Texture Bind Group",
.layout = layouts.textureLayout,
.entryCount = textureCount,
.entries = textureEntries.data(),
}),
};
}
GXBindGroupLayouts build_bind_group_layouts(const ShaderInfo& info, const ShaderConfig& config) noexcept {
GXBindGroupLayouts out;
u32 uniformSizeKey = info.uniformSize + (config.indexedAttributeCount > 0 ? 1 : 0);
const auto uniformIt = sUniformBindGroupLayouts.find(uniformSizeKey);
if (uniformIt != sUniformBindGroupLayouts.end()) {
out.uniformLayout = uniformIt->second;
} else {
std::array<WGPUBindGroupLayoutEntry, GX_VA_MAX_ATTR + 1> uniformLayoutEntries{
WGPUBindGroupLayoutEntry{
.binding = 0,
.visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment,
.buffer =
WGPUBufferBindingLayout{
.type = WGPUBufferBindingType_Uniform,
.hasDynamicOffset = true,
.minBindingSize = info.uniformSize,
},
},
};
u32 bindIdx = 1;
for (int i = 0; i < GX_VA_MAX_ATTR; ++i) {
if (config.attrMapping[i] == static_cast<GXAttr>(i)) {
uniformLayoutEntries[bindIdx] = WGPUBindGroupLayoutEntry{
.binding = bindIdx,
.visibility = WGPUShaderStage_Vertex,
.buffer =
WGPUBufferBindingLayout{
.type = WGPUBufferBindingType_ReadOnlyStorage,
.hasDynamicOffset = true,
},
};
++bindIdx;
}
}
const auto uniformLayoutDescriptor = WGPUBindGroupLayoutDescriptor{
.label = "GX Uniform Bind Group Layout",
.entryCount = bindIdx,
.entries = uniformLayoutEntries.data(),
};
out.uniformLayout = wgpuDeviceCreateBindGroupLayout(g_device, &uniformLayoutDescriptor);
// sUniformBindGroupLayouts.try_emplace(uniformSizeKey, out.uniformLayout);
}
// u32 textureCount = info.sampledTextures.count();
// const auto textureIt = sTextureBindGroupLayouts.find(textureCount);
// if (textureIt != sTextureBindGroupLayouts.end()) {
// const auto& [sl, tl] = textureIt->second;
// out.samplerLayout = sl;
// out.textureLayout = tl;
// } else {
u32 numSamplers = 0;
u32 numTextures = 0;
std::array<WGPUBindGroupLayoutEntry, MaxTextures> samplerEntries;
std::array<WGPUBindGroupLayoutEntry, MaxTextures * 2> textureEntries;
for (u32 i = 0; i < info.sampledTextures.size(); ++i) {
if (!info.sampledTextures.test(i)) {
continue;
}
const auto& texConfig = config.textureConfig[i];
bool copyAsPalette = is_palette_format(texConfig.copyFmt);
bool loadAsPalette = is_palette_format(texConfig.loadFmt);
samplerEntries[numSamplers] = {
.binding = numSamplers,
.visibility = WGPUShaderStage_Fragment,
.sampler = {.type = copyAsPalette && loadAsPalette ? WGPUSamplerBindingType_NonFiltering
: WGPUSamplerBindingType_Filtering},
};
++numSamplers;
if (loadAsPalette) {
textureEntries[numTextures] = {
.binding = numTextures,
.visibility = WGPUShaderStage_Fragment,
.texture =
{
.sampleType = copyAsPalette ? WGPUTextureSampleType_Sint : WGPUTextureSampleType_Float,
.viewDimension = WGPUTextureViewDimension_2D,
},
};
++numTextures;
textureEntries[numTextures] = {
.binding = numTextures,
.visibility = WGPUShaderStage_Fragment,
.texture =
{
.sampleType = WGPUTextureSampleType_Float,
.viewDimension = WGPUTextureViewDimension_2D,
},
};
++numTextures;
} else {
textureEntries[numTextures] = {
.binding = numTextures,
.visibility = WGPUShaderStage_Fragment,
.texture =
{
.sampleType = WGPUTextureSampleType_Float,
.viewDimension = WGPUTextureViewDimension_2D,
},
};
++numTextures;
}
}
{
const WGPUBindGroupLayoutDescriptor descriptor{
.label = "GX Sampler Bind Group Layout",
.entryCount = numSamplers,
.entries = samplerEntries.data(),
};
out.samplerLayout = wgpuDeviceCreateBindGroupLayout(g_device, &descriptor);
}
{
const WGPUBindGroupLayoutDescriptor descriptor{
.label = "GX Texture Bind Group Layout",
.entryCount = numTextures,
.entries = textureEntries.data(),
};
out.textureLayout = wgpuDeviceCreateBindGroupLayout(g_device, &descriptor);
}
// sTextureBindGroupLayouts.try_emplace(textureCount, out.samplerLayout, out.textureLayout);
// }
return out;
}
// TODO this is awkward
extern absl::flat_hash_map<ShaderRef, std::pair<WGPUShaderModule, gx::ShaderInfo>> g_gxCachedShaders;
void shutdown() noexcept {
// TODO we should probably store this all in g_state.gx instead
for (const auto& item : sUniformBindGroupLayouts) {
wgpuBindGroupLayoutRelease(item.second);
}
sUniformBindGroupLayouts.clear();
for (const auto& item : sTextureBindGroupLayouts) {
wgpuBindGroupLayoutRelease(item.second.first);
wgpuBindGroupLayoutRelease(item.second.second);
}
sTextureBindGroupLayouts.clear();
for (auto& item : g_gxState.textures) {
item.texObj.ref.reset();
}
for (auto& item : g_gxState.tluts) {
item.ref.reset();
}
for (const auto& item : g_gxCachedShaders) {
wgpuShaderModuleRelease(item.second.first);
}
g_gxCachedShaders.clear();
}
} // namespace gx
static WGPUAddressMode wgpu_address_mode(GXTexWrapMode mode) {
switch (mode) {
case GX_CLAMP:
return WGPUAddressMode_ClampToEdge;
case GX_REPEAT:
return WGPUAddressMode_Repeat;
case GX_MIRROR:
return WGPUAddressMode_MirrorRepeat;
default:
Log.report(LOG_FATAL, FMT_STRING("invalid wrap mode {}"), mode);
unreachable();
}
}
static std::pair<WGPUFilterMode, WGPUFilterMode> wgpu_filter_mode(GXTexFilter filter) {
switch (filter) {
case GX_NEAR:
return {WGPUFilterMode_Nearest, WGPUFilterMode_Linear};
case GX_LINEAR:
return {WGPUFilterMode_Linear, WGPUFilterMode_Linear};
case GX_NEAR_MIP_NEAR:
return {WGPUFilterMode_Nearest, WGPUFilterMode_Nearest};
case GX_LIN_MIP_NEAR:
return {WGPUFilterMode_Linear, WGPUFilterMode_Nearest};
case GX_NEAR_MIP_LIN:
return {WGPUFilterMode_Nearest, WGPUFilterMode_Linear};
case GX_LIN_MIP_LIN:
return {WGPUFilterMode_Linear, WGPUFilterMode_Linear};
default:
Log.report(LOG_FATAL, FMT_STRING("invalid filter mode {}"), filter);
unreachable();
}
}
static u16 wgpu_aniso(GXAnisotropy aniso) {
switch (aniso) {
case GX_ANISO_1:
return 1;
case GX_ANISO_2:
return std::max<u16>(webgpu::g_graphicsConfig.textureAnisotropy / 2, 1);
case GX_ANISO_4:
return std::max<u16>(webgpu::g_graphicsConfig.textureAnisotropy, 1);
default:
Log.report(LOG_FATAL, FMT_STRING("invalid aniso mode {}"), aniso);
unreachable();
}
}
WGPUSamplerDescriptor TextureBind::get_descriptor() const noexcept {
if (gx::requires_copy_conversion(texObj) && gx::is_palette_format(texObj.ref->gxFormat)) {
return {
.label = "Generated Non-Filtering Sampler",
.addressModeU = wgpu_address_mode(texObj.wrapS),
.addressModeV = wgpu_address_mode(texObj.wrapT),
.addressModeW = WGPUAddressMode_Repeat,
.magFilter = WGPUFilterMode_Nearest,
.minFilter = WGPUFilterMode_Nearest,
.mipmapFilter = WGPUFilterMode_Nearest,
.lodMinClamp = 0.f,
.lodMaxClamp = 1000.f,
.maxAnisotropy = 1,
};
}
const auto [minFilter, mipFilter] = wgpu_filter_mode(texObj.minFilter);
const auto [magFilter, _] = wgpu_filter_mode(texObj.magFilter);
return {
.label = "Generated Filtering Sampler",
.addressModeU = wgpu_address_mode(texObj.wrapS),
.addressModeV = wgpu_address_mode(texObj.wrapT),
.addressModeW = WGPUAddressMode_Repeat,
.magFilter = magFilter,
.minFilter = minFilter,
.mipmapFilter = mipFilter,
.lodMinClamp = 0.f,
.lodMaxClamp = 1000.f,
.maxAnisotropy = wgpu_aniso(texObj.maxAniso),
};
}
} // namespace aurora::gfx

402
lib/gfx/gx.hpp Normal file
View File

@ -0,0 +1,402 @@
#pragma once
#include <dolphin/gx.h>
#include <aurora/math.hpp>
#include "common.hpp"
#include "../internal.hpp"
#include "texture.hpp"
#include <type_traits>
#include <utility>
#include <variant>
#include <cstring>
#include <bitset>
#include <memory>
#include <array>
#define M_PIF 3.14159265358979323846f
namespace GX {
constexpr u8 MaxLights = 8;
using LightMask = std::bitset<MaxLights>;
} // namespace GX
struct GXLightObj_ {
GXColor color;
float a0 = 1.f;
float a1 = 0.f;
float a2 = 0.f;
float k0 = 1.f;
float k1 = 0.f;
float k2 = 0.f;
float px = 0.f;
float py = 0.f;
float pz = 0.f;
float nx = 0.f;
float ny = 0.f;
float nz = 0.f;
};
static_assert(sizeof(GXLightObj_) <= sizeof(GXLightObj), "GXLightObj too small!");
#if GX_IS_WII
constexpr float GX_LARGE_NUMBER = -1.0e+18f;
#else
constexpr float GX_LARGE_NUMBER = -1048576.0f;
#endif
namespace aurora::gfx::gx {
constexpr u32 MaxTextures = GX_MAX_TEXMAP;
constexpr u32 MaxTevStages = GX_MAX_TEVSTAGE;
constexpr u32 MaxColorChannels = 4;
constexpr u32 MaxTevRegs = 4; // TEVPREV, TEVREG0-2
constexpr u32 MaxKColors = GX_MAX_KCOLOR;
constexpr u32 MaxTexMtx = 10;
constexpr u32 MaxPTTexMtx = 20;
constexpr u32 MaxTexCoord = GX_MAX_TEXCOORD;
constexpr u32 MaxVtxAttr = GX_VA_MAX_ATTR;
constexpr u32 MaxTevSwap = GX_MAX_TEVSWAP;
constexpr u32 MaxIndStages = GX_MAX_INDTEXSTAGE;
constexpr u32 MaxIndTexMtxs = 3;
constexpr u32 MaxVtxFmt = GX_MAX_VTXFMT;
constexpr u32 MaxPnMtx = (GX_PNMTX9 / 3) + 1;
template <typename Arg, Arg Default>
struct TevPass {
Arg a = Default;
Arg b = Default;
Arg c = Default;
Arg d = Default;
bool operator==(const TevPass& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }
};
static_assert(std::has_unique_object_representations_v<TevPass<GXTevColorArg, GX_CC_ZERO>>);
static_assert(std::has_unique_object_representations_v<TevPass<GXTevAlphaArg, GX_CA_ZERO>>);
struct TevOp {
GXTevOp op = GX_TEV_ADD;
GXTevBias bias = GX_TB_ZERO;
GXTevScale scale = GX_CS_SCALE_1;
GXTevRegID outReg = GX_TEVPREV;
bool clamp = true;
u8 _p1 = 0;
u8 _p2 = 0;
u8 _p3 = 0;
bool operator==(const TevOp& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }
};
static_assert(std::has_unique_object_representations_v<TevOp>);
struct TevStage {
TevPass<GXTevColorArg, GX_CC_ZERO> colorPass;
TevPass<GXTevAlphaArg, GX_CA_ZERO> alphaPass;
TevOp colorOp;
TevOp alphaOp;
GXTevKColorSel kcSel = GX_TEV_KCSEL_1;
GXTevKAlphaSel kaSel = GX_TEV_KASEL_1;
GXTexCoordID texCoordId = GX_TEXCOORD_NULL;
GXTexMapID texMapId = GX_TEXMAP_NULL;
GXChannelID channelId = GX_COLOR_NULL;
GXTevSwapSel tevSwapRas = GX_TEV_SWAP0;
GXTevSwapSel tevSwapTex = GX_TEV_SWAP0;
GXIndTexStageID indTexStage = GX_INDTEXSTAGE0;
GXIndTexFormat indTexFormat = GX_ITF_8;
GXIndTexBiasSel indTexBiasSel = GX_ITB_NONE;
GXIndTexAlphaSel indTexAlphaSel = GX_ITBA_OFF;
GXIndTexMtxID indTexMtxId = GX_ITM_OFF;
GXIndTexWrap indTexWrapS = GX_ITW_OFF;
GXIndTexWrap indTexWrapT = GX_ITW_OFF;
bool indTexUseOrigLOD = false;
bool indTexAddPrev = false;
u8 _p1 = 0;
u8 _p2 = 0;
bool operator==(const TevStage& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }
};
static_assert(std::has_unique_object_representations_v<TevStage>);
struct IndStage {
GXTexCoordID texCoordId;
GXTexMapID texMapId;
GXIndTexScale scaleS;
GXIndTexScale scaleT;
};
static_assert(std::has_unique_object_representations_v<IndStage>);
// For shader generation
struct ColorChannelConfig {
GXColorSrc matSrc = GX_SRC_REG;
GXColorSrc ambSrc = GX_SRC_REG;
GXDiffuseFn diffFn = GX_DF_NONE;
GXAttnFn attnFn = GX_AF_NONE;
bool lightingEnabled = false;
u8 _p1 = 0;
u8 _p2 = 0;
u8 _p3 = 0;
bool operator==(const ColorChannelConfig& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }
};
static_assert(std::has_unique_object_representations_v<ColorChannelConfig>);
// For uniform generation
struct ColorChannelState {
Vec4<float> matColor;
Vec4<float> ambColor;
GX::LightMask lightMask;
};
// Mat4x4 used instead of Mat4x3 for padding purposes
using TexMtxVariant = std::variant<std::monostate, Mat4x2<float>, Mat4x4<float>>;
struct TcgConfig {
GXTexGenType type = GX_TG_MTX2x4;
GXTexGenSrc src = GX_MAX_TEXGENSRC;
GXTexMtx mtx = GX_IDENTITY;
GXPTTexMtx postMtx = GX_PTIDENTITY;
bool normalize = false;
u8 _p1 = 0;
u8 _p2 = 0;
u8 _p3 = 0;
bool operator==(const TcgConfig& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }
};
static_assert(std::has_unique_object_representations_v<TcgConfig>);
struct FogState {
GXFogType type = GX_FOG_NONE;
float startZ = 0.f;
float endZ = 0.f;
float nearZ = 0.f;
float farZ = 0.f;
Vec4<float> color;
bool operator==(const FogState& rhs) const {
return type == rhs.type && startZ == rhs.startZ && endZ == rhs.endZ && nearZ == rhs.nearZ && farZ == rhs.farZ &&
color == rhs.color;
}
};
struct TevSwap {
GXTevColorChan red = GX_CH_RED;
GXTevColorChan green = GX_CH_GREEN;
GXTevColorChan blue = GX_CH_BLUE;
GXTevColorChan alpha = GX_CH_ALPHA;
bool operator==(const TevSwap& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }
explicit operator bool() const { return !(*this == TevSwap{}); }
};
static_assert(std::has_unique_object_representations_v<TevSwap>);
struct AlphaCompare {
GXCompare comp0 = GX_ALWAYS;
u32 ref0; // would be u8 but extended to avoid padding bytes
GXAlphaOp op = GX_AOP_AND;
GXCompare comp1 = GX_ALWAYS;
u32 ref1;
bool operator==(const AlphaCompare& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }
explicit operator bool() const { return comp0 != GX_ALWAYS || comp1 != GX_ALWAYS; }
};
static_assert(std::has_unique_object_representations_v<AlphaCompare>);
struct IndTexMtxInfo {
aurora::Mat3x2<float> mtx;
s8 scaleExp;
bool operator==(const IndTexMtxInfo& rhs) const { return mtx == rhs.mtx && scaleExp == rhs.scaleExp; }
};
struct VtxAttrFmt {
GXCompCnt cnt;
GXCompType type;
u8 frac;
};
struct VtxFmt {
std::array<VtxAttrFmt, MaxVtxAttr> attrs;
};
struct PnMtx {
Mat4x4<float> pos;
Mat4x4<float> nrm;
};
static_assert(sizeof(PnMtx) == sizeof(Mat4x4<float>) * 2);
struct Light {
Vec4<float> pos{0.f, 0.f, 0.f};
Vec4<float> dir{0.f, 0.f, 0.f};
Vec4<float> color{0.f, 0.f, 0.f, 0.f};
Vec4<float> cosAtt{0.f, 0.f, 0.f};
Vec4<float> distAtt{0.f, 0.f, 0.f};
bool operator==(const Light& rhs) const {
return pos == rhs.pos && dir == rhs.dir && color == rhs.color && cosAtt == rhs.cosAtt && distAtt == rhs.distAtt;
}
};
static_assert(sizeof(Light) == 80);
struct AttrArray {
const void* data;
u32 size;
u8 stride;
Range cachedRange;
};
inline bool operator==(const AttrArray& lhs, const AttrArray& rhs) {
return lhs.data == rhs.data && lhs.size == rhs.size && lhs.stride == rhs.stride;
}
struct GXState {
std::array<PnMtx, MaxPnMtx> pnMtx;
u32 currentPnMtx;
Mat4x4<float> proj;
Mat4x4<float> origProj; // for GXGetProjectionv
GXProjectionType projType; // for GXGetProjectionv
FogState fog;
GXCullMode cullMode = GX_CULL_BACK;
GXBlendMode blendMode = GX_BM_NONE;
GXBlendFactor blendFacSrc = GX_BL_SRCALPHA;
GXBlendFactor blendFacDst = GX_BL_INVSRCALPHA;
GXLogicOp blendOp = GX_LO_CLEAR;
GXCompare depthFunc = GX_LEQUAL;
Vec4<float> clearColor{0.f, 0.f, 0.f, 1.f};
u32 dstAlpha; // u8; UINT32_MAX = disabled
AlphaCompare alphaCompare;
std::array<Vec4<float>, MaxTevRegs> colorRegs;
std::array<Vec4<float>, GX_MAX_KCOLOR> kcolors;
std::array<ColorChannelConfig, MaxColorChannels> colorChannelConfig;
std::array<ColorChannelState, MaxColorChannels> colorChannelState;
std::array<Light, GX::MaxLights> lights;
std::array<TevStage, MaxTevStages> tevStages;
std::array<TextureBind, MaxTextures> textures;
std::array<GXTlutObj_, MaxTextures> tluts;
std::array<TexMtxVariant, MaxTexMtx> texMtxs;
std::array<Mat4x4<float>, MaxPTTexMtx> ptTexMtxs;
std::array<TcgConfig, MaxTexCoord> tcgs;
std::array<GXAttrType, MaxVtxAttr> vtxDesc;
std::array<VtxFmt, MaxVtxFmt> vtxFmts;
std::array<TevSwap, MaxTevSwap> tevSwapTable{
TevSwap{},
TevSwap{GX_CH_RED, GX_CH_RED, GX_CH_RED, GX_CH_ALPHA},
TevSwap{GX_CH_GREEN, GX_CH_GREEN, GX_CH_GREEN, GX_CH_ALPHA},
TevSwap{GX_CH_BLUE, GX_CH_BLUE, GX_CH_BLUE, GX_CH_ALPHA},
};
std::array<IndStage, MaxIndStages> indStages;
std::array<IndTexMtxInfo, MaxIndTexMtxs> indTexMtxs;
std::array<AttrArray, GX_VA_MAX_ATTR> arrays;
bool depthCompare = true;
bool depthUpdate = true;
bool colorUpdate = true;
bool alphaUpdate = true;
u8 numChans = 0;
u8 numIndStages = 0;
u8 numTevStages = 0;
u8 numTexGens = 0;
bool stateDirty = true;
};
extern GXState g_gxState;
void shutdown() noexcept;
const TextureBind& get_texture(GXTexMapID id) noexcept;
static inline bool requires_copy_conversion(const GXTexObj_& obj) {
if (!obj.ref) {
return false;
}
if (obj.ref->isRenderTexture) {
return true;
}
switch (obj.ref->gxFormat) {
// case GX_TF_RGB565:
// case GX_TF_I4:
// case GX_TF_I8:
case GX_TF_C4:
case GX_TF_C8:
case GX_TF_C14X2:
return true;
default:
return false;
}
}
static inline bool requires_load_conversion(const GXTexObj_& obj) {
if (!obj.ref) {
return false;
}
switch (obj.fmt) {
case GX_TF_I4:
case GX_TF_I8:
case GX_TF_C4:
case GX_TF_C8:
case GX_TF_C14X2:
return true;
default:
return false;
}
}
static inline bool is_palette_format(u32 fmt) { return fmt == GX_TF_C4 || fmt == GX_TF_C8 || fmt == GX_TF_C14X2; }
struct TextureConfig {
u32 copyFmt = InvalidTextureFormat; // Underlying texture format
u32 loadFmt = InvalidTextureFormat; // Texture format being bound
bool renderTex = false; // Perform conversion
u8 _p1 = 0;
u8 _p2 = 0;
u8 _p3 = 0;
bool operator==(const TextureConfig& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }
};
static_assert(std::has_unique_object_representations_v<TextureConfig>);
struct ShaderConfig {
GXFogType fogType;
std::array<GXAttrType, MaxVtxAttr> vtxAttrs;
// Mapping for indexed attributes -> storage buffer
std::array<GXAttr, MaxVtxAttr> attrMapping;
std::array<TevSwap, MaxTevSwap> tevSwapTable;
std::array<TevStage, MaxTevStages> tevStages;
u32 tevStageCount = 0;
std::array<ColorChannelConfig, MaxColorChannels> colorChannels;
std::array<TcgConfig, MaxTexCoord> tcgs;
AlphaCompare alphaCompare;
u32 indexedAttributeCount = 0;
std::array<TextureConfig, MaxTextures> textureConfig;
bool operator==(const ShaderConfig& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; }
};
static_assert(std::has_unique_object_representations_v<ShaderConfig>);
constexpr u32 GXPipelineConfigVersion = 4;
struct PipelineConfig {
u32 version = GXPipelineConfigVersion;
ShaderConfig shaderConfig;
GXPrimitive primitive;
GXCompare depthFunc;
GXCullMode cullMode;
GXBlendMode blendMode;
GXBlendFactor blendFacSrc, blendFacDst;
GXLogicOp blendOp;
u32 dstAlpha;
bool depthCompare, depthUpdate, alphaUpdate, colorUpdate;
};
static_assert(std::has_unique_object_representations_v<PipelineConfig>);
struct GXBindGroupLayouts {
WGPUBindGroupLayout uniformLayout;
WGPUBindGroupLayout samplerLayout;
WGPUBindGroupLayout textureLayout;
};
struct GXBindGroups {
BindGroupRef uniformBindGroup;
BindGroupRef samplerBindGroup;
BindGroupRef textureBindGroup;
};
// Output info from shader generation
struct ShaderInfo {
std::bitset<MaxTexCoord> sampledTexCoords;
std::bitset<MaxTextures> sampledTextures;
std::bitset<MaxKColors> sampledKColors;
std::bitset<MaxColorChannels / 2> sampledColorChannels;
std::bitset<MaxTevRegs> loadsTevReg;
std::bitset<MaxTevRegs> writesTevReg;
std::bitset<MaxTexMtx> usesTexMtx;
std::bitset<MaxPTTexMtx> usesPTTexMtx;
std::array<GXTexGenType, MaxTexMtx> texMtxTypes{};
u32 uniformSize = 0;
bool usesFog : 1 = false;
};
struct BindGroupRanges {
std::array<Range, GX_VA_MAX_ATTR> vaRanges{};
};
void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive) noexcept;
WGPURenderPipeline build_pipeline(const PipelineConfig& config, const ShaderInfo& info,
ArrayRef<WGPUVertexBufferLayout> vtxBuffers, WGPUShaderModule shader,
const char* label) noexcept;
ShaderInfo build_shader_info(const ShaderConfig& config) noexcept;
WGPUShaderModule build_shader(const ShaderConfig& config, const ShaderInfo& info) noexcept;
// Range build_vertex_buffer(const GXShaderInfo& info) noexcept;
Range build_uniform(const ShaderInfo& info) noexcept;
GXBindGroupLayouts build_bind_group_layouts(const ShaderInfo& info, const ShaderConfig& config) noexcept;
GXBindGroups build_bind_groups(const ShaderInfo& info, const ShaderConfig& config,
const BindGroupRanges& ranges) noexcept;
} // namespace aurora::gfx::gx

1392
lib/gfx/gx_shader.cpp Normal file

File diff suppressed because it is too large Load Diff

499
lib/gfx/model/shader.cpp Normal file
View File

@ -0,0 +1,499 @@
#include "shader.hpp"
#include "../../webgpu/gpu.hpp"
#include <absl/container/flat_hash_map.h>
namespace aurora::gfx::model {
static Module Log("aurora::gfx::model");
template <typename T>
constexpr T bswap16(T val) noexcept {
static_assert(sizeof(T) == sizeof(u16));
union {
u16 u;
T t;
} v{.t = val};
#if __GNUC__
v.u = __builtin_bswap16(v.u);
#elif _WIN32
v.u = _byteswap_ushort(v.u);
#else
v.u = (v.u << 8) | ((v.u >> 8) & 0xFF);
#endif
return v.t;
}
template <typename T>
constexpr T bswap32(T val) noexcept {
static_assert(sizeof(T) == sizeof(u32));
union {
u32 u;
T t;
} v{.t = val};
#if __GNUC__
v.u = __builtin_bswap32(v.u);
#elif _WIN32
v.u = _byteswap_ulong(v.u);
#else
v.u = ((v.u & 0x0000FFFF) << 16) | ((v.u & 0xFFFF0000) >> 16) | ((v.u & 0x00FF00FF) << 8) | ((v.u & 0xFF00FF00) >> 8);
#endif
return v.t;
}
using IndexedAttrs = std::array<bool, GX_VA_MAX_ATTR>;
struct DisplayListCache {
ByteBuffer vtxBuf;
ByteBuffer idxBuf;
IndexedAttrs indexedAttrs;
DisplayListCache(ByteBuffer&& vtxBuf, ByteBuffer&& idxBuf, IndexedAttrs indexedAttrs)
: vtxBuf(std::move(vtxBuf)), idxBuf(std::move(idxBuf)), indexedAttrs(indexedAttrs) {}
};
static absl::flat_hash_map<HashType, DisplayListCache> sCachedDisplayLists;
static u32 prepare_vtx_buffer(ByteBuffer& buf, GXVtxFmt vtxfmt, const u8* ptr, u16 vtxCount,
IndexedAttrs& indexedAttrs) {
using aurora::gfx::gx::g_gxState;
struct {
u8 count;
GXCompType type;
} attrArrays[GX_VA_MAX_ATTR] = {};
u32 vtxSize = 0;
u32 outVtxSize = 0;
// Calculate attribute offsets and vertex size
for (int attr = 0; attr < GX_VA_MAX_ATTR; attr++) {
const auto& attrFmt = g_gxState.vtxFmts[vtxfmt].attrs[attr];
switch (g_gxState.vtxDesc[attr]) {
case GX_NONE:
break;
case GX_DIRECT:
#define COMBINE(val1, val2, val3) (((val1) << 16) | ((val2) << 8) | (val3))
switch (COMBINE(attr, attrFmt.cnt, attrFmt.type)) {
case COMBINE(GX_VA_POS, GX_POS_XYZ, GX_F32):
case COMBINE(GX_VA_NRM, GX_NRM_XYZ, GX_F32):
attrArrays[attr].count = 3;
attrArrays[attr].type = GX_F32;
vtxSize += 12;
outVtxSize += 12;
break;
case COMBINE(GX_VA_POS, GX_POS_XYZ, GX_S16):
case COMBINE(GX_VA_NRM, GX_NRM_XYZ, GX_S16):
attrArrays[attr].count = 3;
attrArrays[attr].type = GX_S16;
vtxSize += 6;
outVtxSize += 12;
break;
case COMBINE(GX_VA_TEX0, GX_TEX_ST, GX_F32):
case COMBINE(GX_VA_TEX1, GX_TEX_ST, GX_F32):
case COMBINE(GX_VA_TEX2, GX_TEX_ST, GX_F32):
case COMBINE(GX_VA_TEX3, GX_TEX_ST, GX_F32):
case COMBINE(GX_VA_TEX4, GX_TEX_ST, GX_F32):
case COMBINE(GX_VA_TEX5, GX_TEX_ST, GX_F32):
case COMBINE(GX_VA_TEX6, GX_TEX_ST, GX_F32):
case COMBINE(GX_VA_TEX7, GX_TEX_ST, GX_F32):
attrArrays[attr].count = 2;
attrArrays[attr].type = GX_F32;
vtxSize += 8;
outVtxSize += 8;
break;
case COMBINE(GX_VA_TEX0, GX_TEX_ST, GX_S16):
case COMBINE(GX_VA_TEX1, GX_TEX_ST, GX_S16):
case COMBINE(GX_VA_TEX2, GX_TEX_ST, GX_S16):
case COMBINE(GX_VA_TEX3, GX_TEX_ST, GX_S16):
case COMBINE(GX_VA_TEX4, GX_TEX_ST, GX_S16):
case COMBINE(GX_VA_TEX5, GX_TEX_ST, GX_S16):
case COMBINE(GX_VA_TEX6, GX_TEX_ST, GX_S16):
case COMBINE(GX_VA_TEX7, GX_TEX_ST, GX_S16):
attrArrays[attr].count = 2;
attrArrays[attr].type = GX_S16;
vtxSize += 4;
outVtxSize += 8;
break;
case COMBINE(GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8):
case COMBINE(GX_VA_CLR1, GX_CLR_RGBA, GX_RGBA8):
attrArrays[attr].count = 4;
attrArrays[attr].type = GX_RGBA8;
vtxSize += 4;
outVtxSize += 16;
break;
default:
Log.report(LOG_FATAL, FMT_STRING("not handled: attr {}, cnt {}, type {}"), attr, attrFmt.cnt, attrFmt.type);
break;
}
#undef COMBINE
break;
case GX_INDEX8:
++vtxSize;
outVtxSize += 2;
indexedAttrs[attr] = true;
break;
case GX_INDEX16:
vtxSize += 2;
outVtxSize += 2;
indexedAttrs[attr] = true;
break;
default:
Log.report(LOG_FATAL, FMT_STRING("unhandled attribute type {}"), g_gxState.vtxDesc[attr]);
}
}
// Align to 4
int rem = outVtxSize % 4;
int padding = 0;
if (rem != 0) {
padding = 4 - rem;
outVtxSize += padding;
}
// Build vertex buffer
buf.reserve_extra(vtxCount * outVtxSize);
std::array<f32, 4> out{};
for (u32 v = 0; v < vtxCount; ++v) {
for (int attr = 0; attr < GX_VA_MAX_ATTR; attr++) {
if (g_gxState.vtxDesc[attr] == GX_INDEX8) {
u16 index = *ptr;
buf.append(&index, 2);
++ptr;
} else if (g_gxState.vtxDesc[attr] == GX_INDEX16) {
u16 index = bswap16(*reinterpret_cast<const u16*>(ptr));
buf.append(&index, 2);
ptr += 2;
}
if (g_gxState.vtxDesc[attr] != GX_DIRECT) {
continue;
}
const auto& attrFmt = g_gxState.vtxFmts[vtxfmt].attrs[attr];
u8 count = attrArrays[attr].count;
switch (attrArrays[attr].type) {
case GX_U8:
for (int i = 0; i < count; ++i) {
const auto value = reinterpret_cast<const u8*>(ptr)[i];
out[i] = static_cast<f32>(value) / static_cast<f32>(1 << attrFmt.frac);
}
buf.append(out.data(), sizeof(f32) * count);
ptr += count;
break;
case GX_S8:
for (int i = 0; i < count; ++i) {
const auto value = reinterpret_cast<const s8*>(ptr)[i];
out[i] = static_cast<f32>(value) / static_cast<f32>(1 << attrFmt.frac);
}
buf.append(out.data(), sizeof(f32) * count);
ptr += count;
break;
case GX_U16:
for (int i = 0; i < count; ++i) {
const auto value = bswap16(reinterpret_cast<const u16*>(ptr)[i]);
out[i] = static_cast<f32>(value) / static_cast<f32>(1 << attrFmt.frac);
}
buf.append(out.data(), sizeof(f32) * count);
ptr += count * sizeof(u16);
break;
case GX_S16:
for (int i = 0; i < count; ++i) {
const auto value = bswap16(reinterpret_cast<const s16*>(ptr)[i]);
out[i] = static_cast<f32>(value) / static_cast<f32>(1 << attrFmt.frac);
}
buf.append(out.data(), sizeof(f32) * count);
ptr += count * sizeof(s16);
break;
case GX_F32:
for (int i = 0; i < count; ++i) {
out[i] = bswap32(reinterpret_cast<const f32*>(ptr)[i]);
}
buf.append(out.data(), sizeof(f32) * count);
ptr += count * sizeof(f32);
break;
case GX_RGBA8:
out[0] = static_cast<f32>(ptr[0]) / 255.f;
out[1] = static_cast<f32>(ptr[1]) / 255.f;
out[2] = static_cast<f32>(ptr[2]) / 255.f;
out[3] = static_cast<f32>(ptr[3]) / 255.f;
buf.append(out.data(), sizeof(f32) * 4);
ptr += sizeof(u32);
break;
}
}
if (padding > 0) {
buf.append_zeroes(padding);
}
}
return vtxSize;
}
static u16 prepare_idx_buffer(ByteBuffer& buf, GXPrimitive prim, u16 vtxStart, u16 vtxCount) {
u16 numIndices = 0;
if (prim == GX_TRIANGLES) {
buf.reserve_extra(vtxCount * sizeof(u16));
for (u16 v = 0; v < vtxCount; ++v) {
const u16 idx = vtxStart + v;
buf.append(&idx, sizeof(u16));
++numIndices;
}
} else if (prim == GX_TRIANGLEFAN) {
buf.reserve_extra(((u32(vtxCount) - 3) * 3 + 3) * sizeof(u16));
for (u16 v = 0; v < vtxCount; ++v) {
const u16 idx = vtxStart + v;
if (v < 3) {
buf.append(&idx, sizeof(u16));
++numIndices;
continue;
}
const std::array<u16, 3> idxs{vtxStart, u16(idx - 1), idx};
buf.append(idxs.data(), sizeof(u16) * 3);
numIndices += 3;
}
} else if (prim == GX_TRIANGLESTRIP) {
buf.reserve_extra(((u32(vtxCount) - 3) * 3 + 3) * sizeof(u16));
for (u16 v = 0; v < vtxCount; ++v) {
const u16 idx = vtxStart + v;
if (v < 3) {
buf.append(&idx, sizeof(u16));
++numIndices;
continue;
}
if ((v & 1) == 0) {
const std::array<u16, 3> idxs{u16(idx - 2), u16(idx - 1), idx};
buf.append(idxs.data(), sizeof(u16) * 3);
} else {
const std::array<u16, 3> idxs{u16(idx - 1), u16(idx - 2), idx};
buf.append(idxs.data(), sizeof(u16) * 3);
}
numIndices += 3;
}
} else {
Log.report(LOG_FATAL, FMT_STRING("Unsupported primitive type {}"), static_cast<u32>(prim));
}
return numIndices;
}
void queue_surface(const u8* dlStart, u32 dlSize) noexcept {
const auto hash = xxh3_hash_s(dlStart, dlSize, 0);
Range vertRange, idxRange;
u32 numIndices = 0;
IndexedAttrs indexedAttrs{};
auto it = sCachedDisplayLists.find(hash);
if (it != sCachedDisplayLists.end()) {
const auto& cache = it->second;
numIndices = cache.idxBuf.size() / 2;
vertRange = push_verts(cache.vtxBuf.data(), cache.vtxBuf.size());
idxRange = push_indices(cache.idxBuf.data(), cache.idxBuf.size());
indexedAttrs = cache.indexedAttrs;
} else {
const u8* data = dlStart;
u32 pos = 0;
ByteBuffer vtxBuf;
ByteBuffer idxBuf;
u16 vtxStart = 0;
while (pos < dlSize) {
u8 cmd = data[pos++];
u8 opcode = cmd & GX_OPCODE_MASK;
switch (opcode) {
case GX_NOP:
continue;
case GX_LOAD_BP_REG:
// TODO?
pos += 4;
break;
case GX_DRAW_QUADS:
case GX_DRAW_TRIANGLES:
case GX_DRAW_TRIANGLE_STRIP:
case GX_DRAW_TRIANGLE_FAN: {
const auto prim = static_cast<GXPrimitive>(opcode);
const auto fmt = static_cast<GXVtxFmt>(cmd & GX_VAT_MASK);
u16 vtxCount = bswap16(*reinterpret_cast<const u16*>(data + pos));
pos += 2;
pos += vtxCount * prepare_vtx_buffer(vtxBuf, fmt, data + pos, vtxCount, indexedAttrs);
numIndices += prepare_idx_buffer(idxBuf, prim, vtxStart, vtxCount);
vtxStart += vtxCount;
break;
}
case GX_DRAW_LINES:
case GX_DRAW_LINE_STRIP:
case GX_DRAW_POINTS:
Log.report(LOG_FATAL, FMT_STRING("unimplemented prim type: {}"), opcode);
break;
default:
Log.report(LOG_FATAL, FMT_STRING("unimplemented opcode: {}"), opcode);
break;
}
}
vertRange = push_verts(vtxBuf.data(), vtxBuf.size());
idxRange = push_indices(idxBuf.data(), idxBuf.size());
sCachedDisplayLists.try_emplace(hash, std::move(vtxBuf), std::move(idxBuf), indexedAttrs);
}
gx::BindGroupRanges ranges{};
int lastIndexedAttr = -1;
for (int i = 0; i < GX_VA_MAX_ATTR; ++i) {
if (!indexedAttrs[i]) {
continue;
}
auto& array = gx::g_gxState.arrays[i];
if (lastIndexedAttr >= 0 && array == gx::g_gxState.arrays[lastIndexedAttr]) {
// Reuse range from last attribute in shader
// Don't set the output range, so it remains unbound
const auto range = gx::g_gxState.arrays[lastIndexedAttr].cachedRange;
array.cachedRange = range;
} else if (array.cachedRange.size > 0) {
// Use the currently cached range
ranges.vaRanges[i] = array.cachedRange;
} else {
// Push array data to storage and cache range
const auto range = push_storage(static_cast<const uint8_t*>(array.data), array.size);
ranges.vaRanges[i] = range;
array.cachedRange = range;
}
lastIndexedAttr = i;
}
model::PipelineConfig config{};
populate_pipeline_config(config, GX_TRIANGLES);
const auto info = gx::build_shader_info(config.shaderConfig);
const auto bindGroups = gx::build_bind_groups(info, config.shaderConfig, ranges);
const auto pipeline = pipeline_ref(config);
push_draw_command(model::DrawData{
.pipeline = pipeline,
.vertRange = vertRange,
.idxRange = idxRange,
.dataRanges = ranges,
.uniformRange = build_uniform(info),
.indexCount = numIndices,
.bindGroups = bindGroups,
.dstAlpha = gx::g_gxState.dstAlpha,
});
}
State construct_state() { return {}; }
WGPURenderPipeline create_pipeline(const State& state, [[maybe_unused]] const PipelineConfig& config) {
const auto info = build_shader_info(config.shaderConfig); // TODO remove
const auto shader = build_shader(config.shaderConfig, info);
std::array<WGPUVertexAttribute, gx::MaxVtxAttr> vtxAttrs{};
auto [num4xAttr, rem] = std::div(config.shaderConfig.indexedAttributeCount, 4);
u32 num2xAttr = 0;
if (rem > 2) {
++num4xAttr;
} else if (rem > 0) {
++num2xAttr;
}
u32 offset = 0;
u32 shaderLocation = 0;
// Indexed attributes
for (u32 i = 0; i < num4xAttr; ++i) {
vtxAttrs[shaderLocation] = {
.format = WGPUVertexFormat_Sint16x4,
.offset = offset,
.shaderLocation = shaderLocation,
};
offset += 8;
++shaderLocation;
}
for (u32 i = 0; i < num2xAttr; ++i) {
vtxAttrs[shaderLocation] = {
.format = WGPUVertexFormat_Sint16x2,
.offset = offset,
.shaderLocation = shaderLocation,
};
offset += 4;
++shaderLocation;
}
// Direct attributes
for (int i = 0; i < gx::MaxVtxAttr; ++i) {
const auto attrType = config.shaderConfig.vtxAttrs[i];
if (attrType != GX_DIRECT) {
continue;
}
const auto attr = static_cast<GXAttr>(i);
switch (attr) {
case GX_VA_POS:
case GX_VA_NRM:
vtxAttrs[shaderLocation] = WGPUVertexAttribute{
.format = WGPUVertexFormat_Float32x3,
.offset = offset,
.shaderLocation = shaderLocation,
};
offset += 12;
break;
case GX_VA_CLR0:
case GX_VA_CLR1:
vtxAttrs[shaderLocation] = WGPUVertexAttribute{
.format = WGPUVertexFormat_Float32x4,
.offset = offset,
.shaderLocation = shaderLocation,
};
offset += 16;
break;
case GX_VA_TEX0:
case GX_VA_TEX1:
case GX_VA_TEX2:
case GX_VA_TEX3:
case GX_VA_TEX4:
case GX_VA_TEX5:
case GX_VA_TEX6:
case GX_VA_TEX7:
vtxAttrs[shaderLocation] = WGPUVertexAttribute{
.format = WGPUVertexFormat_Float32x2,
.offset = offset,
.shaderLocation = shaderLocation,
};
offset += 8;
break;
default:
Log.report(LOG_FATAL, FMT_STRING("unhandled direct attr {}"), i);
}
++shaderLocation;
}
const std::array vtxBuffers{WGPUVertexBufferLayout{
.arrayStride = offset,
.stepMode = WGPUVertexStepMode_Vertex,
.attributeCount = shaderLocation,
.attributes = vtxAttrs.data(),
}};
return build_pipeline(config, info, vtxBuffers, shader, "GX Pipeline");
}
void render(const State& state, const DrawData& data, const WGPURenderPassEncoder& pass) {
if (!bind_pipeline(data.pipeline, pass)) {
return;
}
std::array<uint32_t, GX_VA_MAX_ATTR + 1> offsets{data.uniformRange.offset};
uint32_t bindIdx = 1;
for (uint32_t i = 0; i < GX_VA_MAX_ATTR; ++i) {
const auto& range = data.dataRanges.vaRanges[i];
if (range.size <= 0) {
continue;
}
offsets[bindIdx] = range.offset;
++bindIdx;
}
wgpuRenderPassEncoderSetBindGroup(pass, 0, find_bind_group(data.bindGroups.uniformBindGroup), bindIdx,
offsets.data());
if (data.bindGroups.samplerBindGroup && data.bindGroups.textureBindGroup) {
wgpuRenderPassEncoderSetBindGroup(pass, 1, find_bind_group(data.bindGroups.samplerBindGroup), 0, nullptr);
wgpuRenderPassEncoderSetBindGroup(pass, 2, find_bind_group(data.bindGroups.textureBindGroup), 0, nullptr);
}
wgpuRenderPassEncoderSetVertexBuffer(pass, 0, g_vertexBuffer, data.vertRange.offset, data.vertRange.size);
wgpuRenderPassEncoderSetIndexBuffer(pass, g_indexBuffer, WGPUIndexFormat_Uint16, data.idxRange.offset,
data.idxRange.size);
if (data.dstAlpha != UINT32_MAX) {
const WGPUColor color{0.f, 0.f, 0.f, data.dstAlpha / 255.f};
wgpuRenderPassEncoderSetBlendConstant(pass, &color);
}
wgpuRenderPassEncoderDrawIndexed(pass, data.indexCount, 1, 0, 0, 0);
}
} // namespace aurora::gfx::model
static absl::flat_hash_map<aurora::HashType, aurora::gfx::Range> sCachedRanges;

27
lib/gfx/model/shader.hpp Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include "../common.hpp"
#include "../gx.hpp"
namespace aurora::gfx::model {
struct DrawData {
PipelineRef pipeline;
Range vertRange;
Range idxRange;
gx::BindGroupRanges dataRanges;
Range uniformRange;
uint32_t indexCount;
gx::GXBindGroups bindGroups;
u32 dstAlpha;
};
struct PipelineConfig : gx::PipelineConfig {};
struct State {};
State construct_state();
WGPURenderPipeline create_pipeline(const State& state, [[maybe_unused]] const PipelineConfig& config);
void render(const State& state, const DrawData& data, const WGPURenderPassEncoder& pass);
void queue_surface(const u8* dlStart, u32 dlSize) noexcept;
} // namespace aurora::gfx::model

84
lib/gfx/stream/shader.cpp Normal file
View File

@ -0,0 +1,84 @@
#include "shader.hpp"
#include "../../webgpu/gpu.hpp"
namespace aurora::gfx::stream {
static Module Log("aurora::gfx::stream");
using webgpu::g_device;
WGPURenderPipeline create_pipeline(const State& state, [[maybe_unused]] const PipelineConfig& config) {
const auto info = build_shader_info(config.shaderConfig); // TODO remove
const auto shader = build_shader(config.shaderConfig, info);
std::array<WGPUVertexAttribute, 4> attributes{};
attributes[0] = WGPUVertexAttribute{
.format = WGPUVertexFormat_Float32x3,
.offset = 0,
.shaderLocation = 0,
};
uint64_t offset = 12;
uint32_t shaderLocation = 1;
if (config.shaderConfig.vtxAttrs[GX_VA_NRM] == GX_DIRECT) {
attributes[shaderLocation] = WGPUVertexAttribute{
.format = WGPUVertexFormat_Float32x3,
.offset = offset,
.shaderLocation = shaderLocation,
};
offset += 12;
shaderLocation++;
}
if (config.shaderConfig.vtxAttrs[GX_VA_CLR0] == GX_DIRECT) {
attributes[shaderLocation] = WGPUVertexAttribute{
.format = WGPUVertexFormat_Float32x4,
.offset = offset,
.shaderLocation = shaderLocation,
};
offset += 16;
shaderLocation++;
}
for (int i = GX_VA_TEX0; i < GX_VA_TEX7; ++i) {
if (config.shaderConfig.vtxAttrs[i] != GX_DIRECT) {
continue;
}
attributes[shaderLocation] = WGPUVertexAttribute{
.format = WGPUVertexFormat_Float32x2,
.offset = offset,
.shaderLocation = shaderLocation,
};
offset += 8;
shaderLocation++;
}
const std::array vertexBuffers{WGPUVertexBufferLayout{
.arrayStride = offset,
.attributeCount = shaderLocation,
.attributes = attributes.data(),
}};
return build_pipeline(config, info, vertexBuffers, shader, "Stream Pipeline");
}
State construct_state() { return {}; }
void render(const State& state, const DrawData& data, const WGPURenderPassEncoder& pass) {
if (!bind_pipeline(data.pipeline, pass)) {
return;
}
const std::array offsets{data.uniformRange.offset};
wgpuRenderPassEncoderSetBindGroup(pass, 0, find_bind_group(data.bindGroups.uniformBindGroup), offsets.size(),
offsets.data());
if (data.bindGroups.samplerBindGroup && data.bindGroups.textureBindGroup) {
wgpuRenderPassEncoderSetBindGroup(pass, 1, find_bind_group(data.bindGroups.samplerBindGroup), 0, nullptr);
wgpuRenderPassEncoderSetBindGroup(pass, 2, find_bind_group(data.bindGroups.textureBindGroup), 0, nullptr);
}
wgpuRenderPassEncoderSetVertexBuffer(pass, 0, g_vertexBuffer, data.vertRange.offset, data.vertRange.size);
wgpuRenderPassEncoderSetIndexBuffer(pass, g_indexBuffer, WGPUIndexFormat_Uint16, data.indexRange.offset,
data.indexRange.size);
if (data.dstAlpha != UINT32_MAX) {
const WGPUColor color{0.f, 0.f, 0.f, data.dstAlpha / 255.f};
wgpuRenderPassEncoderSetBlendConstant(pass, &color);
}
wgpuRenderPassEncoderDrawIndexed(pass, data.indexCount, 1, 0, 0, 0);
}
} // namespace aurora::gfx::stream

24
lib/gfx/stream/shader.hpp Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include "../common.hpp"
#include "../gx.hpp"
namespace aurora::gfx::stream {
struct DrawData {
PipelineRef pipeline;
Range vertRange;
Range uniformRange;
Range indexRange;
uint32_t indexCount;
gx::GXBindGroups bindGroups;
u32 dstAlpha;
};
struct PipelineConfig : public gx::PipelineConfig {};
struct State {};
State construct_state();
WGPURenderPipeline create_pipeline(const State& state, [[maybe_unused]] const PipelineConfig& config);
void render(const State& state, const DrawData& data, const WGPURenderPassEncoder& pass);
} // namespace aurora::gfx::stream

210
lib/gfx/texture.cpp Normal file
View File

@ -0,0 +1,210 @@
#include "common.hpp"
#include "../webgpu/gpu.hpp"
#include "../internal.hpp"
#include "texture.hpp"
#include "texture_convert.hpp"
#include <magic_enum.hpp>
namespace aurora::gfx {
static Module Log("aurora::gfx");
using webgpu::g_device;
using webgpu::g_queue;
struct TextureFormatInfo {
uint8_t blockWidth;
uint8_t blockHeight;
uint8_t blockSize;
bool compressed;
};
static TextureFormatInfo format_info(WGPUTextureFormat format) {
switch (format) {
case WGPUTextureFormat_R8Unorm:
return {1, 1, 1, false};
case WGPUTextureFormat_R16Sint:
return {1, 1, 2, false};
case WGPUTextureFormat_RGBA8Unorm:
case WGPUTextureFormat_R32Float:
return {1, 1, 4, false};
case WGPUTextureFormat_BC1RGBAUnorm:
return {4, 4, 8, true};
default:
Log.report(LOG_FATAL, FMT_STRING("format_info: unimplemented format {}"), magic_enum::enum_name(format));
unreachable();
}
}
static WGPUExtent3D physical_size(WGPUExtent3D size, TextureFormatInfo info) {
const uint32_t width = ((size.width + info.blockWidth - 1) / info.blockWidth) * info.blockWidth;
const uint32_t height = ((size.height + info.blockHeight - 1) / info.blockHeight) * info.blockHeight;
return {width, height, size.depthOrArrayLayers};
}
TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 format,
ArrayRef<uint8_t> data, const char* label) noexcept {
auto handle = new_dynamic_texture_2d(width, height, mips, format, label);
const auto& ref = *handle;
ByteBuffer buffer;
if (ref.gxFormat != InvalidTextureFormat) {
buffer = convert_texture(ref.gxFormat, ref.size.width, ref.size.height, ref.mipCount, data);
if (!buffer.empty()) {
data = {buffer.data(), buffer.size()};
}
}
uint32_t offset = 0;
for (uint32_t mip = 0; mip < mips; ++mip) {
const WGPUExtent3D mipSize{
.width = std::max(ref.size.width >> mip, 1u),
.height = std::max(ref.size.height >> mip, 1u),
.depthOrArrayLayers = ref.size.depthOrArrayLayers,
};
const auto info = format_info(ref.format);
const auto physicalSize = physical_size(mipSize, info);
const uint32_t widthBlocks = physicalSize.width / info.blockWidth;
const uint32_t heightBlocks = physicalSize.height / info.blockHeight;
const uint32_t bytesPerRow = widthBlocks * info.blockSize;
const uint32_t dataSize = bytesPerRow * heightBlocks * mipSize.depthOrArrayLayers;
if (offset + dataSize > data.size()) {
Log.report(LOG_FATAL, FMT_STRING("new_static_texture_2d[{}]: expected at least {} bytes, got {}"), label,
offset + dataSize, data.size());
unreachable();
}
const WGPUImageCopyTexture dstView{
.texture = ref.texture,
.mipLevel = mip,
};
// const auto range = push_texture_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks);
const WGPUTextureDataLayout dataLayout{
// .offset = range.offset,
.bytesPerRow = bytesPerRow,
.rowsPerImage = heightBlocks,
};
// TODO
// g_textureUploads.emplace_back(dataLayout, std::move(dstView), physicalSize);
wgpuQueueWriteTexture(g_queue, &dstView, data.data() + offset, dataSize, &dataLayout, &physicalSize);
offset += dataSize;
}
if (data.size() != UINT32_MAX && offset < data.size()) {
Log.report(LOG_WARNING, FMT_STRING("new_static_texture_2d[{}]: texture used {} bytes, but given {} bytes"), label,
offset, data.size());
}
return handle;
}
TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 format,
const char* label) noexcept {
const auto wgpuFormat = to_wgpu(format);
const WGPUExtent3D size{
.width = width,
.height = height,
.depthOrArrayLayers = 1,
};
const WGPUTextureDescriptor textureDescriptor{
.label = label,
.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
.dimension = WGPUTextureDimension_2D,
.size = size,
.format = wgpuFormat,
.mipLevelCount = mips,
.sampleCount = 1,
};
const auto viewLabel = fmt::format(FMT_STRING("{} view"), label);
const WGPUTextureViewDescriptor textureViewDescriptor{
.label = viewLabel.c_str(),
.format = wgpuFormat,
.dimension = WGPUTextureViewDimension_2D,
.mipLevelCount = mips,
.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED,
};
auto texture = wgpuDeviceCreateTexture(g_device, &textureDescriptor);
auto textureView = wgpuTextureCreateView(texture, &textureViewDescriptor);
return std::make_shared<TextureRef>(texture, textureView, size, wgpuFormat, mips, format, false);
}
TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 fmt, const char* label) noexcept {
const auto wgpuFormat = webgpu::g_graphicsConfig.colorFormat;
const WGPUExtent3D size{
.width = width,
.height = height,
.depthOrArrayLayers = 1,
};
const WGPUTextureDescriptor textureDescriptor{
.label = label,
.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
.dimension = WGPUTextureDimension_2D,
.size = size,
.format = wgpuFormat,
.mipLevelCount = 1,
.sampleCount = 1,
};
const auto viewLabel = fmt::format(FMT_STRING("{} view"), label);
const WGPUTextureViewDescriptor textureViewDescriptor{
.label = viewLabel.c_str(),
.format = wgpuFormat,
.dimension = WGPUTextureViewDimension_2D,
.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED,
.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED,
};
auto texture = wgpuDeviceCreateTexture(g_device, &textureDescriptor);
auto textureView = wgpuTextureCreateView(texture, &textureViewDescriptor);
return std::make_shared<TextureRef>(texture, textureView, size, wgpuFormat, 1, fmt, true);
}
void write_texture(const TextureRef& ref, ArrayRef<uint8_t> data) noexcept {
ByteBuffer buffer;
if (ref.gxFormat != InvalidTextureFormat) {
buffer = convert_texture(ref.gxFormat, ref.size.width, ref.size.height, ref.mipCount, data);
if (!buffer.empty()) {
data = {buffer.data(), buffer.size()};
}
}
uint32_t offset = 0;
for (uint32_t mip = 0; mip < ref.mipCount; ++mip) {
const WGPUExtent3D mipSize{
.width = std::max(ref.size.width >> mip, 1u),
.height = std::max(ref.size.height >> mip, 1u),
.depthOrArrayLayers = ref.size.depthOrArrayLayers,
};
const auto info = format_info(ref.format);
const auto physicalSize = physical_size(mipSize, info);
const uint32_t widthBlocks = physicalSize.width / info.blockWidth;
const uint32_t heightBlocks = physicalSize.height / info.blockHeight;
const uint32_t bytesPerRow = widthBlocks * info.blockSize;
const uint32_t dataSize = bytesPerRow * heightBlocks * mipSize.depthOrArrayLayers;
if (offset + dataSize > data.size()) {
Log.report(LOG_FATAL, FMT_STRING("write_texture: expected at least {} bytes, got {}"), offset + dataSize,
data.size());
unreachable();
}
// auto dstView = WGPUImageCopyTexture{
// .texture = ref.texture,
// .mipLevel = mip,
// };
// const auto range = push_texture_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks);
// const auto dataLayout = WGPUTextureDataLayout{
// .offset = range.offset,
// .bytesPerRow = bytesPerRow,
// .rowsPerImage = heightBlocks,
// };
// g_textureUploads.emplace_back(dataLayout, std::move(dstView), physicalSize);
const WGPUImageCopyTexture dstView{
.texture = ref.texture,
.mipLevel = mip,
};
const WGPUTextureDataLayout dataLayout{
.bytesPerRow = bytesPerRow,
.rowsPerImage = heightBlocks,
};
wgpuQueueWriteTexture(g_queue, &dstView, data.data() + offset, dataSize, &dataLayout, &physicalSize);
offset += dataSize;
}
if (data.size() != UINT32_MAX && offset < data.size()) {
Log.report(LOG_WARNING, FMT_STRING("write_texture: texture used {} bytes, but given {} bytes"), offset,
data.size());
}
}
} // namespace aurora::gfx

90
lib/gfx/texture.hpp Normal file
View File

@ -0,0 +1,90 @@
#pragma once
#include <dolphin/gx.h>
#include "common.hpp"
namespace aurora::gfx {
struct TextureUpload {
WGPUTextureDataLayout layout;
WGPUImageCopyTexture tex;
WGPUExtent3D size;
TextureUpload(WGPUTextureDataLayout layout, WGPUImageCopyTexture tex, WGPUExtent3D size) noexcept
: layout(layout), tex(tex), size(size) {}
};
extern std::vector<TextureUpload> g_textureUploads;
constexpr u32 InvalidTextureFormat = -1;
struct TextureRef {
WGPUTexture texture;
WGPUTextureView view;
WGPUExtent3D size;
WGPUTextureFormat format;
uint32_t mipCount;
u32 gxFormat;
bool isRenderTexture; // :shrug: for now
TextureRef(WGPUTexture texture, WGPUTextureView view, WGPUExtent3D size, WGPUTextureFormat format, uint32_t mipCount,
u32 gxFormat, bool isRenderTexture)
: texture(texture)
, view(view)
, size(size)
, format(format)
, mipCount(mipCount)
, gxFormat(gxFormat)
, isRenderTexture(isRenderTexture) {}
~TextureRef() {
wgpuTextureViewRelease(view);
wgpuTextureDestroy(texture);
}
};
using TextureHandle = std::shared_ptr<TextureRef>;
TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 format,
ArrayRef<uint8_t> data, const char* label) noexcept;
TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 format,
const char* label) noexcept;
TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 fmt, const char* label) noexcept;
void write_texture(const TextureRef& ref, ArrayRef<uint8_t> data) noexcept;
}; // namespace aurora::gfx
struct GXTexObj_ {
aurora::gfx::TextureHandle ref;
const void* data;
u32 dataSize;
u16 width;
u16 height;
u32 fmt;
GXTexWrapMode wrapS;
GXTexWrapMode wrapT;
GXBool hasMips;
GXTexFilter minFilter;
GXTexFilter magFilter;
float minLod;
float maxLod;
float lodBias;
GXBool biasClamp;
GXBool doEdgeLod;
GXAnisotropy maxAniso;
GXTlut tlut;
bool dataInvalidated;
};
static_assert(sizeof(GXTexObj_) <= sizeof(GXTexObj), "GXTexObj too small!");
struct GXTlutObj_ {
aurora::gfx::TextureHandle ref;
};
static_assert(sizeof(GXTlutObj_) <= sizeof(GXTlutObj), "GXTlutObj too small!");
namespace aurora::gfx {
struct TextureBind {
GXTexObj_ texObj;
TextureBind() noexcept = default;
TextureBind(GXTexObj_ obj) noexcept : texObj(std::move(obj)) {}
void reset() noexcept { texObj.ref.reset(); };
[[nodiscard]] WGPUSamplerDescriptor get_descriptor() const noexcept;
operator bool() const noexcept { return texObj.ref.operator bool(); }
};
} // namespace aurora::gfx

607
lib/gfx/texture_convert.cpp Normal file
View File

@ -0,0 +1,607 @@
#include "texture_convert.hpp"
#include "../internal.hpp"
namespace aurora::gfx {
static Module Log("aurora::gfx");
struct RGBA8 {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
struct DXT1Block {
uint16_t color1;
uint16_t color2;
std::array<uint8_t, 4> lines;
};
// http://www.mindcontrol.org/~hplus/graphics/expand-bits.html
template <uint8_t v>
constexpr uint8_t ExpandTo8(uint8_t n) {
if constexpr (v == 3) {
return (n << (8 - 3)) | (n << (8 - 6)) | (n >> (9 - 8));
} else {
return (n << (8 - v)) | (n >> ((v * 2) - 8));
}
}
constexpr uint8_t S3TCBlend(uint32_t a, uint32_t b) {
return static_cast<uint8_t>((((a << 1) + a) + ((b << 2) + b)) >> 3);
}
constexpr uint8_t HalfBlend(uint8_t a, uint8_t b) {
return static_cast<uint8_t>((static_cast<uint32_t>(a) + static_cast<uint32_t>(b)) >> 1);
}
static size_t ComputeMippedTexelCount(uint32_t w, uint32_t h, uint32_t mips) {
size_t ret = w * h;
for (uint32_t i = mips; i > 1; --i) {
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
ret += w * h;
}
return ret;
}
static size_t ComputeMippedBlockCountDXT1(uint32_t w, uint32_t h, uint32_t mips) {
w /= 4;
h /= 4;
size_t ret = w * h;
for (uint32_t i = mips; i > 1; --i) {
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
ret += w * h;
}
return ret;
}
template <typename T>
constexpr T bswap16(T val) noexcept {
#if __GNUC__
return __builtin_bswap16(val);
#elif _WIN32
return _byteswap_ushort(val);
#else
return (val = (val << 8) | ((val >> 8) & 0xFF));
#endif
}
static ByteBuffer BuildI4FromGCN(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
const size_t texelCount = ComputeMippedTexelCount(width, height, mips);
ByteBuffer buf{texelCount};
uint32_t w = width;
uint32_t h = height;
uint8_t* targetMip = buf.data();
const uint8_t* in = data.data();
for (uint32_t mip = 0; mip < mips; ++mip) {
const uint32_t bwidth = (w + 7) / 8;
const uint32_t bheight = (h + 7) / 8;
for (uint32_t by = 0; by < bheight; ++by) {
const uint32_t baseY = by * 8;
for (uint32_t bx = 0; bx < bwidth; ++bx) {
const uint32_t baseX = bx * 8;
for (uint32_t y = 0; y < std::min(h, 8u); ++y) {
uint8_t* target = targetMip + (baseY + y) * w + baseX;
for (uint32_t x = 0; x < std::min(w, 8u); ++x) {
target[x] = ExpandTo8<4>(in[x / 2] >> ((x & 1) ? 0 : 4) & 0xf);
}
in += std::min<size_t>(w / 4, 4);
}
}
}
targetMip += w * h;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
static ByteBuffer BuildI8FromGCN(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
const size_t texelCount = ComputeMippedTexelCount(width, height, mips);
ByteBuffer buf{texelCount};
uint32_t w = width;
uint32_t h = height;
auto* targetMip = buf.data();
const uint8_t* in = data.data();
for (uint32_t mip = 0; mip < mips; ++mip) {
const uint32_t bwidth = (w + 7) / 8;
const uint32_t bheight = (h + 3) / 4;
for (uint32_t by = 0; by < bheight; ++by) {
const uint32_t baseY = by * 4;
for (uint32_t bx = 0; bx < bwidth; ++bx) {
const uint32_t baseX = bx * 8;
for (uint32_t y = 0; y < 4; ++y) {
uint8_t* target = targetMip + (baseY + y) * w + baseX;
const auto n = std::min(w, 8u);
for (size_t x = 0; x < n; ++x) {
target[x] = in[x];
}
in += n;
}
}
}
targetMip += w * h;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
ByteBuffer BuildIA4FromGCN(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
const size_t texelCount = ComputeMippedTexelCount(width, height, mips);
ByteBuffer buf{sizeof(RGBA8) * texelCount};
uint32_t w = width;
uint32_t h = height;
RGBA8* targetMip = reinterpret_cast<RGBA8*>(buf.data());
const uint8_t* in = data.data();
for (uint32_t mip = 0; mip < mips; ++mip) {
const uint32_t bwidth = (w + 7) / 8;
const uint32_t bheight = (h + 3) / 4;
for (uint32_t by = 0; by < bheight; ++by) {
const uint32_t baseY = by * 4;
for (uint32_t bx = 0; bx < bwidth; ++bx) {
const uint32_t baseX = bx * 8;
for (uint32_t y = 0; y < 4; ++y) {
RGBA8* target = targetMip + (baseY + y) * w + baseX;
const auto n = std::min(w, 8u);
for (size_t x = 0; x < n; ++x) {
const uint8_t intensity = ExpandTo8<4>(in[x] & 0xf);
target[x].r = intensity;
target[x].g = intensity;
target[x].b = intensity;
target[x].a = ExpandTo8<4>(in[x] >> 4);
}
in += n;
}
}
}
targetMip += w * h;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
ByteBuffer BuildIA8FromGCN(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
const size_t texelCount = ComputeMippedTexelCount(width, height, mips);
ByteBuffer buf{sizeof(RGBA8) * texelCount};
uint32_t w = width;
uint32_t h = height;
auto* targetMip = reinterpret_cast<RGBA8*>(buf.data());
const auto* in = reinterpret_cast<const uint16_t*>(data.data());
for (uint32_t mip = 0; mip < mips; ++mip) {
const uint32_t bwidth = (w + 3) / 4;
const uint32_t bheight = (h + 3) / 4;
for (uint32_t by = 0; by < bheight; ++by) {
const uint32_t baseY = by * 4;
for (uint32_t bx = 0; bx < bwidth; ++bx) {
const uint32_t baseX = bx * 4;
for (uint32_t y = 0; y < 4; ++y) {
RGBA8* target = targetMip + (baseY + y) * w + baseX;
for (size_t x = 0; x < 4; ++x) {
const auto texel = bswap16(in[x]);
const uint8_t intensity = texel >> 8;
target[x].r = intensity;
target[x].g = intensity;
target[x].b = intensity;
target[x].a = texel & 0xff;
}
in += 4;
}
}
}
targetMip += w * h;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
ByteBuffer BuildC4FromGCN(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
const size_t texelCount = ComputeMippedTexelCount(width, height, mips);
ByteBuffer buf{texelCount * 2};
uint32_t w = width;
uint32_t h = height;
uint16_t* targetMip = reinterpret_cast<uint16_t*>(buf.data());
const uint8_t* in = data.data();
for (uint32_t mip = 0; mip < mips; ++mip) {
const uint32_t bwidth = (w + 7) / 8;
const uint32_t bheight = (h + 7) / 8;
for (uint32_t by = 0; by < bheight; ++by) {
const uint32_t baseY = by * 8;
for (uint32_t bx = 0; bx < bwidth; ++bx) {
const uint32_t baseX = bx * 8;
for (uint32_t y = 0; y < std::min(8u, h); ++y) {
uint16_t* target = targetMip + (baseY + y) * w + baseX;
const auto n = std::min(w, 8u);
for (size_t x = 0; x < n; ++x) {
target[x] = in[x / 2] >> ((x & 1) ? 0 : 4) & 0xf;
}
in += n / 2;
}
}
}
targetMip += w * h;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
ByteBuffer BuildC8FromGCN(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
const size_t texelCount = ComputeMippedTexelCount(width, height, mips);
ByteBuffer buf{texelCount * 2};
uint32_t w = width;
uint32_t h = height;
uint16_t* targetMip = reinterpret_cast<uint16_t*>(buf.data());
const uint8_t* in = data.data();
for (uint32_t mip = 0; mip < mips; ++mip) {
const uint32_t bwidth = (w + 7) / 8;
const uint32_t bheight = (h + 3) / 4;
for (uint32_t by = 0; by < bheight; ++by) {
const uint32_t baseY = by * 4;
for (uint32_t bx = 0; bx < bwidth; ++bx) {
const uint32_t baseX = bx * 8;
for (uint32_t y = 0; y < 4; ++y) {
uint16_t* target = targetMip + (baseY + y) * w + baseX;
const auto n = std::min(w, 8u);
for (size_t x = 0; x < n; ++x) {
target[x] = in[x];
}
in += n;
}
}
}
targetMip += w * h;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
ByteBuffer BuildRGB565FromGCN(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
const size_t texelCount = ComputeMippedTexelCount(width, height, mips);
ByteBuffer buf{sizeof(RGBA8) * texelCount};
uint32_t w = width;
uint32_t h = height;
auto* targetMip = reinterpret_cast<RGBA8*>(buf.data());
const auto* in = reinterpret_cast<const uint16_t*>(data.data());
for (uint32_t mip = 0; mip < mips; ++mip) {
const uint32_t bwidth = (w + 3) / 4;
const uint32_t bheight = (h + 3) / 4;
for (uint32_t by = 0; by < bheight; ++by) {
const uint32_t baseY = by * 4;
for (uint32_t bx = 0; bx < bwidth; ++bx) {
const uint32_t baseX = bx * 4;
for (uint32_t y = 0; y < std::min(4u, h); ++y) {
RGBA8* target = targetMip + (baseY + y) * w + baseX;
for (size_t x = 0; x < std::min(4u, w); ++x) {
const auto texel = bswap16(in[x]);
target[x].r = ExpandTo8<5>(texel >> 11 & 0x1f);
target[x].g = ExpandTo8<6>(texel >> 5 & 0x3f);
target[x].b = ExpandTo8<5>(texel & 0x1f);
target[x].a = 0xff;
}
in += 4;
}
}
}
targetMip += w * h;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
ByteBuffer BuildRGB5A3FromGCN(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
size_t texelCount = ComputeMippedTexelCount(width, height, mips);
ByteBuffer buf{sizeof(RGBA8) * texelCount};
uint32_t w = width;
uint32_t h = height;
auto* targetMip = reinterpret_cast<RGBA8*>(buf.data());
const auto* in = reinterpret_cast<const uint16_t*>(data.data());
for (uint32_t mip = 0; mip < mips; ++mip) {
const uint32_t bwidth = (w + 3) / 4;
const uint32_t bheight = (h + 3) / 4;
for (uint32_t by = 0; by < bheight; ++by) {
const uint32_t baseY = by * 4;
for (uint32_t bx = 0; bx < bwidth; ++bx) {
const uint32_t baseX = bx * 4;
for (uint32_t y = 0; y < std::min(4u, h); ++y) {
RGBA8* target = targetMip + (baseY + y) * w + baseX;
for (size_t x = 0; x < std::min(4u, w); ++x) {
const auto texel = bswap16(in[x]);
if ((texel & 0x8000) != 0) {
target[x].r = ExpandTo8<5>(texel >> 10 & 0x1f);
target[x].g = ExpandTo8<5>(texel >> 5 & 0x1f);
target[x].b = ExpandTo8<5>(texel & 0x1f);
target[x].a = 0xff;
} else {
target[x].r = ExpandTo8<4>(texel >> 8 & 0xf);
target[x].g = ExpandTo8<4>(texel >> 4 & 0xf);
target[x].b = ExpandTo8<4>(texel & 0xf);
target[x].a = ExpandTo8<3>(texel >> 12 & 0x7);
}
}
in += 4;
}
}
}
targetMip += w * h;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
ByteBuffer BuildRGBA8FromGCN(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
const size_t texelCount = ComputeMippedTexelCount(width, height, mips);
ByteBuffer buf{sizeof(RGBA8) * texelCount};
uint32_t w = width;
uint32_t h = height;
auto* targetMip = reinterpret_cast<RGBA8*>(buf.data());
const uint8_t* in = data.data();
for (uint32_t mip = 0; mip < mips; ++mip) {
const uint32_t bwidth = (w + 3) / 4;
const uint32_t bheight = (h + 3) / 4;
for (uint32_t by = 0; by < bheight; ++by) {
const uint32_t baseY = by * 4;
for (uint32_t bx = 0; bx < bwidth; ++bx) {
const uint32_t baseX = bx * 4;
for (uint32_t c = 0; c < 2; ++c) {
for (uint32_t y = 0; y < 4; ++y) {
RGBA8* target = targetMip + (baseY + y) * w + baseX;
for (size_t x = 0; x < 4; ++x) {
if (c != 0) {
target[x].g = in[x * 2];
target[x].b = in[x * 2 + 1];
} else {
target[x].a = in[x * 2];
target[x].r = in[x * 2 + 1];
}
}
in += 8;
}
}
}
}
targetMip += w * h;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
ByteBuffer BuildDXT1FromGCN(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
const size_t blockCount = ComputeMippedBlockCountDXT1(width, height, mips);
ByteBuffer buf{sizeof(DXT1Block) * blockCount};
uint32_t w = width / 4;
uint32_t h = height / 4;
auto* targetMip = reinterpret_cast<DXT1Block*>(buf.data());
const auto* in = reinterpret_cast<const DXT1Block*>(data.data());
for (uint32_t mip = 0; mip < mips; ++mip) {
const uint32_t bwidth = (w + 1) / 2;
const uint32_t bheight = (h + 1) / 2;
for (uint32_t by = 0; by < bheight; ++by) {
const uint32_t baseY = by * 2;
for (uint32_t bx = 0; bx < bwidth; ++bx) {
const uint32_t baseX = bx * 2;
for (uint32_t y = 0; y < 2; ++y) {
DXT1Block* target = targetMip + (baseY + y) * w + baseX;
for (size_t x = 0; x < 2; ++x) {
target[x].color1 = bswap16(in[x].color1);
target[x].color2 = bswap16(in[x].color2);
for (size_t i = 0; i < 4; ++i) {
std::array<uint8_t, 4> ind;
const uint8_t packed = in[x].lines[i];
ind[3] = packed & 0x3;
ind[2] = (packed >> 2) & 0x3;
ind[1] = (packed >> 4) & 0x3;
ind[0] = (packed >> 6) & 0x3;
target[x].lines[i] = ind[0] | (ind[1] << 2) | (ind[2] << 4) | (ind[3] << 6);
}
}
in += 2;
}
}
}
targetMip += w * h;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
ByteBuffer BuildRGBA8FromCMPR(uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
const size_t texelCount = ComputeMippedTexelCount(width, height, mips);
const size_t blockCount = ComputeMippedBlockCountDXT1(width, height, mips);
ByteBuffer buf{sizeof(RGBA8) * texelCount};
uint32_t h = height;
uint32_t w = width;
uint8_t* dst = buf.data();
const uint8_t* src = data.data();
for (uint32_t mip = 0; mip < mips; ++mip) {
for (uint32_t yy = 0; yy < h; yy += 8) {
for (uint32_t xx = 0; xx < w; xx += 8) {
for (uint32_t yb = 0; yb < 8; yb += 4) {
for (uint32_t xb = 0; xb < 8; xb += 4) {
// CMPR difference: Big-endian color1/2
const uint16_t color1 = bswap16(*reinterpret_cast<const uint16_t*>(src));
const uint16_t color2 = bswap16(*reinterpret_cast<const uint16_t*>(src + 2));
src += 4;
// Fill in first two colors in color table.
std::array<uint8_t, 16> color_table{};
color_table[0] = ExpandTo8<5>(static_cast<uint8_t>((color1 >> 11) & 0x1F));
color_table[1] = ExpandTo8<6>(static_cast<uint8_t>((color1 >> 5) & 0x3F));
color_table[2] = ExpandTo8<5>(static_cast<uint8_t>(color1 & 0x1F));
color_table[3] = 0xFF;
color_table[4] = ExpandTo8<5>(static_cast<uint8_t>((color2 >> 11) & 0x1F));
color_table[5] = ExpandTo8<6>(static_cast<uint8_t>((color2 >> 5) & 0x3F));
color_table[6] = ExpandTo8<5>(static_cast<uint8_t>(color2 & 0x1F));
color_table[7] = 0xFF;
if (color1 > color2) {
// Predict gradients.
color_table[8] = S3TCBlend(color_table[4], color_table[0]);
color_table[9] = S3TCBlend(color_table[5], color_table[1]);
color_table[10] = S3TCBlend(color_table[6], color_table[2]);
color_table[11] = 0xFF;
color_table[12] = S3TCBlend(color_table[0], color_table[4]);
color_table[13] = S3TCBlend(color_table[1], color_table[5]);
color_table[14] = S3TCBlend(color_table[2], color_table[6]);
color_table[15] = 0xFF;
} else {
color_table[8] = HalfBlend(color_table[0], color_table[4]);
color_table[9] = HalfBlend(color_table[1], color_table[5]);
color_table[10] = HalfBlend(color_table[2], color_table[6]);
color_table[11] = 0xFF;
// CMPR difference: GX fills with an alpha 0 midway point here.
color_table[12] = color_table[8];
color_table[13] = color_table[9];
color_table[14] = color_table[10];
color_table[15] = 0;
}
for (uint32_t y = 0; y < 4; ++y) {
uint8_t bits = src[y];
for (uint32_t x = 0; x < 4; ++x) {
if (xx + xb + x >= w || yy + yb + y >= h) {
continue;
}
uint8_t* dstOffs = dst + ((yy + yb + y) * w + (xx + xb + x)) * 4;
const uint8_t* colorTableOffs = &color_table[static_cast<size_t>((bits >> 6) & 3) * 4];
memcpy(dstOffs, colorTableOffs, 4);
bits <<= 2;
}
}
src += 4;
}
}
}
}
dst += w * h * 4;
if (w > 1) {
w /= 2;
}
if (h > 1) {
h /= 2;
}
}
return buf;
}
ByteBuffer convert_texture(u32 format, uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
switch (format) {
default:
Log.report(LOG_FATAL, FMT_STRING("convert_texture: unknown format supplied {}"), format);
unreachable();
case GX_TF_R8_PC:
case GX_TF_RGBA8_PC:
return {}; // No conversion
case GX_TF_I4:
return BuildI4FromGCN(width, height, mips, data);
case GX_TF_I8:
return BuildI8FromGCN(width, height, mips, data);
case GX_TF_IA4:
return BuildIA4FromGCN(width, height, mips, data);
case GX_TF_IA8:
return BuildIA8FromGCN(width, height, mips, data);
case GX_TF_C4:
return BuildC4FromGCN(width, height, mips, data);
case GX_TF_C8:
return BuildC8FromGCN(width, height, mips, data);
case GX_TF_C14X2:
Log.report(LOG_FATAL, FMT_STRING("convert_texture: C14X2 unimplemented"));
unreachable();
case GX_TF_RGB565:
return BuildRGB565FromGCN(width, height, mips, data);
case GX_TF_RGB5A3:
return BuildRGB5A3FromGCN(width, height, mips, data);
case GX_TF_RGBA8:
return BuildRGBA8FromGCN(width, height, mips, data);
case GX_TF_CMPR:
if (wgpuDeviceHasFeature(webgpu::g_device, WGPUFeatureName_TextureCompressionBC)) {
return BuildDXT1FromGCN(width, height, mips, data);
} else {
return BuildRGBA8FromCMPR(width, height, mips, data);
}
}
}
} // namespace aurora::gfx

View File

@ -0,0 +1,29 @@
#pragma once
#include "common.hpp"
#include "texture.hpp"
#include "../webgpu/gpu.hpp"
namespace aurora::gfx {
static WGPUTextureFormat to_wgpu(u32 format) {
switch (format) {
case GX_TF_I4:
case GX_TF_I8:
case GX_TF_R8_PC:
return WGPUTextureFormat_R8Unorm;
case GX_TF_C4:
case GX_TF_C8:
case GX_TF_C14X2:
return WGPUTextureFormat_R16Sint;
case GX_TF_CMPR:
if (wgpuDeviceHasFeature(webgpu::g_device, WGPUFeatureName_TextureCompressionBC)) {
return WGPUTextureFormat_BC1RGBAUnorm;
}
[[fallthrough]];
default:
return WGPUTextureFormat_RGBA8Unorm;
}
}
ByteBuffer convert_texture(u32 format, uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data);
} // namespace aurora::gfx

170
lib/imgui.cpp Normal file
View File

@ -0,0 +1,170 @@
#include "imgui.hpp"
#include "webgpu/gpu.hpp"
#include "internal.hpp"
#include "window.hpp"
#include <SDL.h>
#include <webgpu/webgpu.h>
#include "../imgui/backends/imgui_impl_sdl.cpp" // NOLINT(bugprone-suspicious-include)
#include "../imgui/backends/imgui_impl_sdlrenderer.cpp" // NOLINT(bugprone-suspicious-include)
#include "../imgui/backends/imgui_impl_wgpu.cpp" // NOLINT(bugprone-suspicious-include)
namespace aurora::imgui {
static float g_scale;
static std::string g_imguiSettings{};
static std::string g_imguiLog{};
static bool g_useSdlRenderer = false;
static std::vector<SDL_Texture*> g_sdlTextures;
static std::vector<WGPUTexture> g_wgpuTextures;
void create_context() noexcept {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
g_imguiSettings = std::string{g_config.configPath} + "/imgui.ini";
g_imguiLog = std::string{g_config.configPath} + "/imgui.log";
io.IniFilename = g_imguiSettings.c_str();
io.LogFilename = g_imguiLog.c_str();
}
void initialize() noexcept {
SDL_Renderer* renderer = window::get_sdl_renderer();
ImGui_ImplSDL2_Init(window::get_sdl_window(), renderer);
#ifdef __APPLE__
// Disable MouseCanUseGlobalState for scaling purposes
ImGui_ImplSDL2_GetBackendData()->MouseCanUseGlobalState = false;
#endif
g_useSdlRenderer = renderer != nullptr;
if (g_useSdlRenderer) {
ImGui_ImplSDLRenderer_Init(renderer);
} else {
ImGui_ImplWGPU_Init(webgpu::g_device, 1, webgpu::g_graphicsConfig.colorFormat);
}
}
void shutdown() noexcept {
if (g_useSdlRenderer) {
ImGui_ImplSDLRenderer_Shutdown();
} else {
ImGui_ImplWGPU_Shutdown();
}
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
for (const auto& texture : g_sdlTextures) {
SDL_DestroyTexture(texture);
}
g_sdlTextures.clear();
for (const auto& texture : g_wgpuTextures) {
wgpuTextureDestroy(texture);
}
g_wgpuTextures.clear();
}
void process_event(const SDL_Event& event) noexcept {
#ifdef __APPLE__
if (event.type == SDL_MOUSEMOTION) {
auto& io = ImGui::GetIO();
// Scale up mouse coordinates
io.AddMousePosEvent(static_cast<float>(event.motion.x) * g_scale, static_cast<float>(event.motion.y) * g_scale);
return;
}
#endif
ImGui_ImplSDL2_ProcessEvent(&event);
}
void new_frame(const AuroraWindowSize& size) noexcept {
if (g_useSdlRenderer) {
ImGui_ImplSDLRenderer_NewFrame();
} else {
if (g_scale != size.scale) {
if (g_scale > 0.f) {
// TODO wgpu backend bug: doesn't clear bind groups on invalidate
g_resources.ImageBindGroups.Clear();
ImGui_ImplWGPU_CreateDeviceObjects();
}
g_scale = size.scale;
}
ImGui_ImplWGPU_NewFrame();
}
ImGui_ImplSDL2_NewFrame();
// Render at full DPI
ImGui::GetIO().DisplaySize = {
static_cast<float>(size.fb_width),
static_cast<float>(size.fb_height),
};
ImGui::NewFrame();
}
void render(WGPURenderPassEncoder pass) noexcept {
ImGui::Render();
auto* data = ImGui::GetDrawData();
// io.DisplayFramebufferScale is informational; we're rendering at full DPI
data->FramebufferScale = {1.f, 1.f};
if (g_useSdlRenderer) {
SDL_Renderer* renderer = ImGui_ImplSDLRenderer_GetBackendData()->SDLRenderer;
SDL_RenderClear(renderer);
ImGui_ImplSDLRenderer_RenderDrawData(data);
SDL_RenderPresent(renderer);
} else {
ImGui_ImplWGPU_RenderDrawData(data, pass);
}
}
ImTextureID add_texture(uint32_t width, uint32_t height, const uint8_t* data) noexcept {
if (g_useSdlRenderer) {
SDL_Renderer* renderer = ImGui_ImplSDLRenderer_GetBackendData()->SDLRenderer;
SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, width, height);
SDL_UpdateTexture(texture, nullptr, data, width * 4);
SDL_SetTextureScaleMode(texture, SDL_ScaleModeLinear);
g_sdlTextures.push_back(texture);
return texture;
}
const auto size = WGPUExtent3D{
.width = width,
.height = height,
.depthOrArrayLayers = 1,
};
const auto textureDescriptor = WGPUTextureDescriptor{
.label = "imgui texture",
.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
.dimension = WGPUTextureDimension_2D,
.size = size,
.format = WGPUTextureFormat_RGBA8Unorm,
.mipLevelCount = 1,
.sampleCount = 1,
};
const auto textureViewDescriptor = WGPUTextureViewDescriptor{
.label = "imgui texture view",
.format = WGPUTextureFormat_RGBA8Unorm,
.dimension = WGPUTextureViewDimension_2D,
.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED,
.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED,
};
auto texture = wgpuDeviceCreateTexture(webgpu::g_device, &textureDescriptor);
auto textureView = wgpuTextureCreateView(texture, &textureViewDescriptor);
{
const auto dstView = WGPUImageCopyTexture{
.texture = texture,
};
const auto dataLayout = WGPUTextureDataLayout{
.bytesPerRow = 4 * width,
.rowsPerImage = height,
};
wgpuQueueWriteTexture(webgpu::g_queue, &dstView, data, width * height * 4, &dataLayout, &size);
}
g_wgpuTextures.push_back(texture);
return textureView;
}
} // namespace aurora::imgui
// C bindings
extern "C" {
ImTextureID aurora_imgui_add_texture(uint32_t width, uint32_t height, const void* rgba8) {
return aurora::imgui::add_texture(width, height, static_cast<const uint8_t*>(rgba8));
}
}

18
lib/imgui.hpp Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <aurora/event.h>
#include <string_view>
union SDL_Event;
typedef struct WGPURenderPassEncoderImpl* WGPURenderPassEncoder;
namespace aurora::imgui {
void create_context() noexcept;
void initialize() noexcept;
void shutdown() noexcept;
void process_event(const SDL_Event& event) noexcept;
void new_frame(const AuroraWindowSize& size) noexcept;
void render(WGPURenderPassEncoder pass) noexcept;
} // namespace aurora::imgui

815
lib/input.cpp Normal file
View File

@ -0,0 +1,815 @@
#include "input.hpp"
#include "internal.hpp"
#include "pad.hpp"
#include "magic_enum.hpp"
#include <SDL_haptic.h>
#include <SDL_version.h>
#include <absl/container/btree_map.h>
#include <absl/container/flat_hash_map.h>
#include <absl/strings/str_split.h>
#include <cmath>
using namespace std::string_view_literals;
namespace aurora::input {
static Module Log("aurora::input");
struct GameController {
SDL_GameController* m_controller = nullptr;
bool m_isGameCube = false;
Sint32 m_index = -1;
bool m_hasRumble = false;
PADDeadZones m_deadZones{
.emulateTriggers = true,
.useDeadzones = true,
.stickDeadZone = 8000,
.substickDeadZone = 8000,
.leftTriggerActivationZone = 31150,
.rightTriggerActivationZone = 31150,
};
uint16_t m_vid = 0;
uint16_t m_pid = 0;
std::array<PADButtonMapping, 12> m_mapping{};
bool m_mappingLoaded = false;
constexpr bool operator==(const GameController& other) const {
return m_controller == other.m_controller && m_index == other.m_index;
}
};
absl::flat_hash_map<Uint32, GameController> g_GameControllers;
GameController* get_controller_for_player(uint32_t player) noexcept {
for (auto& [which, controller] : g_GameControllers) {
if (player_index(which) == player) {
return &controller;
}
}
#if 0
/* If we don't have a controller assigned to this port use the first unassigned controller */
if (!g_GameControllers.empty()) {
int32_t availIndex = -1;
GameController* ct = nullptr;
for (auto& controller : g_GameControllers) {
if (player_index(controller.first) == -1) {
availIndex = controller.first;
ct = &controller.second;
break;
}
}
if (availIndex != -1) {
set_player_index(availIndex, player);
return ct;
}
}
#endif
return nullptr;
}
Sint32 get_instance_for_player(uint32_t player) noexcept {
for (const auto& [which, controller] : g_GameControllers) {
if (player_index(which) == player) {
return which;
}
}
return {};
}
static std::optional<std::string> remap_controller_layout(std::string_view mapping) {
std::string newMapping;
newMapping.reserve(mapping.size());
absl::btree_map<std::string_view, std::string_view> entries;
for (size_t idx = 0; const auto value : absl::StrSplit(mapping, ',')) {
if (idx < 2) {
if (idx > 0) {
newMapping.push_back(',');
}
newMapping.append(value);
} else {
const auto split = absl::StrSplit(value, absl::MaxSplits(':', 2));
auto iter = split.begin();
entries.emplace(*iter++, *iter);
}
idx++;
}
if (entries.contains("rightshoulder"sv) && !entries.contains("leftshoulder"sv)) {
Log.report(LOG_INFO, FMT_STRING("Remapping GameCube controller layout"));
entries.insert_or_assign("back"sv, entries["rightshoulder"sv]);
// TODO trigger buttons may differ per platform
entries.insert_or_assign("leftshoulder"sv, "b11"sv);
entries.insert_or_assign("rightshoulder"sv, "b10"sv);
} else if (entries.contains("leftshoulder"sv) && entries.contains("rightshoulder"sv) && entries.contains("back"sv)) {
Log.report(LOG_INFO, FMT_STRING("Controller has standard layout"));
auto a = entries["a"sv];
entries.insert_or_assign("a"sv, entries["b"sv]);
entries.insert_or_assign("b"sv, a);
auto x = entries["x"sv];
entries.insert_or_assign("x"sv, entries["y"sv]);
entries.insert_or_assign("y"sv, x);
} else {
Log.report(LOG_ERROR, FMT_STRING("Controller has unsupported layout: {}"), mapping);
return {};
}
for (auto [k, v] : entries) {
newMapping.push_back(',');
newMapping.append(k);
newMapping.push_back(':');
newMapping.append(v);
}
return newMapping;
}
Sint32 add_controller(Sint32 which) noexcept {
auto* ctrl = SDL_GameControllerOpen(which);
if (ctrl != nullptr) {
{
char* mapping = SDL_GameControllerMapping(ctrl);
if (mapping != nullptr) {
auto newMapping = remap_controller_layout(mapping);
SDL_free(mapping);
if (newMapping) {
if (SDL_GameControllerAddMapping(newMapping->c_str()) == -1) {
Log.report(LOG_ERROR, FMT_STRING("Failed to update controller mapping: {}"), SDL_GetError());
}
}
} else {
Log.report(LOG_ERROR, FMT_STRING("Failed to retrieve mapping for controller"));
}
}
GameController controller;
controller.m_controller = ctrl;
controller.m_index = which;
controller.m_vid = SDL_GameControllerGetVendor(ctrl);
controller.m_pid = SDL_GameControllerGetProduct(ctrl);
if (controller.m_vid == 0x05ac /* USB_VENDOR_APPLE */ && controller.m_pid == 3) {
// Ignore Apple TV remote
SDL_GameControllerClose(ctrl);
return -1;
}
controller.m_isGameCube = controller.m_vid == 0x057E && controller.m_pid == 0x0337;
#if SDL_VERSION_ATLEAST(2, 0, 18)
controller.m_hasRumble = (SDL_GameControllerHasRumble(ctrl) != 0u);
#else
controller.m_hasRumble = true;
#endif
Sint32 instance = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ctrl));
g_GameControllers[instance] = controller;
return instance;
}
return -1;
}
void remove_controller(Uint32 instance) noexcept {
if (g_GameControllers.find(instance) != g_GameControllers.end()) {
SDL_GameControllerClose(g_GameControllers[instance].m_controller);
g_GameControllers.erase(instance);
}
}
bool is_gamecube(Uint32 instance) noexcept {
if (g_GameControllers.find(instance) != g_GameControllers.end()) {
return g_GameControllers[instance].m_isGameCube;
}
return false;
}
int32_t player_index(Uint32 instance) noexcept {
if (g_GameControllers.find(instance) != g_GameControllers.end()) {
return SDL_GameControllerGetPlayerIndex(g_GameControllers[instance].m_controller);
}
return -1;
}
void set_player_index(Uint32 instance, Sint32 index) noexcept {
if (g_GameControllers.find(instance) != g_GameControllers.end()) {
SDL_GameControllerSetPlayerIndex(g_GameControllers[instance].m_controller, index);
}
}
std::string controller_name(Uint32 instance) noexcept {
if (g_GameControllers.find(instance) != g_GameControllers.end()) {
const auto* name = SDL_GameControllerName(g_GameControllers[instance].m_controller);
if (name != nullptr) {
return {name};
}
}
return {};
}
bool controller_has_rumble(Uint32 instance) noexcept {
if (g_GameControllers.find(instance) != g_GameControllers.end()) {
return g_GameControllers[instance].m_hasRumble;
}
return false;
}
void controller_rumble(uint32_t instance, uint16_t low_freq_intensity, uint16_t high_freq_intensity,
uint16_t duration_ms) noexcept {
if (g_GameControllers.find(instance) != g_GameControllers.end()) {
SDL_GameControllerRumble(g_GameControllers[instance].m_controller, low_freq_intensity, high_freq_intensity,
duration_ms);
}
}
uint32_t controller_count() noexcept { return g_GameControllers.size(); }
} // namespace aurora::input
static const std::array<PADButtonMapping, 12> mDefaultButtons{{
{SDL_CONTROLLER_BUTTON_A, PAD_BUTTON_A},
{SDL_CONTROLLER_BUTTON_B, PAD_BUTTON_B},
{SDL_CONTROLLER_BUTTON_X, PAD_BUTTON_X},
{SDL_CONTROLLER_BUTTON_Y, PAD_BUTTON_Y},
{SDL_CONTROLLER_BUTTON_START, PAD_BUTTON_START},
{SDL_CONTROLLER_BUTTON_BACK, PAD_TRIGGER_Z},
{SDL_CONTROLLER_BUTTON_LEFTSHOULDER, PAD_TRIGGER_L},
{SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, PAD_TRIGGER_R},
{SDL_CONTROLLER_BUTTON_DPAD_UP, PAD_BUTTON_UP},
{SDL_CONTROLLER_BUTTON_DPAD_DOWN, PAD_BUTTON_DOWN},
{SDL_CONTROLLER_BUTTON_DPAD_LEFT, PAD_BUTTON_LEFT},
{SDL_CONTROLLER_BUTTON_DPAD_RIGHT, PAD_BUTTON_RIGHT},
}};
void PADSetSpec(u32 spec) {}
BOOL PADInit() { return true; }
BOOL PADRecalibrate(u32 mask) { return true; }
BOOL PADReset(u32 mask) { return true; }
void PADSetAnalogMode(u32 mode) {}
aurora::input::GameController* __PADGetControllerForIndex(uint32_t idx) {
if (idx >= aurora::input::g_GameControllers.size()) {
return nullptr;
}
uint32_t tmp = 0;
auto iter = aurora::input::g_GameControllers.begin();
while (tmp < idx) {
++iter;
++tmp;
}
if (iter == aurora::input::g_GameControllers.end()) {
return nullptr;
}
return &iter->second;
}
uint32_t PADCount() { return aurora::input::g_GameControllers.size(); }
const char* PADGetNameForControllerIndex(uint32_t idx) {
auto* ctrl = __PADGetControllerForIndex(idx);
if (ctrl == nullptr) {
return nullptr;
}
return SDL_GameControllerName(ctrl->m_controller);
}
void PADSetPortForIndex(uint32_t idx, int32_t port) {
auto* ctrl = __PADGetControllerForIndex(idx);
auto* dest = aurora::input::get_controller_for_player(port);
if (ctrl == nullptr) {
return;
}
if (dest != nullptr) {
SDL_GameControllerSetPlayerIndex(dest->m_controller, -1);
}
SDL_GameControllerSetPlayerIndex(ctrl->m_controller, port);
}
int32_t PADGetIndexForPort(uint32_t port) {
auto* ctrl = aurora::input::get_controller_for_player(port);
if (ctrl == nullptr) {
return -1;
}
int32_t index = 0;
for (auto iter = aurora::input::g_GameControllers.begin(); iter != aurora::input::g_GameControllers.end();
++iter, ++index) {
if (&iter->second == ctrl) {
break;
}
}
return index;
}
void PADClearPort(uint32_t port) {
auto* ctrl = aurora::input::get_controller_for_player(port);
if (ctrl == nullptr) {
return;
}
SDL_GameControllerSetPlayerIndex(ctrl->m_controller, -1);
}
void __PADLoadMapping(aurora::input::GameController* controller) {
int32_t playerIndex = SDL_GameControllerGetPlayerIndex(controller->m_controller);
if (playerIndex == -1) {
return;
}
std::string basePath{aurora::g_config.configPath};
if (!controller->m_mappingLoaded) {
controller->m_mapping = mDefaultButtons;
}
controller->m_mappingLoaded = true;
auto path = fmt::format(FMT_STRING("{}/{}_{:04X}_{:04X}.controller"), basePath, PADGetName(playerIndex),
controller->m_vid, controller->m_pid);
FILE* file = fopen(path.c_str(), "rb");
if (file == nullptr) {
return;
}
uint32_t magic = 0;
fread(&magic, 1, sizeof(uint32_t), file);
if (magic != SBIG('CTRL')) {
fmt::print(FMT_STRING("Invalid controller mapping magic!\n"));
return;
}
uint32_t version = 0;
fread(&version, 1, sizeof(uint32_t), file);
if (version != 1) {
fmt::print(FMT_STRING("Invalid controller mapping version!\n"));
return;
}
bool isGameCube = false;
fread(&isGameCube, 1, 1, file);
fseek(file, (ftell(file) + 31) & ~31, SEEK_SET);
uint32_t dataStart = ftell(file);
if (isGameCube) {
fseek(file, dataStart + ((sizeof(PADDeadZones) + sizeof(PADButtonMapping)) * playerIndex), SEEK_SET);
}
fread(&controller->m_deadZones, 1, sizeof(PADDeadZones), file);
fread(&controller->m_mapping, 1, sizeof(PADButtonMapping) * controller->m_mapping.size(), file);
fclose(file);
}
bool gBlockPAD = false;
uint32_t PADRead(PADStatus* status) {
if (gBlockPAD) {
return 0;
}
uint32_t rumbleSupport = 0;
for (uint32_t i = 0; i < 4; ++i) {
memset(&status[i], 0, sizeof(PADStatus));
auto controller = aurora::input::get_controller_for_player(i);
if (controller == nullptr) {
status[i].err = PAD_ERR_NO_CONTROLLER;
continue;
}
if (!controller->m_mappingLoaded) {
__PADLoadMapping(controller);
}
status[i].err = PAD_ERR_NONE;
std::for_each(controller->m_mapping.begin(), controller->m_mapping.end(),
[&controller, &i, &status](const auto& mapping) {
if (SDL_GameControllerGetButton(controller->m_controller,
static_cast<SDL_GameControllerButton>(mapping.nativeButton))) {
status[i].button |= mapping.padButton;
}
});
Sint16 x = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_LEFTX);
Sint16 y = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_LEFTY);
if (controller->m_deadZones.useDeadzones) {
if (std::abs(x) > controller->m_deadZones.stickDeadZone) {
x /= 256;
} else {
x = 0;
}
if (std::abs(y) > controller->m_deadZones.stickDeadZone) {
y = (-(y + 1u)) / 256u;
} else {
y = 0;
}
} else {
x /= 256;
y = (-(y + 1u)) / 256u;
}
status[i].stickX = static_cast<int8_t>(x);
status[i].stickY = static_cast<int8_t>(y);
x = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_RIGHTX);
y = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_RIGHTY);
if (controller->m_deadZones.useDeadzones) {
if (std::abs(x) > controller->m_deadZones.substickDeadZone) {
x /= 256;
} else {
x = 0;
}
if (std::abs(y) > controller->m_deadZones.substickDeadZone) {
y = (-(y + 1u)) / 256u;
} else {
y = 0;
}
} else {
x /= 256;
y = (-(y + 1u)) / 256u;
}
status[i].substickX = static_cast<int8_t>(x);
status[i].substickY = static_cast<int8_t>(y);
x = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
y = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
if (/*!controller->m_isGameCube && */ controller->m_deadZones.emulateTriggers) {
if (x > controller->m_deadZones.leftTriggerActivationZone) {
status[i].button |= PAD_TRIGGER_L;
}
if (y > controller->m_deadZones.rightTriggerActivationZone) {
status[i].button |= PAD_TRIGGER_R;
}
}
x /= 128;
y /= 128;
status[i].triggerL = static_cast<int8_t>(x);
status[i].triggerR = static_cast<int8_t>(y);
if (controller->m_hasRumble) {
rumbleSupport |= PAD_CHAN0_BIT >> i;
}
}
return rumbleSupport;
}
void PADControlAllMotors(const uint32_t* commands) {
for (uint32_t i = 0; i < 4; ++i) {
auto controller = aurora::input::get_controller_for_player(i);
auto instance = aurora::input::get_instance_for_player(i);
if (controller == nullptr) {
continue;
}
if (controller->m_isGameCube) {
if (commands[i] == PAD_MOTOR_STOP) {
aurora::input::controller_rumble(instance, 0, 1, 0);
} else if (commands[i] == PAD_MOTOR_RUMBLE) {
aurora::input::controller_rumble(instance, 1, 1, 0);
} else if (commands[i] == PAD_MOTOR_STOP_HARD) {
aurora::input::controller_rumble(instance, 0, 0, 0);
}
} else {
if (commands[i] == PAD_MOTOR_STOP) {
aurora::input::controller_rumble(instance, 0, 0, 1);
} else if (commands[i] == PAD_MOTOR_RUMBLE) {
aurora::input::controller_rumble(instance, 32767, 32767, 0);
} else if (commands[i] == PAD_MOTOR_STOP_HARD) {
aurora::input::controller_rumble(instance, 0, 0, 0);
}
}
}
}
uint32_t SIProbe(int32_t chan) {
auto* const controller = aurora::input::get_controller_for_player(chan);
if (controller == nullptr) {
return SI_ERROR_NO_RESPONSE;
}
if (controller->m_isGameCube) {
auto level = SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(controller->m_controller));
if (level == SDL_JOYSTICK_POWER_UNKNOWN) {
return SI_GC_WAVEBIRD;
}
}
return SI_GC_CONTROLLER;
}
struct PADCLampRegion {
uint8_t minTrigger;
uint8_t maxTrigger;
int8_t minStick;
int8_t maxStick;
int8_t xyStick;
int8_t minSubstick;
int8_t maxSubstick;
int8_t xySubstick;
int8_t radStick;
int8_t radSubstick;
};
static constexpr PADCLampRegion ClampRegion{
// Triggers
30,
180,
// Left stick
15,
72,
40,
// Right stick
15,
59,
31,
// Stick radii
56,
44,
};
void ClampTrigger(uint8_t* trigger, uint8_t min, uint8_t max) {
if (*trigger <= min) {
*trigger = 0;
} else {
if (*trigger > max) {
*trigger = max;
}
*trigger -= min;
}
}
void ClampCircle(int8_t* px, int8_t* py, int8_t radius, int8_t min) {
int x = *px;
int y = *py;
if (-min < x && x < min) {
x = 0;
} else if (0 < x) {
x -= min;
} else {
x += min;
}
if (-min < y && y < min) {
y = 0;
} else if (0 < y) {
y -= min;
} else {
y += min;
}
int squared = x * x + y * y;
if (radius * radius < squared) {
int32_t length = static_cast<int32_t>(std::sqrt(squared));
x = (x * radius) / length;
y = (y * radius) / length;
}
*px = static_cast<int8_t>(x);
*py = static_cast<int8_t>(y);
}
void ClampStick(int8_t* px, int8_t* py, int8_t max, int8_t xy, int8_t min) {
int32_t x = *px;
int32_t y = *py;
int32_t signX = 0;
if (0 <= x) {
signX = 1;
} else {
signX = -1;
x = -x;
}
int8_t signY = 0;
if (0 <= y) {
signY = 1;
} else {
signY = -1;
y = -y;
}
if (x <= min) {
x = 0;
} else {
x -= min;
}
if (y <= min) {
y = 0;
} else {
y -= min;
}
if (x == 0 && y == 0) {
*px = *py = 0;
return;
}
if (xy * y <= xy * x) {
int32_t d = xy * x + (max - xy) * y;
if (xy * max < d) {
x = (xy * max * x / d);
y = (xy * max * y / d);
}
} else {
int32_t d = xy * y + (max - xy) * x;
if (xy * max < d) {
x = (xy * max * x / d);
y = (xy * max * y / d);
}
}
*px = (signX * x);
*py = (signY * y);
}
void PADClamp(PADStatus* status) {
for (uint32_t i = 0; i < 4; ++i) {
if (status[i].err != PAD_ERR_NONE) {
continue;
}
ClampStick(&status[i].stickX, &status[i].stickY, ClampRegion.maxStick, ClampRegion.xyStick, ClampRegion.minStick);
ClampStick(&status[i].substickX, &status[i].substickY, ClampRegion.maxSubstick, ClampRegion.xySubstick,
ClampRegion.minSubstick);
ClampTrigger(&status[i].triggerL, ClampRegion.minTrigger, ClampRegion.maxTrigger);
ClampTrigger(&status[i].triggerR, ClampRegion.minTrigger, ClampRegion.maxTrigger);
}
}
void PADClampCircle(PADStatus* status) {
for (uint32_t i = 0; i < 4; ++i) {
if (status[i].err != PAD_ERR_NONE) {
continue;
}
ClampCircle(&status[i].stickX, &status[i].stickY, ClampRegion.radStick, ClampRegion.minStick);
ClampCircle(&status[i].substickX, &status[i].substickY, ClampRegion.radSubstick, ClampRegion.minSubstick);
ClampTrigger(&status[i].triggerL, ClampRegion.minTrigger, ClampRegion.maxTrigger);
ClampTrigger(&status[i].triggerR, ClampRegion.minTrigger, ClampRegion.maxTrigger);
}
}
void PADGetVidPid(uint32_t port, uint32_t* vid, uint32_t* pid) {
*vid = 0;
*pid = 0;
auto* controller = aurora::input::get_controller_for_player(port);
if (controller == nullptr) {
return;
}
*vid = controller->m_vid;
*pid = controller->m_pid;
}
const char* PADGetName(uint32_t port) {
auto* controller = aurora::input::get_controller_for_player(port);
if (controller == nullptr) {
return nullptr;
}
return SDL_GameControllerName(controller->m_controller);
}
void PADSetButtonMapping(uint32_t port, PADButtonMapping mapping) {
auto* controller = aurora::input::get_controller_for_player(port);
if (controller == nullptr) {
return;
}
auto iter = std::find_if(controller->m_mapping.begin(), controller->m_mapping.end(),
[mapping](const auto& pair) { return mapping.padButton == pair.padButton; });
if (iter == controller->m_mapping.end()) {
return;
}
*iter = mapping;
}
void PADSetAllButtonMappings(uint32_t port, PADButtonMapping buttons[12]) {
for (uint32_t i = 0; i < 12; ++i) {
PADSetButtonMapping(port, buttons[i]);
}
}
PADButtonMapping* PADGetButtonMappings(uint32_t port, uint32_t* buttonCount) {
auto* controller = aurora::input::get_controller_for_player(port);
if (controller == nullptr) {
*buttonCount = 0;
return nullptr;
}
*buttonCount = controller->m_mapping.size();
return controller->m_mapping.data();
}
void __PADWriteDeadZones(FILE* file, aurora::input::GameController& controller) {
fwrite(&controller.m_deadZones, 1, sizeof(PADDeadZones), file);
}
void PADSerializeMappings() {
std::string basePath{aurora::g_config.configPath};
bool wroteGameCubeAlready = false;
for (auto& controller : aurora::input::g_GameControllers) {
if (!controller.second.m_mappingLoaded) {
__PADLoadMapping(&controller.second);
}
FILE* file = fopen(fmt::format(FMT_STRING("{}/{}_{:04X}_{:04X}.controller"), basePath,
aurora::input::controller_name(controller.second.m_index), controller.second.m_vid,
controller.second.m_pid)
.c_str(),
"wbe");
if (file == nullptr) {
return;
}
uint32_t magic = SBIG('CTRL');
uint32_t version = 1;
fwrite(&magic, 1, sizeof(magic), file);
fwrite(&version, 1, sizeof(magic), file);
fwrite(&controller.second.m_isGameCube, 1, 1, file);
fseek(file, (ftell(file) + 31) & ~31, SEEK_SET);
int32_t dataStart = ftell(file);
if (!controller.second.m_isGameCube) {
__PADWriteDeadZones(file, controller.second);
fwrite(controller.second.m_mapping.data(), 1, sizeof(PADButtonMapping) * controller.second.m_mapping.size(),
file);
} else {
if (!wroteGameCubeAlready) {
for (uint32_t i = 0; i < 4; ++i) {
/* Just use the current controller's configs for this */
__PADWriteDeadZones(file, controller.second);
fwrite(mDefaultButtons.data(), 1, sizeof(PADButtonMapping) * mDefaultButtons.size(), file);
}
fflush(file);
wroteGameCubeAlready = true;
}
uint32_t port = aurora::input::player_index(controller.second.m_index);
fseek(file, dataStart + ((sizeof(PADDeadZones) + sizeof(PADButtonMapping)) * port), SEEK_SET);
__PADWriteDeadZones(file, controller.second);
fwrite(controller.second.m_mapping.data(), 1, sizeof(PADButtonMapping) * controller.second.m_mapping.size(),
file);
}
fclose(file);
}
}
PADDeadZones* PADGetDeadZones(uint32_t port) {
auto* controller = aurora::input::get_controller_for_player(port);
if (controller == nullptr) {
return nullptr;
}
return &controller->m_deadZones;
}
static constexpr std::array<std::pair<PADButton, std::string_view>, 12> skButtonNames = {{
{PAD_BUTTON_LEFT, "Left"sv},
{PAD_BUTTON_RIGHT, "Right"sv},
{PAD_BUTTON_DOWN, "Down"sv},
{PAD_BUTTON_UP, "Up"sv},
{PAD_TRIGGER_Z, "Z"sv},
{PAD_TRIGGER_R, "R"sv},
{PAD_TRIGGER_L, "L"sv},
{PAD_BUTTON_A, "A"sv},
{PAD_BUTTON_B, "B"sv},
{PAD_BUTTON_X, "X"sv},
{PAD_BUTTON_Y, "Y"sv},
{PAD_BUTTON_START, "Start"sv},
}};
const char* PADGetButtonName(PADButton button) {
auto it = std::find_if(skButtonNames.begin(), skButtonNames.end(),
[&button](const auto& pair) { return button == pair.first; });
if (it != skButtonNames.end()) {
return it->second.data();
}
return nullptr;
}
const char* PADGetNativeButtonName(uint32_t button) {
return SDL_GameControllerGetStringForButton(static_cast<SDL_GameControllerButton>(button));
}
int32_t PADGetNativeButtonPressed(uint32_t port) {
auto* controller = aurora::input::get_controller_for_player(port);
if (controller == nullptr) {
return -1;
}
for (uint32_t i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i) {
if (SDL_GameControllerGetButton(controller->m_controller, static_cast<SDL_GameControllerButton>(i)) != 0u) {
return i;
}
}
return -1;
}
void PADRestoreDefaultMapping(uint32_t port) {
auto* controller = aurora::input::get_controller_for_player(port);
if (controller == nullptr) {
return;
}
controller->m_mapping = mDefaultButtons;
}
void PADBlockInput(bool block) { gBlockPAD = block; }

22
lib/input.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <string>
#include "SDL_gamecontroller.h"
#include "SDL_keyboard.h"
#include "SDL_keycode.h"
#include "SDL_mouse.h"
namespace aurora::input {
Sint32 get_instance_for_player(uint32_t player) noexcept;
Sint32 add_controller(Sint32 which) noexcept;
void remove_controller(Uint32 instance) noexcept;
Sint32 player_index(Uint32 instance) noexcept;
void set_player_index(Uint32 instance, Sint32 index) noexcept;
std::string controller_name(Uint32 instance) noexcept;
bool is_gamecube(Uint32 instance) noexcept;
bool controller_has_rumble(Uint32 instance) noexcept;
void controller_rumble(uint32_t instance, uint16_t low_freq_intensity, uint16_t high_freq_intensity,
uint16_t duration_ms) noexcept;
uint32_t controller_count() noexcept;
} // namespace aurora::input

113
lib/internal.hpp Normal file
View File

@ -0,0 +1,113 @@
#pragma once
#include <aurora/aurora.h>
#include <fmt/format.h>
#include <string>
#include <string_view>
#include <vector>
#include <cassert>
using namespace std::string_view_literals;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#ifndef SBIG
#define SBIG(q) (((q)&0x000000FF) << 24 | ((q)&0x0000FF00) << 8 | ((q)&0x00FF0000) >> 8 | ((q)&0xFF000000) >> 24)
#endif
#else
#ifndef SBIG
#define SBIG(q) (q)
#endif
#endif
#ifdef __GNUC__
[[noreturn]] inline __attribute__((always_inline)) void unreachable() { __builtin_unreachable(); }
#elif defined(_MSC_VER)
[[noreturn]] __forceinline void unreachable() { __assume(false); }
#else
#error Unknown compiler
#endif
#ifndef ALIGN
#define ALIGN(x, a) (((x) + ((a)-1)) & ~((a)-1))
#endif
namespace aurora {
extern AuroraConfig g_config;
struct Module {
const char* name;
explicit Module(const char* name) noexcept : name(name) {}
template <typename... T>
inline void report(AuroraLogLevel level, fmt::format_string<T...> fmt, T&&... args) noexcept {
auto message = fmt::format(fmt, std::forward<T>(args)...);
if (g_config.logCallback != nullptr) {
g_config.logCallback(level, message.c_str(), message.size());
}
}
};
template <typename T>
class ArrayRef {
public:
using value_type = std::remove_cvref_t<T>;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = const_pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
ArrayRef() = default;
explicit ArrayRef(const T& one) : ptr(&one), length(1) {}
ArrayRef(const T* data, size_t length) : ptr(data), length(length) {}
ArrayRef(const T* begin, const T* end) : ptr(begin), length(end - begin) {}
template <size_t N>
constexpr ArrayRef(const T (&arr)[N]) : ptr(arr), length(N) {}
template <size_t N>
constexpr ArrayRef(const std::array<T, N>& arr) : ptr(arr.data()), length(arr.size()) {}
ArrayRef(const std::vector<T>& vec) : ptr(vec.data()), length(vec.size()) {}
// template <size_t N>
// ArrayRef(const rstl::reserved_vector<T, N>& vec) : ptr(vec.data()), length(vec.size()) {}
const T* data() const { return ptr; }
size_t size() const { return length; }
bool empty() const { return length == 0; }
const T& front() const {
assert(!empty());
return ptr[0];
}
const T& back() const {
assert(!empty());
return ptr[length - 1];
}
const T& operator[](size_t i) const {
assert(i < length && "Invalid index!");
return ptr[i];
}
iterator begin() const { return ptr; }
iterator end() const { return ptr + length; }
reverse_iterator rbegin() const { return reverse_iterator(end()); }
reverse_iterator rend() const { return reverse_iterator(begin()); }
/// Disallow accidental assignment from a temporary.
template <typename U>
std::enable_if_t<std::is_same<U, T>::value, ArrayRef<T>>& operator=(U&& Temporary) = delete;
/// Disallow accidental assignment from a temporary.
template <typename U>
std::enable_if_t<std::is_same<U, T>::value, ArrayRef<T>>& operator=(std::initializer_list<U>) = delete;
private:
const T* ptr = nullptr;
size_t length = 0;
};
} // namespace aurora

6
lib/main.cpp Normal file
View File

@ -0,0 +1,6 @@
#include <aurora/main.h>
#undef main
#include <SDL_main.h>
int main(int argc, char** argv) { return aurora_main(argc, argv); }

5
lib/pad.hpp Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include <dolphin/pad.h>
#include <dolphin/si.h>

466
lib/webgpu/gpu.cpp Normal file
View File

@ -0,0 +1,466 @@
#include "gpu.hpp"
#include <aurora/aurora.h>
#include "../window.hpp"
#include "../internal.hpp"
#include <SDL.h>
#include <dawn/native/DawnNative.h>
#include <magic_enum.hpp>
#include <memory>
#include <algorithm>
#include "../dawn/BackendBinding.hpp"
namespace aurora::webgpu {
static Module Log("aurora::gpu");
WGPUDevice g_device;
WGPUQueue g_queue;
WGPUSwapChain g_swapChain;
WGPUBackendType g_backendType;
GraphicsConfig g_graphicsConfig;
TextureWithSampler g_frameBuffer;
TextureWithSampler g_frameBufferResolved;
TextureWithSampler g_depthBuffer;
// EFB -> XFB copy pipeline
static WGPUBindGroupLayout g_CopyBindGroupLayout;
WGPURenderPipeline g_CopyPipeline;
WGPUBindGroup g_CopyBindGroup;
static std::unique_ptr<dawn::native::Instance> g_Instance;
static dawn::native::Adapter g_Adapter;
static WGPUAdapterProperties g_AdapterProperties;
static std::unique_ptr<utils::BackendBinding> g_BackendBinding;
TextureWithSampler create_render_texture(bool multisampled) {
const WGPUExtent3D size{
.width = g_graphicsConfig.width,
.height = g_graphicsConfig.height,
.depthOrArrayLayers = 1,
};
const auto format = g_graphicsConfig.colorFormat;
uint32_t sampleCount = 1;
if (multisampled) {
sampleCount = g_graphicsConfig.msaaSamples;
}
const WGPUTextureDescriptor textureDescriptor{
.label = "Render texture",
.usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopySrc |
WGPUTextureUsage_CopyDst,
.dimension = WGPUTextureDimension_2D,
.size = size,
.format = format,
.mipLevelCount = 1,
.sampleCount = sampleCount,
};
auto texture = wgpuDeviceCreateTexture(g_device, &textureDescriptor);
const WGPUTextureViewDescriptor viewDescriptor{
.dimension = WGPUTextureViewDimension_2D,
.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED,
.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED,
};
auto view = wgpuTextureCreateView(texture, &viewDescriptor);
const WGPUSamplerDescriptor samplerDescriptor{
.label = "Render sampler",
.addressModeU = WGPUAddressMode_ClampToEdge,
.addressModeV = WGPUAddressMode_ClampToEdge,
.addressModeW = WGPUAddressMode_ClampToEdge,
.magFilter = WGPUFilterMode_Linear,
.minFilter = WGPUFilterMode_Linear,
.mipmapFilter = WGPUFilterMode_Linear,
.lodMinClamp = 0.f,
.lodMaxClamp = 1000.f,
.maxAnisotropy = 1,
};
auto sampler = wgpuDeviceCreateSampler(g_device, &samplerDescriptor);
return {
.texture{texture},
.view{view},
.size = size,
.format = format,
.sampler{sampler},
};
}
static TextureWithSampler create_depth_texture() {
const WGPUExtent3D size{
.width = g_graphicsConfig.width,
.height = g_graphicsConfig.height,
.depthOrArrayLayers = 1,
};
const auto format = g_graphicsConfig.depthFormat;
const WGPUTextureDescriptor textureDescriptor{
.label = "Depth texture",
.usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding,
.dimension = WGPUTextureDimension_2D,
.size = size,
.format = format,
.mipLevelCount = 1,
.sampleCount = g_graphicsConfig.msaaSamples,
};
auto texture = wgpuDeviceCreateTexture(g_device, &textureDescriptor);
const WGPUTextureViewDescriptor viewDescriptor{
.dimension = WGPUTextureViewDimension_2D,
.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED,
.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED,
};
auto view = wgpuTextureCreateView(texture, &viewDescriptor);
const WGPUSamplerDescriptor samplerDescriptor{
.label = "Depth sampler",
.addressModeU = WGPUAddressMode_ClampToEdge,
.addressModeV = WGPUAddressMode_ClampToEdge,
.addressModeW = WGPUAddressMode_ClampToEdge,
.magFilter = WGPUFilterMode_Linear,
.minFilter = WGPUFilterMode_Linear,
.mipmapFilter = WGPUFilterMode_Linear,
.lodMinClamp = 0.f,
.lodMaxClamp = 1000.f,
.maxAnisotropy = 1,
};
auto sampler = wgpuDeviceCreateSampler(g_device, &samplerDescriptor);
return {
.texture{texture},
.view{view},
.size = size,
.format = format,
.sampler{sampler},
};
}
void create_copy_pipeline() {
WGPUShaderModuleWGSLDescriptor sourceDescriptor{
.chain = {.sType = WGPUSType_ShaderModuleWGSLDescriptor},
.source = R"""(
@group(0) @binding(0)
var efb_sampler: sampler;
@group(0) @binding(1)
var efb_texture: texture_2d<f32>;
struct VertexOutput {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
};
var<private> pos: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
vec2(-1.0, 1.0),
vec2(-1.0, -3.0),
vec2(3.0, 1.0),
);
var<private> uvs: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
vec2(0.0, 0.0),
vec2(0.0, 2.0),
vec2(2.0, 0.0),
);
@stage(vertex)
fn vs_main(@builtin(vertex_index) vtxIdx: u32) -> VertexOutput {
var out: VertexOutput;
out.pos = vec4<f32>(pos[vtxIdx], 0.0, 1.0);
out.uv = uvs[vtxIdx];
return out;
}
@stage(fragment)
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(efb_texture, efb_sampler, in.uv);
}
)""",
};
const WGPUShaderModuleDescriptor moduleDescriptor{
.nextInChain = &sourceDescriptor.chain,
.label = "XFB Copy Module",
};
auto module = wgpuDeviceCreateShaderModule(g_device, &moduleDescriptor);
const std::array colorTargets{WGPUColorTargetState{
.format = g_graphicsConfig.colorFormat,
.writeMask = WGPUColorWriteMask_All,
}};
const WGPUFragmentState fragmentState{
.module = module,
.entryPoint = "fs_main",
.targetCount = colorTargets.size(),
.targets = colorTargets.data(),
};
const std::array bindGroupLayoutEntries{
WGPUBindGroupLayoutEntry{
.binding = 0,
.visibility = WGPUShaderStage_Fragment,
.sampler =
WGPUSamplerBindingLayout{
.type = WGPUSamplerBindingType_Filtering,
},
},
WGPUBindGroupLayoutEntry{
.binding = 1,
.visibility = WGPUShaderStage_Fragment,
.texture =
WGPUTextureBindingLayout{
.sampleType = WGPUTextureSampleType_Float,
.viewDimension = WGPUTextureViewDimension_2D,
},
},
};
const WGPUBindGroupLayoutDescriptor bindGroupLayoutDescriptor{
.entryCount = bindGroupLayoutEntries.size(),
.entries = bindGroupLayoutEntries.data(),
};
g_CopyBindGroupLayout = wgpuDeviceCreateBindGroupLayout(g_device, &bindGroupLayoutDescriptor);
const WGPUPipelineLayoutDescriptor layoutDescriptor{
.bindGroupLayoutCount = 1,
.bindGroupLayouts = &g_CopyBindGroupLayout,
};
auto pipelineLayout = wgpuDeviceCreatePipelineLayout(g_device, &layoutDescriptor);
const WGPURenderPipelineDescriptor pipelineDescriptor{
.layout = pipelineLayout,
.vertex =
WGPUVertexState{
.module = module,
.entryPoint = "vs_main",
},
.primitive =
WGPUPrimitiveState{
.topology = WGPUPrimitiveTopology_TriangleList,
},
.multisample =
WGPUMultisampleState{
.count = 1,
.mask = UINT32_MAX,
},
.fragment = &fragmentState,
};
g_CopyPipeline = wgpuDeviceCreateRenderPipeline(g_device, &pipelineDescriptor);
wgpuPipelineLayoutRelease(pipelineLayout);
}
void create_copy_bind_group() {
const std::array bindGroupEntries{
WGPUBindGroupEntry{
.binding = 0,
.sampler = g_graphicsConfig.msaaSamples > 1 ? g_frameBufferResolved.sampler : g_frameBuffer.sampler,
},
WGPUBindGroupEntry{
.binding = 1,
.textureView = g_graphicsConfig.msaaSamples > 1 ? g_frameBufferResolved.view : g_frameBuffer.view,
},
};
const WGPUBindGroupDescriptor bindGroupDescriptor{
.layout = g_CopyBindGroupLayout,
.entryCount = bindGroupEntries.size(),
.entries = bindGroupEntries.data(),
};
g_CopyBindGroup = wgpuDeviceCreateBindGroup(g_device, &bindGroupDescriptor);
}
static void error_callback(WGPUErrorType type, char const* message, void* userdata) {
Log.report(LOG_FATAL, FMT_STRING("Dawn error {}: {}"), magic_enum::enum_name(static_cast<WGPUErrorType>(type)),
message);
}
static void device_callback(WGPURequestDeviceStatus status, WGPUDevice device, char const* message, void* userdata) {
if (status == WGPURequestDeviceStatus_Success) {
g_device = device;
} else {
Log.report(LOG_WARNING, FMT_STRING("Device request failed with message: {}"), message);
}
*static_cast<bool*>(userdata) = true;
}
static WGPUBackendType to_wgpu_backend(AuroraBackend backend) {
switch (backend) {
case BACKEND_WEBGPU:
return WGPUBackendType_WebGPU;
case BACKEND_D3D12:
return WGPUBackendType_D3D12;
case BACKEND_METAL:
return WGPUBackendType_Metal;
case BACKEND_VULKAN:
return WGPUBackendType_Vulkan;
case BACKEND_OPENGL:
return WGPUBackendType_OpenGL;
case BACKEND_OPENGLES:
return WGPUBackendType_OpenGLES;
default:
return WGPUBackendType_Null;
}
}
bool initialize(AuroraBackend auroraBackend) {
if (!g_Instance) {
Log.report(LOG_INFO, FMT_STRING("Creating Dawn instance"));
g_Instance = std::make_unique<dawn::native::Instance>();
}
WGPUBackendType backend = to_wgpu_backend(auroraBackend);
Log.report(LOG_INFO, FMT_STRING("Attempting to initialize {}"), magic_enum::enum_name(backend));
#if 0
// D3D12's debug layer is very slow
g_Instance->EnableBackendValidation(backend != WGPUBackendType::D3D12);
#endif
SDL_Window* window = window::get_sdl_window();
if (!utils::DiscoverAdapter(g_Instance.get(), window, backend)) {
return false;
}
{
std::vector<dawn::native::Adapter> adapters = g_Instance->GetAdapters();
std::sort(adapters.begin(), adapters.end(), [&](const auto& a, const auto& b) {
WGPUAdapterProperties propertiesA;
WGPUAdapterProperties propertiesB;
a.GetProperties(&propertiesA);
b.GetProperties(&propertiesB);
constexpr std::array PreferredTypeOrder{
WGPUAdapterType_DiscreteGPU,
WGPUAdapterType_IntegratedGPU,
WGPUAdapterType_CPU,
};
const auto typeItA = std::find(PreferredTypeOrder.begin(), PreferredTypeOrder.end(), propertiesA.adapterType);
const auto typeItB = std::find(PreferredTypeOrder.begin(), PreferredTypeOrder.end(), propertiesB.adapterType);
return typeItA < typeItB;
});
const auto adapterIt = std::find_if(adapters.begin(), adapters.end(), [=](const auto& adapter) -> bool {
WGPUAdapterProperties properties;
adapter.GetProperties(&properties);
return properties.backendType == backend;
});
if (adapterIt == adapters.end()) {
return false;
}
g_Adapter = *adapterIt;
}
g_Adapter.GetProperties(&g_AdapterProperties);
g_backendType = g_AdapterProperties.backendType;
const auto backendName = magic_enum::enum_name(g_backendType);
Log.report(LOG_INFO, FMT_STRING("Graphics adapter information\n API: {}\n Device: {} ({})\n Driver: {}"),
backendName, g_AdapterProperties.name, magic_enum::enum_name(g_AdapterProperties.adapterType),
g_AdapterProperties.driverDescription);
{
WGPUSupportedLimits supportedLimits{};
g_Adapter.GetLimits(&supportedLimits);
const WGPURequiredLimits requiredLimits{
.limits =
{
// Use "best" supported alignments
.minUniformBufferOffsetAlignment = supportedLimits.limits.minUniformBufferOffsetAlignment == 0
? static_cast<uint32_t>(WGPU_LIMIT_U32_UNDEFINED)
: supportedLimits.limits.minUniformBufferOffsetAlignment,
.minStorageBufferOffsetAlignment = supportedLimits.limits.minStorageBufferOffsetAlignment == 0
? static_cast<uint32_t>(WGPU_LIMIT_U32_UNDEFINED)
: supportedLimits.limits.minStorageBufferOffsetAlignment,
},
};
std::vector<WGPUFeatureName> features;
const auto supportedFeatures = g_Adapter.GetSupportedFeatures();
for (const auto* const feature : supportedFeatures) {
if (strcmp(feature, "texture-compression-bc") == 0) {
features.push_back(WGPUFeatureName_TextureCompressionBC);
}
}
const std::array enableToggles {
/* clang-format off */
#if _WIN32
"use_dxc",
#endif
#ifdef NDEBUG
"skip_validation",
"disable_robustness",
#endif
"use_user_defined_labels_in_backend",
"disable_symbol_renaming",
/* clang-format on */
};
const WGPUDawnTogglesDeviceDescriptor togglesDescriptor{
.chain = {.sType = WGPUSType_DawnTogglesDeviceDescriptor},
.forceEnabledTogglesCount = enableToggles.size(),
.forceEnabledToggles = enableToggles.data(),
};
const WGPUDeviceDescriptor deviceDescriptor{
.nextInChain = &togglesDescriptor.chain,
.requiredFeaturesCount = static_cast<uint32_t>(features.size()),
.requiredFeatures = features.data(),
.requiredLimits = &requiredLimits,
};
bool deviceCallbackReceived = false;
g_Adapter.RequestDevice(&deviceDescriptor, &device_callback, &deviceCallbackReceived);
// while (!deviceCallbackReceived) {
// TODO wgpuInstanceProcessEvents
// }
if (!g_device) {
return false;
}
wgpuDeviceSetUncapturedErrorCallback(g_device, &error_callback, nullptr);
}
wgpuDeviceSetDeviceLostCallback(g_device, nullptr, nullptr);
g_queue = wgpuDeviceGetQueue(g_device);
g_BackendBinding = std::unique_ptr<utils::BackendBinding>(utils::CreateBinding(g_backendType, window, g_device));
if (!g_BackendBinding) {
return false;
}
auto swapChainFormat = static_cast<WGPUTextureFormat>(g_BackendBinding->GetPreferredSwapChainTextureFormat());
if (swapChainFormat == WGPUTextureFormat_RGBA8UnormSrgb) {
swapChainFormat = WGPUTextureFormat_RGBA8Unorm;
} else if (swapChainFormat == WGPUTextureFormat_BGRA8UnormSrgb) {
swapChainFormat = WGPUTextureFormat_BGRA8Unorm;
}
Log.report(LOG_INFO, FMT_STRING("Using swapchain format {}"), magic_enum::enum_name(swapChainFormat));
{
const WGPUSwapChainDescriptor descriptor{
.format = swapChainFormat,
.implementation = g_BackendBinding->GetSwapChainImplementation(),
};
g_swapChain = wgpuDeviceCreateSwapChain(g_device, nullptr, &descriptor);
}
{
const auto size = window::get_window_size();
g_graphicsConfig = GraphicsConfig{
.width = size.fb_width,
.height = size.fb_height,
.colorFormat = swapChainFormat,
.depthFormat = WGPUTextureFormat_Depth32Float,
.msaaSamples = g_config.msaa,
.textureAnisotropy = g_config.maxTextureAnisotropy,
};
create_copy_pipeline();
resize_swapchain(size.fb_width, size.fb_height, true);
// g_windowSize = size;
}
return true;
}
void shutdown() {
wgpuBindGroupLayoutRelease(g_CopyBindGroupLayout);
wgpuRenderPipelineRelease(g_CopyPipeline);
wgpuBindGroupRelease(g_CopyBindGroup);
g_frameBuffer = {};
g_frameBufferResolved = {};
g_depthBuffer = {};
wgpuSwapChainRelease(g_swapChain);
wgpuQueueRelease(g_queue);
g_BackendBinding.reset();
wgpuDeviceDestroy(g_device);
g_Instance.reset();
}
void resize_swapchain(uint32_t width, uint32_t height, bool force) {
if (!force && g_graphicsConfig.width == width && g_graphicsConfig.height == height) {
return;
}
g_graphicsConfig.width = width;
g_graphicsConfig.height = height;
wgpuSwapChainConfigure(g_swapChain, g_graphicsConfig.colorFormat, WGPUTextureUsage_RenderAttachment, width, height);
g_frameBuffer = create_render_texture(true);
g_frameBufferResolved = create_render_texture(false);
g_depthBuffer = create_depth_texture();
create_copy_bind_group();
}
} // namespace aurora::webgpu

Some files were not shown because too many files have changed in this diff Show More