diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..c2bdf2a9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: bjornbytes diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..7f9803eb --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,114 @@ +name: Build + +on: [push, pull_request] + +env: + CMAKE_BUILD_TYPE: ${{ github.event_name == 'pull_request' && 'Debug' || 'Release' }} + +jobs: + windows: + name: Windows + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Configure + run: cmake -B build + shell: cmd + - name: Build + run: cmake --build build --config %CMAKE_BUILD_TYPE% + shell: cmd + - name: Upload + uses: actions/upload-artifact@v3 + with: + name: lovr.exe + path: | + build/Release/lovr.exe + build/Release/*.dll + linux: + name: Linux + runs-on: ubuntu-22.04 + steps: + - name: Update Packages + run: sudo apt update + - name: Install Packages + run: sudo apt install -y xorg-dev libxcb-glx0-dev libfuse2 + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Init + run: cmake -B build -D LOVR_BUILD_BUNDLE=ON + - name: Build + run: cmake --build build + - name: AppImage + run: > + curl -OL https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage && + chmod +x ./appimagetool-x86_64.AppImage && + ./appimagetool-x86_64.AppImage build/bin && + mv LÖVR-x86_64.AppImage lovr-x86_64.AppImage + - name: Upload + uses: actions/upload-artifact@v3 + with: + name: lovr.appimage + path: lovr-x86_64.AppImage + android: + name: Android + runs-on: ubuntu-22.04 + steps: + - name: Update Packages + run: sudo apt update + - name: Install Packages + run: sudo apt install -y glslang-tools + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Init + run: > + mkdir build && + cd build && + keytool + -genkey + -dname 'cn=Unknown, ou=Unknown, o=Unknown, l=Unknown, st=Unknown, c=Unknown' + -keystore key.keystore + -keypass hunter2 + -storepass hunter2 + -alias key + -keyalg RSA + -keysize 2048 + -validity 10000 && + cmake .. + -D CMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake + -D ANDROID_SDK=$ANDROID_HOME + -D ANDROID_ABI=arm64-v8a + -D ANDROID_NATIVE_API_LEVEL=29 + -D ANDROID_BUILD_TOOLS_VERSION=30.0.3 + -D ANDROID_KEYSTORE=key.keystore + -D ANDROID_KEYSTORE_PASS=pass:hunter2 + - name: Build + run: cmake --build build + - name: Upload + uses: actions/upload-artifact@v3 + with: + name: lovr.apk + path: build/lovr.apk + macos: + name: macOS + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true + - name: Init + run: cmake -B build -D LOVR_BUILD_BUNDLE=ON + - name: Build + run: cmake --build build + - name: Upload + uses: actions/upload-artifact@v3 + with: + name: lovr.app + path: build/lovr.app diff --git a/.gitmodules b/.gitmodules index 40b280c4..af35dac0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "deps/glslang"] path = deps/glslang url = https://github.com/bjornbytes/glslang +[submodule "deps/vulkan-headers"] + path = deps/vulkan-headers + url = https://github.com/KhronosGroup/Vulkan-Headers diff --git a/CMakeLists.txt b/CMakeLists.txt index 6439feb9..1a5400f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,8 +192,7 @@ endif() # Vulkan if(LOVR_USE_VULKAN) - find_package(Vulkan REQUIRED) - include_directories(${Vulkan_INCLUDE_DIRS}) + include_directories(deps/vulkan-headers/include) endif() # OpenXR @@ -440,7 +439,6 @@ endif() if(LOVR_ENABLE_FILESYSTEM) target_sources(lovr PRIVATE src/modules/filesystem/filesystem.c - src/modules/filesystem/file.c src/api/l_filesystem.c ) else() @@ -475,12 +473,9 @@ if(LOVR_ENABLE_GRAPHICS) if(LOVR_USE_WEBGPU) target_compile_definitions(lovr PRIVATE LOVR_WGPU) - target_sources(lovr PRIVATE src/core/gpu_webgpu.c) + target_sources(lovr PRIVATE src/core/gpu_wgpu.c) endif() - add_custom_target(compile_shaders ALL) - add_dependencies(lovr compile_shaders) - function(compile_shaders) if(LOVR_USE_GLSLANG AND ENABLE_GLSLANG_BINARIES AND NOT ANDROID) set(GLSLANG_VALIDATOR $) @@ -496,15 +491,18 @@ if(LOVR_ENABLE_GRAPHICS) foreach(shader_file ${shader_files}) string(REGEX MATCH "([^\/]+)\.${ARGV0}" shader ${shader_file}) string(REPLACE ".${ARGV0}" "" shader ${shader}) - add_custom_command(TARGET compile_shaders POST_BUILD + add_custom_command( + OUTPUT ${shader_file}.h + DEPENDS ${shader_file} COMMAND - ${GLSLANG_VALIDATOR} - --quiet - --target-env vulkan1.1 - --vn lovr_shader_${shader}_${ARGV0} - -o ${shader_file}.h - ${shader_file} + ${GLSLANG_VALIDATOR} + --quiet + --target-env vulkan1.1 + --vn lovr_shader_${shader}_${ARGV0} + -o ${shader_file}.h + ${shader_file} ) + target_sources(lovr PRIVATE ${shader_file}.h) endforeach() endfunction() diff --git a/README.md b/README.md index 106b5f74..2319844b 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ You can use LÖVR to easily create VR experiences without much setup or programming experience. The framework is tiny, fast, open source, and supports lots of different platforms and devices. -[![Build status](https://ci.appveyor.com/api/projects/status/alx3kdi35bmxka8c/branch/master?svg=true)](https://ci.appveyor.com/project/bjornbytes/lovr/branch/master) +[![Build](https://github.com/bjornbytes/lovr/actions/workflows/build.yml/badge.svg?event=push)](https://github.com/bjornbytes/lovr/actions/workflows/build.yml) [![Version](https://img.shields.io/github/release/bjornbytes/lovr.svg?label=version)](https://github.com/bjornbytes/lovr/releases) -[![Slack](https://img.shields.io/badge/chat-slack-7e4e76.svg)](https://lovr.org/slack) +[![Matrix](https://img.shields.io/badge/chat-matrix-7e4e76.svg)](https://lovr.org/matrix) [**Homepage**](https://lovr.org) | [**Documentation**](https://lovr.org/docs) | [**FAQ**](https://lovr.org/docs/FAQ) @@ -35,7 +35,7 @@ Getting Started --- It's really easy to get started making things with LÖVR. Grab a copy of the executable from , -then write a `main.lua` script and drag its parent folder onto the executable. Here are some example projects to try: +then write a `main.lua` script and drag it onto the executable. Here are some example projects to try: #### Hello World diff --git a/Tupfile.lua b/Tupfile.lua index de1a4523..3785899a 100644 --- a/Tupfile.lua +++ b/Tupfile.lua @@ -141,6 +141,8 @@ if target == 'linux' then end if target == 'wasm' then + cc = 'emcc' + cxx = 'em++' cflags += '-std=gnu11' cflags += '-D_POSIX_C_SOURCE=200809L' lflags += '-s FORCE_FILESYSTEM' @@ -412,9 +414,9 @@ end for renderer, enabled in pairs(config.renderers) do if enabled then - local code = renderer:gsub('vulkan', 'vk') - cflags += '-DLOVR_' .. code:upper() - src += 'src/core/gpu_' .. code .. '.c' + local shortname = ({ vulkan = 'vk', webgpu = 'wgpu' })[renderer] + cflags += '-DLOVR_' .. shortname:upper() + src += 'src/core/gpu_' .. shortname .. '.c' end end diff --git a/deps/vulkan-headers b/deps/vulkan-headers new file mode 160000 index 00000000..29c0457c --- /dev/null +++ b/deps/vulkan-headers @@ -0,0 +1 @@ +Subproject commit 29c0457cc167bfc9e9361a3818440e388986f5b5 diff --git a/etc/appveyor.yml b/etc/appveyor.yml deleted file mode 100644 index 503104a1..00000000 --- a/etc/appveyor.yml +++ /dev/null @@ -1,55 +0,0 @@ -skip_branch_with_pr: true - -clone_depth: 1 - -only_commits: - files: - - etc/ - - src/ - - deps/ - - CMakeLists.txt - -branches: - only: - - master - - dev - -environment: - VULKAN_SDK: C:/VulkanSDK - -cache: - - VulkanSDK.exe - -install: - - if not exist VulkanSDK.exe curl -L --silent --show-error --output VulkanSDK.exe https://sdk.lunarg.com/sdk/download/1.3.216.0/windows/VulkanSDK-1.3.216.0-Installer.exe?Human=true - - VulkanSDK.exe --root C:\VulkanSDK --accept-licenses --default-answer --confirm-command install - -image: Visual Studio 2022 - -before_build: - - cd C:\projects\lovr - - git submodule update --init - - md build - - cd build - - cmake -DCMAKE_BUILD_TYPE=%configuration% -A x64 .. - -configuration: Release - -build: - project: build\lovr.sln - verbosity: quiet - -after_build: - - cd C:\projects\lovr - - 7z a lovr.zip C:\projects\lovr\build\Release\*.dll C:\projects\lovr\build\Release\lovr.exe - -artifacts: - - path: lovr.zip - -deploy: - provider: Webhook - url: https://lovr.org/nightly - on: - branch: master - authorization: - secure: zbxwiHmtJRVP9FdiqxepLdLGjxhtpjVytb+yLSqrz+8Bl1bH6k5Zts809bLrEjzn diff --git a/etc/boot.lua b/etc/boot.lua index 026932c8..b46be332 100644 --- a/etc/boot.lua +++ b/etc/boot.lua @@ -90,7 +90,7 @@ function lovr.boot() lovr.graphics.initialize() end - if lovr.headset and lovr.graphics and conf.window then + if lovr.headset then lovr.headset.start() if lovr.headset.getDriver() == 'desktop' then diff --git a/src/api/api.c b/src/api/api.c index 180adf1b..815d2684 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -396,7 +396,7 @@ uint32_t _luax_checku32(lua_State* L, int index) { double x = lua_tonumber(L, index); if (x == 0. && !lua_isnumber(L, index)) { - luaL_typerror(L, index, "number"); + luax_typeerror(L, index, "number"); } if (x < 0. || x > UINT32_MAX) { diff --git a/src/api/api.h b/src/api/api.h index 95bb3f03..6ba7f505 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -2,12 +2,11 @@ #include #include #include +#include +#include #pragma once -struct lua_State; -struct luaL_Reg; - // Enums typedef struct { uint8_t length; @@ -107,43 +106,43 @@ typedef struct { #define luax_optfloat(L, i, x) (float) luaL_optnumber(L, i, x) #define luax_tofloat(L, i) (float) lua_tonumber(L, i) -void luax_preload(struct lua_State* L); -void _luax_registertype(struct lua_State* L, const char* name, const struct luaL_Reg* functions, void (*destructor)(void*)); -void* _luax_totype(struct lua_State* L, int index, uint64_t hash); -void* _luax_checktype(struct lua_State* L, int index, uint64_t hash, const char* debug); -int luax_typeerror(struct lua_State* L, int index, const char* expected); -void _luax_pushtype(struct lua_State* L, const char* name, uint64_t hash, void* object); -int _luax_checkenum(struct lua_State* L, int index, const StringEntry* map, const char* fallback, const char* label); -void luax_registerloader(struct lua_State* L, int (*loader)(struct lua_State* L), int index); -int luax_resume(struct lua_State* T, int n); +void luax_preload(lua_State* L); +void _luax_registertype(lua_State* L, const char* name, const luaL_Reg* functions, void (*destructor)(void*)); +void* _luax_totype(lua_State* L, int index, uint64_t hash); +void* _luax_checktype(lua_State* L, int index, uint64_t hash, const char* debug); +int luax_typeerror(lua_State* L, int index, const char* expected); +void _luax_pushtype(lua_State* L, const char* name, uint64_t hash, void* object); +int _luax_checkenum(lua_State* L, int index, const StringEntry* map, const char* fallback, const char* label); +void luax_registerloader(lua_State* L, int (*loader)(lua_State* L), int index); +int luax_resume(lua_State* T, int n); void luax_vthrow(void* L, const char* format, va_list args); void luax_vlog(void* context, int level, const char* tag, const char* format, va_list args); -void luax_traceback(struct lua_State* L, struct lua_State* T, const char* message, int level); -int luax_getstack(struct lua_State* L); -void luax_pushconf(struct lua_State* L); -int luax_setconf(struct lua_State* L); -void luax_setmainthread(struct lua_State* L); -void luax_atexit(struct lua_State* L, void (*finalizer)(void)); -uint32_t _luax_checku32(struct lua_State* L, int index); -uint32_t _luax_optu32(struct lua_State* L, int index, uint32_t fallback); -void luax_readcolor(struct lua_State* L, int index, float color[4]); -void luax_optcolor(struct lua_State* L, int index, float color[4]); -int luax_readmesh(struct lua_State* L, int index, float** vertices, uint32_t* vertexCount, uint32_t** indices, uint32_t* indexCount, bool* shouldFree); +void luax_traceback(lua_State* L, lua_State* T, const char* message, int level); +int luax_getstack(lua_State* L); +void luax_pushconf(lua_State* L); +int luax_setconf(lua_State* L); +void luax_setmainthread(lua_State* L); +void luax_atexit(lua_State* L, void (*finalizer)(void)); +uint32_t _luax_checku32(lua_State* L, int index); +uint32_t _luax_optu32(lua_State* L, int index, uint32_t fallback); +void luax_readcolor(lua_State* L, int index, float color[4]); +void luax_optcolor(lua_State* L, int index, float color[4]); +int luax_readmesh(lua_State* L, int index, float** vertices, uint32_t* vertexCount, uint32_t** indices, uint32_t* indexCount, bool* shouldFree); // Module helpers #ifndef LOVR_DISABLE_DATA struct Blob; struct Image; -struct Blob* luax_readblob(struct lua_State* L, int index, const char* debug); -struct Image* luax_checkimage(struct lua_State* L, int index); -uint32_t luax_checkcodepoint(struct lua_State* L, int index); +struct Blob* luax_readblob(lua_State* L, int index, const char* debug); +struct Image* luax_checkimage(lua_State* L, int index); +uint32_t luax_checkcodepoint(lua_State* L, int index); #endif #ifndef LOVR_DISABLE_EVENT struct Variant; -void luax_checkvariant(struct lua_State* L, int index, struct Variant* variant); -int luax_pushvariant(struct lua_State* L, struct Variant* variant); +void luax_checkvariant(lua_State* L, int index, struct Variant* variant); +int luax_pushvariant(lua_State* L, struct Variant* variant); #endif #ifndef LOVR_DISABLE_FILESYSTEM @@ -155,37 +154,37 @@ bool luax_writefile(const char* filename, const void* data, size_t size); struct Buffer; struct ColoredString; struct Model; -struct Buffer* luax_checkbuffer(struct lua_State* L, int index); -void luax_readbufferfield(struct lua_State* L, int index, int type, void* data); -void luax_readbufferdata(struct lua_State* L, int index, struct Buffer* buffer, char* data); -uint32_t luax_checkcomparemode(struct lua_State* L, int index); -struct ColoredString* luax_checkcoloredstrings(struct lua_State* L, int index, uint32_t* count, struct ColoredString* stack); -uint32_t luax_checknodeindex(struct lua_State* L, int index, struct Model* model); +struct Buffer* luax_checkbuffer(lua_State* L, int index); +void luax_readbufferfield(lua_State* L, int index, int type, void* data); +void luax_readbufferdata(lua_State* L, int index, struct Buffer* buffer, char* data); +uint32_t luax_checkcomparemode(lua_State* L, int index); +struct ColoredString* luax_checkcoloredstrings(lua_State* L, int index, uint32_t* count, struct ColoredString* stack); +uint32_t luax_checknodeindex(lua_State* L, int index, struct Model* model); #endif #ifndef LOVR_DISABLE_MATH #include "math/pool.h" // TODO -float* luax_tovector(struct lua_State* L, int index, VectorType* type); -float* luax_checkvector(struct lua_State* L, int index, VectorType type, const char* expected); -float* luax_newtempvector(struct lua_State* L, VectorType type); -int luax_readvec3(struct lua_State* L, int index, float* v, const char* expected); -int luax_readscale(struct lua_State* L, int index, float* v, int components, const char* expected); -int luax_readquat(struct lua_State* L, int index, float* q, const char* expected); -int luax_readmat4(struct lua_State* L, int index, float* m, int scaleComponents); -uint64_t luax_checkrandomseed(struct lua_State* L, int index); +float* luax_tovector(lua_State* L, int index, VectorType* type); +float* luax_checkvector(lua_State* L, int index, VectorType type, const char* expected); +float* luax_newtempvector(lua_State* L, VectorType type); +int luax_readvec3(lua_State* L, int index, float* v, const char* expected); +int luax_readscale(lua_State* L, int index, float* v, int components, const char* expected); +int luax_readquat(lua_State* L, int index, float* q, const char* expected); +int luax_readmat4(lua_State* L, int index, float* m, int scaleComponents); +uint64_t luax_checkrandomseed(lua_State* L, int index); #endif #ifndef LOVR_DISABLE_PHYSICS struct Joint; struct Shape; -void luax_pushjoint(struct lua_State* L, struct Joint* joint); -void luax_pushshape(struct lua_State* L, struct Shape* shape); -struct Joint* luax_checkjoint(struct lua_State* L, int index); -struct Shape* luax_checkshape(struct lua_State* L, int index); -struct Shape* luax_newsphereshape(struct lua_State* L, int index); -struct Shape* luax_newboxshape(struct lua_State* L, int index); -struct Shape* luax_newcapsuleshape(struct lua_State* L, int index); -struct Shape* luax_newcylindershape(struct lua_State* L, int index); -struct Shape* luax_newmeshshape(struct lua_State* L, int index); -struct Shape* luax_newterrainshape(struct lua_State* L, int index); +void luax_pushjoint(lua_State* L, struct Joint* joint); +void luax_pushshape(lua_State* L, struct Shape* shape); +struct Joint* luax_checkjoint(lua_State* L, int index); +struct Shape* luax_checkshape(lua_State* L, int index); +struct Shape* luax_newsphereshape(lua_State* L, int index); +struct Shape* luax_newboxshape(lua_State* L, int index); +struct Shape* luax_newcapsuleshape(lua_State* L, int index); +struct Shape* luax_newcylindershape(lua_State* L, int index); +struct Shape* luax_newmeshshape(lua_State* L, int index); +struct Shape* luax_newterrainshape(lua_State* L, int index); #endif diff --git a/src/api/l_audio.c b/src/api/l_audio.c index fc5afe08..3beac8a2 100644 --- a/src/api/l_audio.c +++ b/src/api/l_audio.c @@ -4,8 +4,6 @@ #include "data/sound.h" #include "core/maf.h" #include "util.h" -#include -#include #include StringEntry lovrEffect[] = { diff --git a/src/api/l_audio_source.c b/src/api/l_audio_source.c index 6a21df85..e5461650 100644 --- a/src/api/l_audio_source.c +++ b/src/api/l_audio_source.c @@ -2,8 +2,6 @@ #include "audio/audio.h" #include "core/maf.h" #include "util.h" -#include -#include static int l_lovrSourceClone(lua_State* L) { Source* source = luax_checktype(L, 1, Source); diff --git a/src/api/l_data.c b/src/api/l_data.c index e500b9ad..491a9727 100644 --- a/src/api/l_data.c +++ b/src/api/l_data.c @@ -5,8 +5,6 @@ #include "data/sound.h" #include "data/image.h" #include "util.h" -#include -#include #include #include diff --git a/src/api/l_data_blob.c b/src/api/l_data_blob.c index c12f3332..13c6e395 100644 --- a/src/api/l_data_blob.c +++ b/src/api/l_data_blob.c @@ -1,8 +1,6 @@ #include "api.h" #include "data/blob.h" #include "util.h" -#include -#include static int l_lovrBlobGetName(lua_State* L) { Blob* blob = luax_checktype(L, 1, Blob); @@ -24,7 +22,14 @@ static int l_lovrBlobGetSize(lua_State* L) { static int l_lovrBlobGetString(lua_State* L) { Blob* blob = luax_checktype(L, 1, Blob); - lua_pushlstring(L, blob->data, blob->size); + + uint32_t offset = luax_optu32(L, 2, 0); + lovrCheck(offset < blob->size, "Blob byte offset must be less than the size of the Blob"); + + uint32_t length = luax_optu32(L, 3, blob->size - offset); + lovrCheck(length <= blob->size - offset, "Blob:getString range overflows the length of the Blob"); + + lua_pushlstring(L, (char*) blob->data + offset, length); return 1; } diff --git a/src/api/l_data_image.c b/src/api/l_data_image.c index 07657498..c4d22bcb 100644 --- a/src/api/l_data_image.c +++ b/src/api/l_data_image.c @@ -2,8 +2,6 @@ #include "data/image.h" #include "data/blob.h" #include "util.h" -#include -#include StringEntry lovrTextureFormat[] = { [FORMAT_R8] = ENTRY("r8"), diff --git a/src/api/l_data_modelData.c b/src/api/l_data_modelData.c index 868efd6b..bca3fa4e 100644 --- a/src/api/l_data_modelData.c +++ b/src/api/l_data_modelData.c @@ -2,8 +2,6 @@ #include "data/modelData.h" #include "core/maf.h" #include "util.h" -#include -#include static ModelNode* luax_checknode(lua_State* L, int index, ModelData* model) { switch (lua_type(L, index)) { diff --git a/src/api/l_data_rasterizer.c b/src/api/l_data_rasterizer.c index e5f71280..7a129f58 100644 --- a/src/api/l_data_rasterizer.c +++ b/src/api/l_data_rasterizer.c @@ -2,8 +2,6 @@ #include "data/rasterizer.h" #include "data/image.h" #include "util.h" -#include -#include #include uint32_t luax_checkcodepoint(lua_State* L, int index) { diff --git a/src/api/l_data_sound.c b/src/api/l_data_sound.c index b447ea56..821bc3ff 100644 --- a/src/api/l_data_sound.c +++ b/src/api/l_data_sound.c @@ -2,8 +2,6 @@ #include "data/sound.h" #include "data/blob.h" #include "util.h" -#include -#include StringEntry lovrSampleFormat[] = { [SAMPLE_F32] = ENTRY("f32"), diff --git a/src/api/l_event.c b/src/api/l_event.c index 80145e68..2df5ac4d 100644 --- a/src/api/l_event.c +++ b/src/api/l_event.c @@ -2,8 +2,6 @@ #include "event/event.h" #include "thread/thread.h" #include "util.h" -#include -#include #include #include @@ -42,16 +40,23 @@ void luax_checkvariant(lua_State* L, int index, Variant* variant) { variant->value.number = lua_tonumber(L, index); break; - case LUA_TSTRING: - variant->type = TYPE_STRING; + case LUA_TSTRING: { size_t length; const char* string = lua_tolstring(L, index, &length); - variant->value.string.pointer = malloc(length + 1); - lovrAssert(variant->value.string.pointer, "Out of memory"); - memcpy(variant->value.string.pointer, string, length); - variant->value.string.pointer[length] = '\0'; - variant->value.string.length = length; + if (length <= sizeof(variant->value.ministring.data)) { + variant->type = TYPE_MINISTRING; + variant->value.ministring.length = length; + memcpy(variant->value.ministring.data, string, length); + } else { + variant->type = TYPE_STRING; + variant->value.string.pointer = malloc(length + 1); + lovrAssert(variant->value.string.pointer, "Out of memory"); + memcpy(variant->value.string.pointer, string, length); + variant->value.string.pointer[length] = '\0'; + variant->value.string.length = length; + } break; + } case LUA_TUSERDATA: variant->type = TYPE_OBJECT; @@ -82,6 +87,7 @@ int luax_pushvariant(lua_State* L, Variant* variant) { case TYPE_BOOLEAN: lua_pushboolean(L, variant->value.boolean); return 1; case TYPE_NUMBER: lua_pushnumber(L, variant->value.number); return 1; case TYPE_STRING: lua_pushlstring(L, variant->value.string.pointer, variant->value.string.length); return 1; + case TYPE_MINISTRING: lua_pushlstring(L, variant->value.ministring.data, variant->value.ministring.length); return 1; case TYPE_OBJECT: _luax_pushtype(L, variant->value.object.type, hash64(variant->value.object.type, strlen(variant->value.object.type)), variant->value.object.pointer); return 1; default: return 0; } @@ -185,7 +191,7 @@ static int l_lovrEventPush(lua_State* L) { } static int l_lovrEventQuit(lua_State* L) { - int exitCode = luaL_optint(L, 1, 0); + int exitCode = luaL_optinteger(L, 1, 0); Event event = { .type = EVENT_QUIT, .data.quit.exitCode = exitCode }; lovrEventPush(event); return 0; diff --git a/src/api/l_filesystem.c b/src/api/l_filesystem.c index 0e9c3dbe..8d46a8ea 100644 --- a/src/api/l_filesystem.c +++ b/src/api/l_filesystem.c @@ -2,8 +2,6 @@ #include "filesystem/filesystem.h" #include "data/blob.h" #include "util.h" -#include -#include #include #include diff --git a/src/api/l_graphics.c b/src/api/l_graphics.c index 5f1a57cd..1a854d89 100644 --- a/src/api/l_graphics.c +++ b/src/api/l_graphics.c @@ -5,8 +5,6 @@ #include "data/modelData.h" #include "data/rasterizer.h" #include "util.h" -#include -#include #include #include @@ -1013,7 +1011,8 @@ static int l_lovrGraphicsNewTexture(lua_State* L) { lua_getfield(L, index, "usage"); switch (lua_type(L, -1)) { case LUA_TSTRING: info.usage = 1 << luax_checkenum(L, -1, TextureUsage, NULL); break; - case LUA_TTABLE: { + case LUA_TTABLE: + info.usage = 0; int length = luax_len(L, -1); for (int i = 0; i < length; i++) { lua_rawgeti(L, -1, i + 1); @@ -1021,7 +1020,6 @@ static int l_lovrGraphicsNewTexture(lua_State* L) { lua_pop(L, 1); } break; - } case LUA_TNIL: break; default: return luaL_error(L, "Expected Texture usage to be a string, table, or nil"); } @@ -1157,6 +1155,9 @@ static ShaderSource luax_checkshadersource(lua_State* L, int index, ShaderStage source.code = blob->data; source.size = blob->size; *allocated = false; + } else { + *allocated = false; + return lovrGraphicsGetDefaultShaderSource(SHADER_UNLIT, stage); } ShaderSource bytecode = lovrGraphicsCompileShader(stage, &source); diff --git a/src/api/l_graphics_buffer.c b/src/api/l_graphics_buffer.c index 02d09466..2d24eeff 100644 --- a/src/api/l_graphics_buffer.c +++ b/src/api/l_graphics_buffer.c @@ -2,8 +2,6 @@ #include "graphics/graphics.h" #include "data/blob.h" #include "util.h" -#include -#include #include #include diff --git a/src/api/l_graphics_font.c b/src/api/l_graphics_font.c index 119eedfa..fa527006 100644 --- a/src/api/l_graphics_font.c +++ b/src/api/l_graphics_font.c @@ -2,8 +2,6 @@ #include "graphics/graphics.h" #include "data/rasterizer.h" #include "util.h" -#include -#include #include ColoredString* luax_checkcoloredstrings(lua_State* L, int index, uint32_t* count, ColoredString* stack) { diff --git a/src/api/l_graphics_material.c b/src/api/l_graphics_material.c index dafd9ed0..d97990f0 100644 --- a/src/api/l_graphics_material.c +++ b/src/api/l_graphics_material.c @@ -1,8 +1,6 @@ #include "api.h" #include "graphics/graphics.h" #include "util.h" -#include -#include static int l_lovrMaterialGetProperties(lua_State* L) { Material* material = luax_checktype(L, 1, Material); diff --git a/src/api/l_graphics_model.c b/src/api/l_graphics_model.c index b00a97c0..545e097f 100644 --- a/src/api/l_graphics_model.c +++ b/src/api/l_graphics_model.c @@ -3,8 +3,6 @@ #include "data/modelData.h" #include "core/maf.h" #include "util.h" -#include -#include // This adds about 2-3us of overhead, which sucks, but the reduction in complexity is large static int luax_callmodeldata(lua_State* L, const char* method, int nrets) { diff --git a/src/api/l_graphics_pass.c b/src/api/l_graphics_pass.c index 16232107..332c4aec 100644 --- a/src/api/l_graphics_pass.c +++ b/src/api/l_graphics_pass.c @@ -4,8 +4,6 @@ #include "data/image.h" #include "core/maf.h" #include "util.h" -#include -#include #include #include @@ -190,7 +188,7 @@ static int l_lovrPassSetProjection(lua_State* L) { float up = luax_checkfloat(L, 5); float down = luax_checkfloat(L, 6); float clipNear = luax_optfloat(L, 7, .01f); - float clipFar = luax_optfloat(L, 8, 100.f); + float clipFar = luax_optfloat(L, 8, 0.f); float matrix[16]; mat4_fov(matrix, left, right, up, down, clipNear, clipFar); lovrPassSetProjection(pass, view, matrix); diff --git a/src/api/l_graphics_readback.c b/src/api/l_graphics_readback.c index 761fa81f..a83d8578 100644 --- a/src/api/l_graphics_readback.c +++ b/src/api/l_graphics_readback.c @@ -3,8 +3,6 @@ #include "data/blob.h" #include "data/image.h" #include "util.h" -#include -#include static int l_lovrReadbackIsComplete(lua_State* L) { Readback* readback = luax_checktype(L, 1, Readback); diff --git a/src/api/l_graphics_sampler.c b/src/api/l_graphics_sampler.c index 8a27f443..6fb11087 100644 --- a/src/api/l_graphics_sampler.c +++ b/src/api/l_graphics_sampler.c @@ -1,8 +1,6 @@ #include "api.h" #include "graphics/graphics.h" #include "util.h" -#include -#include static int l_lovrSamplerGetFilter(lua_State* L) { Sampler* sampler = luax_checktype(L, 1, Sampler); diff --git a/src/api/l_graphics_shader.c b/src/api/l_graphics_shader.c index a36ccd91..d67555d2 100644 --- a/src/api/l_graphics_shader.c +++ b/src/api/l_graphics_shader.c @@ -1,8 +1,6 @@ #include "api.h" #include "graphics/graphics.h" #include "util.h" -#include -#include #include static int l_lovrShaderClone(lua_State* L) { diff --git a/src/api/l_graphics_tally.c b/src/api/l_graphics_tally.c index 178d4c1a..bc16b6ee 100644 --- a/src/api/l_graphics_tally.c +++ b/src/api/l_graphics_tally.c @@ -1,8 +1,6 @@ #include "api.h" #include "graphics/graphics.h" #include "util.h" -#include -#include static int l_lovrTallyGetType(lua_State* L) { Tally* tally = luax_checktype(L, 1, Tally); diff --git a/src/api/l_graphics_texture.c b/src/api/l_graphics_texture.c index 758d7870..39e77541 100644 --- a/src/api/l_graphics_texture.c +++ b/src/api/l_graphics_texture.c @@ -1,8 +1,6 @@ #include "api.h" #include "util.h" #include "graphics/graphics.h" -#include -#include static int l_lovrTextureNewView(lua_State* L) { Texture* texture = luax_checktype(L, 1, Texture); @@ -116,6 +114,6 @@ const luaL_Reg lovrTexture[] = { { "getDimensions", l_lovrTextureGetDimensions }, { "getMipmapCount", l_lovrTextureGetMipmapCount }, { "getSampleCount", l_lovrTextureGetSampleCount }, - { "hasUsage ", l_lovrTextureHasUsage }, + { "hasUsage", l_lovrTextureHasUsage }, { NULL, NULL } }; diff --git a/src/api/l_headset.c b/src/api/l_headset.c index ec5e5a68..9dc50b08 100644 --- a/src/api/l_headset.c +++ b/src/api/l_headset.c @@ -3,8 +3,6 @@ #include "data/modelData.h" #include "graphics/graphics.h" #include "core/maf.h" -#include -#include #include StringEntry lovrHeadsetDriver[] = { @@ -650,8 +648,6 @@ int luaopen_lovr_headset(lua_State* L) { if (lua_istable(L, -1)) { lua_getfield(L, -1, "headset"); if (lua_istable(L, -1)) { - - // Drivers lua_getfield(L, -1, "drivers"); int n = luax_len(L, -1); for (int i = 0; i < n; i++) { @@ -662,7 +658,6 @@ int luaopen_lovr_headset(lua_State* L) { } lua_pop(L, 1); - // Supersample lua_getfield(L, -1, "supersample"); if (lua_type(L, -1) == LUA_TBOOLEAN) { config.supersample = lua_toboolean(L, -1) ? 2.f : 1.f; @@ -671,27 +666,22 @@ int luaopen_lovr_headset(lua_State* L) { } lua_pop(L, 1); - // Offset lua_getfield(L, -1, "offset"); config.offset = luax_optfloat(L, -1, 1.7f); lua_pop(L, 1); - // Stencil lua_getfield(L, -1, "stencil"); config.stencil = lua_toboolean(L, -1); lua_pop(L, 1); - // Samples lua_getfield(L, -1, "antialias"); config.antialias = lua_isnil(L, -1) ? true : lua_toboolean(L, -1); lua_pop(L, 1); - // Depth lua_getfield(L, -1, "submitdepth"); config.submitDepth = lua_toboolean(L, -1); lua_pop(L, 1); - // Overlay lua_getfield(L, -1, "overlay"); config.overlay = lua_toboolean(L, -1); lua_pop(L, 1); diff --git a/src/api/l_lovr.c b/src/api/l_lovr.c index 9966f404..f56bec80 100644 --- a/src/api/l_lovr.c +++ b/src/api/l_lovr.c @@ -1,7 +1,5 @@ #include "api.h" #include "util.h" -#include -#include static int l_lovrGetVersion(lua_State* L) { lua_pushinteger(L, LOVR_VERSION_MAJOR); diff --git a/src/api/l_math.c b/src/api/l_math.c index 7b32b22f..c875d3e7 100644 --- a/src/api/l_math.c +++ b/src/api/l_math.c @@ -4,8 +4,6 @@ #include "math/pool.h" #include "math/randomGenerator.h" #include "util.h" -#include -#include #include int l_lovrRandomGeneratorRandom(lua_State* L); diff --git a/src/api/l_math_curve.c b/src/api/l_math_curve.c index 89b26360..25e1d3c9 100644 --- a/src/api/l_math_curve.c +++ b/src/api/l_math_curve.c @@ -1,8 +1,6 @@ #include "api.h" #include "math/curve.h" #include "util.h" -#include -#include static int l_lovrCurveEvaluate(lua_State* L) { Curve* curve = luax_checktype(L, 1, Curve); diff --git a/src/api/l_math_randomGenerator.c b/src/api/l_math_randomGenerator.c index 9903bebc..e10564e9 100644 --- a/src/api/l_math_randomGenerator.c +++ b/src/api/l_math_randomGenerator.c @@ -1,8 +1,6 @@ #include "api.h" #include "math/randomGenerator.h" #include "util.h" -#include -#include #include static double luax_checkrandomseedpart(lua_State* L, int index) { diff --git a/src/api/l_math_vectors.c b/src/api/l_math_vectors.c index afd7bdd2..2360a06e 100644 --- a/src/api/l_math_vectors.c +++ b/src/api/l_math_vectors.c @@ -1,8 +1,6 @@ #include "api.h" #include "core/maf.h" #include "util.h" -#include -#include #define EQ_THRESHOLD 1e-10f @@ -1942,6 +1940,15 @@ static int l_lovrMat4Target(lua_State* L) { return 1; } +static int l_lovrMat4Reflect(lua_State* L) { + mat4 m = luax_checkvector(L, 1, V_MAT4, NULL); + vec3 position = luax_checkvector(L, 2, V_VEC3, NULL); + vec3 normal = luax_checkvector(L, 3, V_VEC3, NULL); + mat4_reflect(m, position, normal); + lua_settop(L, 1); + return 1; +} + static int l_lovrMat4__mul(lua_State* L) { mat4 m = luax_checkvector(L, 1, V_MAT4, NULL); VectorType type; @@ -2027,6 +2034,7 @@ const luaL_Reg lovrMat4[] = { { "fov", l_lovrMat4Fov }, { "lookAt", l_lovrMat4LookAt }, { "target", l_lovrMat4Target }, + { "reflect", l_lovrMat4Reflect }, { "__mul", l_lovrMat4__mul }, { "__tostring", l_lovrMat4__tostring }, { "__newindex", l_lovrMat4__newindex }, diff --git a/src/api/l_physics.c b/src/api/l_physics.c index 4ae7fe98..84cf8521 100644 --- a/src/api/l_physics.c +++ b/src/api/l_physics.c @@ -1,8 +1,6 @@ #include "api.h" #include "physics/physics.h" #include "util.h" -#include -#include StringEntry lovrShapeType[] = { [SHAPE_SPHERE] = ENTRY("sphere"), diff --git a/src/api/l_physics_collider.c b/src/api/l_physics_collider.c index d2a800d5..e6cbf808 100644 --- a/src/api/l_physics_collider.c +++ b/src/api/l_physics_collider.c @@ -2,8 +2,6 @@ #include "physics/physics.h" #include "core/maf.h" #include "util.h" -#include -#include #include static int l_lovrColliderDestroy(lua_State* L) { diff --git a/src/api/l_physics_joints.c b/src/api/l_physics_joints.c index 991dd5ac..2631e654 100644 --- a/src/api/l_physics_joints.c +++ b/src/api/l_physics_joints.c @@ -1,8 +1,6 @@ #include "api.h" #include "physics/physics.h" #include "util.h" -#include -#include #include void luax_pushjoint(lua_State* L, Joint* joint) { diff --git a/src/api/l_physics_shapes.c b/src/api/l_physics_shapes.c index a1cf3cf7..d69925aa 100644 --- a/src/api/l_physics_shapes.c +++ b/src/api/l_physics_shapes.c @@ -3,8 +3,6 @@ #include "data/image.h" #include "core/maf.h" #include "util.h" -#include -#include #include #include diff --git a/src/api/l_physics_world.c b/src/api/l_physics_world.c index 01737fbb..e756a954 100644 --- a/src/api/l_physics_world.c +++ b/src/api/l_physics_world.c @@ -1,8 +1,6 @@ #include "api.h" #include "physics/physics.h" #include "util.h" -#include -#include #include #include diff --git a/src/api/l_system.c b/src/api/l_system.c index 58f1fbe8..425cb17b 100644 --- a/src/api/l_system.c +++ b/src/api/l_system.c @@ -3,8 +3,6 @@ #include "data/image.h" #include "core/os.h" #include "util.h" -#include -#include #include StringEntry lovrKeyboardKey[] = { diff --git a/src/api/l_thread.c b/src/api/l_thread.c index 24549d33..e9714b36 100644 --- a/src/api/l_thread.c +++ b/src/api/l_thread.c @@ -3,8 +3,6 @@ #include "thread/thread.h" #include "thread/channel.h" #include "util.h" -#include -#include #include #include #include diff --git a/src/api/l_thread_channel.c b/src/api/l_thread_channel.c index 9bdd4667..19b1336f 100644 --- a/src/api/l_thread_channel.c +++ b/src/api/l_thread_channel.c @@ -2,8 +2,6 @@ #include "thread/channel.h" #include "event/event.h" #include "util.h" -#include -#include #include static void luax_checktimeout(lua_State* L, int index, double* timeout) { diff --git a/src/api/l_thread_thread.c b/src/api/l_thread_thread.c index 0d8576ee..109e9804 100644 --- a/src/api/l_thread_thread.c +++ b/src/api/l_thread_thread.c @@ -1,8 +1,6 @@ #include "api.h" #include "thread/thread.h" #include "util.h" -#include -#include static int l_lovrThreadStart(lua_State* L) { Thread* thread = luax_checktype(L, 1, Thread); diff --git a/src/api/l_timer.c b/src/api/l_timer.c index 48dfb170..81529a51 100644 --- a/src/api/l_timer.c +++ b/src/api/l_timer.c @@ -1,7 +1,5 @@ #include "api.h" #include "timer/timer.h" -#include -#include static int l_lovrTimerGetDelta(lua_State* L) { lua_pushnumber(L, lovrTimerGetDelta()); diff --git a/src/core/gpu.h b/src/core/gpu.h index 8467eccc..aa47c0c3 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -197,7 +197,6 @@ typedef enum { } gpu_slot_type; enum { - GPU_STAGE_ALL = 0, GPU_STAGE_VERTEX = (1 << 0), GPU_STAGE_FRAGMENT = (1 << 1), GPU_STAGE_COMPUTE = (1 << 2), diff --git a/src/core/gpu_vk.c b/src/core/gpu_vk.c index 8ed59c38..19e80652 100644 --- a/src/core/gpu_vk.c +++ b/src/core/gpu_vk.c @@ -210,6 +210,8 @@ static gpu_memory* gpu_allocate(gpu_memory_type type, VkMemoryRequirements info, static void gpu_release(gpu_memory* memory); static void condemn(void* handle, VkObjectType type); static void expunge(void); +static bool hasLayer(VkLayerProperties* layers, uint32_t count, const char* layer); +static bool hasExtension(VkExtensionProperties* extensions, uint32_t count, const char* extension); static void createSwapchain(uint32_t width, uint32_t height); static VkRenderPass getCachedRenderPass(gpu_pass_info* pass, bool exact); static VkFramebuffer getCachedFramebuffer(VkRenderPass pass, VkImageView images[9], uint32_t imageCount, uint32_t size[2]); @@ -226,6 +228,7 @@ static bool check(bool condition, const char* message); // Functions that don't require an instance #define GPU_FOREACH_ANONYMOUS(X)\ + X(vkEnumerateInstanceLayerProperties)\ X(vkEnumerateInstanceExtensionProperties)\ X(vkCreateInstance) @@ -900,7 +903,7 @@ bool gpu_layout_init(gpu_layout* layout, gpu_layout_info* info) { .binding = info->slots[i].number, .descriptorType = types[info->slots[i].type], .descriptorCount = 1, - .stageFlags = info->slots[i].stages == GPU_STAGE_ALL ? VK_SHADER_STAGE_ALL : + .stageFlags = (((info->slots[i].stages & GPU_STAGE_VERTEX) ? VK_SHADER_STAGE_VERTEX_BIT : 0) | ((info->slots[i].stages & GPU_STAGE_FRAGMENT) ? VK_SHADER_STAGE_FRAGMENT_BIT : 0) | ((info->slots[i].stages & GPU_STAGE_COMPUTE) ? VK_SHADER_STAGE_COMPUTE_BIT : 0)) @@ -1840,51 +1843,88 @@ bool gpu_init(gpu_config* config) { GPU_FOREACH_ANONYMOUS(GPU_LOAD_ANONYMOUS); { // Instance - const char* extensions[32]; - uint32_t extensionCount = 0; + struct { + bool validation; + bool portability; + bool debug; + } supports = { 0 }; - if (state.config.vk.getInstanceExtensions) { - const char** instanceExtensions = state.config.vk.getInstanceExtensions(&extensionCount); - CHECK(extensionCount < COUNTOF(extensions), "Too many instance extensions") return gpu_destroy(), false; - for (uint32_t i = 0; i < extensionCount; i++) { - extensions[i] = instanceExtensions[i]; + // Layers + + struct { const char* name; bool shouldEnable; bool* isEnabled; } layers[] = { + { "VK_LAYER_KHRONOS_validation", config->debug, &supports.validation } + }; + + const char* enabledLayers[1]; + uint32_t enabledLayerCount = 0; + + VkLayerProperties layerInfo[32]; + uint32_t count = COUNTOF(layerInfo); + VK(vkEnumerateInstanceLayerProperties(&count, layerInfo), "Failed to enumerate instance layers") return gpu_destroy(), false; + + for (uint32_t i = 0; i < COUNTOF(layers); i++) { + if (!layers[i].shouldEnable) continue; + if (hasLayer(layerInfo, count, layers[i].name)) { + CHECK(enabledLayerCount < COUNTOF(enabledLayers), "Too many layers") return gpu_destroy(), false; + if (layers[i].isEnabled) *layers[i].isEnabled = true; + enabledLayers[enabledLayerCount++] = layers[i].name; } } - if (state.config.debug) { - CHECK(extensionCount < COUNTOF(extensions), "Too many instance extensions") return gpu_destroy(), false; - extensions[extensionCount++] = "VK_EXT_debug_utils"; + // Extensions + + struct { const char* name; bool shouldEnable; bool* isEnabled; } extensions[] = { + { "VK_KHR_portability_enumeration", true, &supports.portability }, + { "VK_EXT_debug_utils", config->debug, &supports.debug } + }; + + const char* enabledExtensions[32]; + uint32_t enabledExtensionCount = 0; + + if (state.config.vk.getInstanceExtensions) { + const char** instanceExtensions = state.config.vk.getInstanceExtensions(&enabledExtensionCount); + CHECK(enabledExtensionCount < COUNTOF(enabledExtensions), "Too many instance extensions") return gpu_destroy(), false; + for (uint32_t i = 0; i < enabledExtensionCount; i++) { + enabledExtensions[i] = instanceExtensions[i]; + } } VkExtensionProperties extensionInfo[256]; - uint32_t count = COUNTOF(extensionInfo); + count = COUNTOF(extensionInfo); VK(vkEnumerateInstanceExtensionProperties(NULL, &count, extensionInfo), "Failed to enumerate instance extensions") return gpu_destroy(), false; - VkInstanceCreateFlags instanceFlags = 0; - -#ifdef VK_KHR_portability_enumeration - for (uint32_t i = 0; i < count; i++) { - if (!strcmp(extensionInfo[i].extensionName, "VK_KHR_portability_enumeration")) { - CHECK(extensionCount < COUNTOF(extensions), "Too many instance extensions") return gpu_destroy(), false; - extensions[extensionCount++] = "VK_KHR_portability_enumeration"; - instanceFlags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + for (uint32_t i = 0; i < COUNTOF(extensions); i++) { + if (!extensions[i].shouldEnable) continue; + if (hasExtension(extensionInfo, count, extensions[i].name)) { + CHECK(enabledExtensionCount < COUNTOF(enabledExtensions), "Too many instance extensions") return gpu_destroy(), false; + if (extensions[i].isEnabled) *extensions[i].isEnabled = true; + enabledExtensions[enabledExtensionCount++] = extensions[i].name; } } -#endif + + if (state.config.debug && !supports.validation && state.config.callback) { + state.config.callback(state.config.userdata, "Warning: GPU debug mode is enabled, but validation layer is not supported", false); + } + + if (state.config.debug && !supports.debug) { + state.config.debug = false; + } VkInstanceCreateInfo instanceInfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .flags = instanceFlags, +#ifdef VK_KHR_portability_enumeration + .flags = supports.portability ? VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR : 0, +#endif .pApplicationInfo = &(VkApplicationInfo) { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pEngineName = config->engineName, .engineVersion = VK_MAKE_VERSION(config->engineVersion[0], config->engineVersion[1], config->engineVersion[2]), .apiVersion = VK_MAKE_VERSION(1, 1, 0) }, - .enabledLayerCount = state.config.debug ? 1 : 0, - .ppEnabledLayerNames = (const char*[]) { "VK_LAYER_KHRONOS_validation" }, - .enabledExtensionCount = extensionCount, - .ppEnabledExtensionNames = extensions + .enabledLayerCount = enabledLayerCount, + .ppEnabledLayerNames = enabledLayers, + .enabledExtensionCount = enabledExtensionCount, + .ppEnabledExtensionNames = enabledExtensions }; if (state.config.vk.createInstance) { @@ -2056,23 +2096,33 @@ bool gpu_init(gpu_config* config) { } CHECK(state.queueFamilyIndex != ~0u, "Queue selection failed") return gpu_destroy(), false; - const char* extensions[4]; - uint32_t extensionCount = 0; + struct { + bool swapchain; + } supports = { 0 }; - if (state.surface) { - extensions[extensionCount++] = "VK_KHR_swapchain"; - } + struct { const char* name; bool shouldEnable; bool* isEnabled; } extensions[] = { + { "VK_KHR_swapchain", state.surface, &supports.swapchain }, + { "VK_KHR_portability_subset", true, NULL } + }; + + const char* enabledExtensions[4]; + uint32_t enabledExtensionCount = 0; VkExtensionProperties extensionInfo[256]; uint32_t count = COUNTOF(extensionInfo); VK(vkEnumerateDeviceExtensionProperties(state.adapter, NULL, &count, extensionInfo), "Failed to enumerate device extensions") return gpu_destroy(), false; - for (uint32_t i = 0; i < count; i++) { - if (!strcmp(extensionInfo[i].extensionName, "VK_KHR_portability_subset")) { - extensions[extensionCount++] = "VK_KHR_portability_subset"; + for (uint32_t i = 0; i < COUNTOF(extensions); i++) { + if (!extensions[i].shouldEnable) continue; + if (hasExtension(extensionInfo, count, extensions[i].name)) { + CHECK(enabledExtensionCount < COUNTOF(enabledExtensions), "Too many device extensions") return gpu_destroy(), false; + if (extensions[i].isEnabled) *extensions[i].isEnabled = true; + enabledExtensions[enabledExtensionCount++] = extensions[i].name; } } + CHECK(supports.swapchain || !state.surface, "Swapchain extension not supported") return gpu_destroy(), false; + VkDeviceCreateInfo deviceInfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = config->features ? &enabledFeatures : NULL, @@ -2083,8 +2133,8 @@ bool gpu_init(gpu_config* config) { .pQueuePriorities = &(float) { 1.f }, .queueCount = 1 }, - .enabledExtensionCount = extensionCount, - .ppEnabledExtensionNames = extensions + .enabledExtensionCount = enabledExtensionCount, + .ppEnabledExtensionNames = enabledExtensions }; if (state.config.vk.createDevice) { @@ -2325,8 +2375,9 @@ void gpu_destroy(void) { state.tick[GPU] = state.tick[CPU]; expunge(); if (state.pipelineCache) vkDestroyPipelineCache(state.device, state.pipelineCache, NULL); - if (state.scratchpad[0].buffer) vkDestroyBuffer(state.device, state.scratchpad[0].buffer, NULL); - if (state.scratchpad[1].buffer) vkDestroyBuffer(state.device, state.scratchpad[1].buffer, NULL); + for (uint32_t i = 0; i < COUNTOF(state.scratchpad); i++) { + if (state.scratchpad[i].buffer) vkDestroyBuffer(state.device, state.scratchpad[i].buffer, NULL); + } for (uint32_t i = 0; i < COUNTOF(state.ticks); i++) { gpu_tick* tick = &state.ticks[i]; if (tick->pool) vkDestroyCommandPool(state.device, tick->pool, NULL); @@ -2529,6 +2580,8 @@ static gpu_memory* gpu_allocate(gpu_memory_type type, VkMemoryRequirements info, memory->handle = NULL; return NULL; } + } else { + memory->pointer = NULL; } allocator->block = memory; @@ -2586,6 +2639,24 @@ static void expunge() { } } +static bool hasLayer(VkLayerProperties* layers, uint32_t count, const char* layer) { + for (uint32_t i = 0; i < count; i++) { + if (!strcmp(layers[i].layerName, layer)) { + return true; + } + } + return false; +} + +static bool hasExtension(VkExtensionProperties* extensions, uint32_t count, const char* extension) { + for (uint32_t i = 0; i < count; i++) { + if (!strcmp(extensions[i].extensionName, extension)) { + return true; + } + } + return false; +} + static void createSwapchain(uint32_t width, uint32_t height) { if (width == 0 || height == 0) { state.swapchainValid = false; diff --git a/src/core/gpu_webgpu.c b/src/core/gpu_webgpu.c deleted file mode 100644 index ed96e886..00000000 --- a/src/core/gpu_webgpu.c +++ /dev/null @@ -1,10 +0,0 @@ -#include "gpu.h" -#include - -bool gpu_init(gpu_config* config) { - return false; -} - -void gpu_destroy(void) { - // -} diff --git a/src/core/gpu_wgpu.c b/src/core/gpu_wgpu.c new file mode 100644 index 00000000..c78e712b --- /dev/null +++ b/src/core/gpu_wgpu.c @@ -0,0 +1,552 @@ +#include "gpu.h" +#include +#include +#include + +struct gpu_buffer { + WGPUBuffer handle; +}; + +struct gpu_texture { + WGPUTexture handle; + WGPUTextureView view; +}; + +struct gpu_sampler { + WGPUSampler handle; +}; + +struct gpu_layout { + WGPUBindGroupLayout handle; +}; + +struct gpu_shader { + WGPUShaderModule handles[2]; + WGPUPipelineLayout pipelineLayout; +}; + +struct gpu_pipeline { + WGPURenderPipeline render; + WGPUComputePipeline compute; +}; + +size_t gpu_sizeof_buffer(void) { return sizeof(gpu_buffer); } +size_t gpu_sizeof_texture(void) { return sizeof(gpu_texture); } +size_t gpu_sizeof_sampler(void) { return sizeof(gpu_sampler); } +size_t gpu_sizeof_layout(void) { return sizeof(gpu_layout); } +size_t gpu_sizeof_shader(void) { return sizeof(gpu_shader); } + +// State + +static struct { + WGPUDevice device; +} state; + +// Helpers + +#define COUNTOF(x) (sizeof(x) / sizeof(x[0])) + +static WGPUTextureFormat convertFormat(gpu_texture_format format, bool srgb); + +// Buffer + +bool gpu_buffer_init(gpu_buffer* buffer, gpu_buffer_info* info) { + return buffer->handle = wgpuDeviceCreateBuffer(state.device, &(WGPUBufferDescriptor) { + .label = info->label, + .usage = + WGPUBufferUsage_Vertex | + WGPUBufferUsage_Index | + WGPUBufferUsage_Uniform | + WGPUBufferUsage_Storage | + WGPUBufferUsage_Indirect | + WGPUBufferUsage_CopySrc | + WGPUBufferUsage_CopyDst | + WGPUBufferUsage_QueryResolve, + .size = info->size, + .mappedAtCreation = !!info->pointer + }); +} + +void gpu_buffer_destroy(gpu_buffer* buffer) { + wgpuBufferDestroy(buffer->handle); +} + +// Texture + +bool gpu_texture_init(gpu_texture* texture, gpu_texture_info* info) { + static const WGPUTextureDimension dimensions[] = { + [GPU_TEXTURE_2D] = WGPUTextureDimension_2D, + [GPU_TEXTURE_3D] = WGPUTextureDimension_3D, + [GPU_TEXTURE_CUBE] = WGPUTextureDimension_2D, + [GPU_TEXTURE_ARRAY] = WGPUTextureDimension_2D + }; + + static const WGPUTextureViewDimension types[] = { + [GPU_TEXTURE_2D] = WGPUTextureViewDimension_2D, + [GPU_TEXTURE_3D] = WGPUTextureViewDimension_3D, + [GPU_TEXTURE_CUBE] = WGPUTextureViewDimension_Cube, + [GPU_TEXTURE_ARRAY] = WGPUTextureViewDimension_2DArray + }; + + texture->handle = wgpuDeviceCreateTexture(state.device, &(WGPUTextureDescriptor) { + .label = info->label, + .usage = + ((info->usage & GPU_TEXTURE_RENDER) ? WGPUTextureUsage_RenderAttachment : 0) | + ((info->usage & GPU_TEXTURE_SAMPLE) ? WGPUTextureUsage_TextureBinding : 0) | + ((info->usage & GPU_TEXTURE_STORAGE) ? WGPUTextureUsage_StorageBinding : 0) | + ((info->usage & GPU_TEXTURE_COPY_SRC) ? WGPUTextureUsage_CopySrc : 0) | + ((info->usage & GPU_TEXTURE_COPY_DST) ? WGPUTextureUsage_CopyDst : 0), + .dimension = dimensions[info->type], + .size = { info->size[0], info->size[1], info->size[2] }, + .format = convertFormat(info->format, info->srgb), + .mipLevelCount = info->mipmaps, + .sampleCount = info->samples + }); + + texture->view = wgpuTextureCreateView(texture->handle, &(WGPUTextureViewDescriptor) { + .format = wgpuTextureGetFormat(texture->handle), + .dimension = types[info->type], + .mipLevelCount = info->mipmaps, + .arrayLayerCount = info->type == GPU_TEXTURE_ARRAY ? info->size[2] : 1 + }); + + // TODO upload, mipgen + + return true; +} + +bool gpu_texture_init_view(gpu_texture* texture, gpu_texture_view_info* info) { + static const WGPUTextureViewDimension types[] = { + [GPU_TEXTURE_2D] = WGPUTextureViewDimension_2D, + [GPU_TEXTURE_3D] = WGPUTextureViewDimension_3D, + [GPU_TEXTURE_CUBE] = WGPUTextureViewDimension_Cube, + [GPU_TEXTURE_ARRAY] = WGPUTextureViewDimension_2DArray + }; + + texture->handle = NULL; + + return texture->view = wgpuTextureCreateView(info->source->handle, &(WGPUTextureViewDescriptor) { + .format = wgpuTextureGetFormat(info->source->handle), + .dimension = types[info->type], + .baseMipLevel = info->levelIndex, + .mipLevelCount = info->levelCount, + .baseArrayLayer = info->layerIndex, + .arrayLayerCount = info->layerCount + }); +} + +void gpu_texture_destroy(gpu_texture* texture) { + wgpuTextureViewRelease(texture->view); + wgpuTextureDestroy(texture->handle); +} + +// Sampler + +bool gpu_sampler_init(gpu_sampler* sampler, gpu_sampler_info* info) { + static const WGPUFilterMode filters[] = { + [GPU_FILTER_NEAREST] = WGPUFilterMode_Nearest, + [GPU_FILTER_LINEAR] = WGPUFilterMode_Linear + }; + + static const WGPUAddressMode wraps[] = { + [GPU_WRAP_CLAMP] = WGPUAddressMode_ClampToEdge, + [GPU_WRAP_REPEAT] = WGPUAddressMode_Repeat, + [GPU_WRAP_MIRROR] = WGPUAddressMode_MirrorRepeat + }; + + static const WGPUCompareFunction compares[] = { + [GPU_COMPARE_NONE] = WGPUCompareFunction_Always, + [GPU_COMPARE_EQUAL] = WGPUCompareFunction_Equal, + [GPU_COMPARE_NEQUAL] = WGPUCompareFunction_NotEqual, + [GPU_COMPARE_LESS] = WGPUCompareFunction_Less, + [GPU_COMPARE_LEQUAL] = WGPUCompareFunction_LessEqual, + [GPU_COMPARE_GREATER] = WGPUCompareFunction_Greater, + [GPU_COMPARE_GEQUAL] = WGPUCompareFunction_GreaterEqual + }; + + return sampler->handle = wgpuDeviceCreateSampler(state.device, &(WGPUSamplerDescriptor) { + .addressModeU = wraps[info->wrap[0]], + .addressModeV = wraps[info->wrap[1]], + .addressModeW = wraps[info->wrap[2]], + .magFilter = filters[info->mag], + .minFilter = filters[info->min], + .mipmapFilter = filters[info->mip], + .lodMinClamp = info->lodClamp[0], + .lodMaxClamp = info->lodClamp[1], + .compare = compares[info->compare], + .maxAnisotropy = info->anisotropy + }); +} + +void gpu_sampler_destroy(gpu_sampler* sampler) { + wgpuSamplerRelease(sampler->handle); +} + +// Layout + +bool gpu_layout_init(gpu_layout* layout, gpu_layout_info* info) { + static const WGPUBufferBindingType bufferTypes[] = { + [GPU_SLOT_UNIFORM_BUFFER] = WGPUBufferBindingType_Uniform, + [GPU_SLOT_STORAGE_BUFFER] = WGPUBufferBindingType_Storage, + [GPU_SLOT_UNIFORM_BUFFER_DYNAMIC] = WGPUBufferBindingType_Uniform, + [GPU_SLOT_STORAGE_BUFFER_DYNAMIC] = WGPUBufferBindingType_Storage + }; + + gpu_slot* slot = info->slots; + WGPUBindGroupLayoutEntry entries[32]; + for (uint32_t i = 0; i < info->count; i++, slot++) { + entries[i] = (WGPUBindGroupLayoutEntry) { + .binding = slot->number, + .visibility = + (((slot->stages & GPU_STAGE_VERTEX) ? WGPUShaderStage_Vertex : 0) | + ((slot->stages & GPU_STAGE_FRAGMENT) ? WGPUShaderStage_Fragment : 0) | + ((slot->stages & GPU_STAGE_COMPUTE) ? WGPUShaderStage_Compute : 0)) + }; + + switch (info->slots[i].type) { + case GPU_SLOT_UNIFORM_BUFFER_DYNAMIC: + case GPU_SLOT_STORAGE_BUFFER_DYNAMIC: + entries[i].buffer.hasDynamicOffset = true; + /* fallthrough */ + case GPU_SLOT_UNIFORM_BUFFER: + case GPU_SLOT_STORAGE_BUFFER: + entries[i].buffer.type = bufferTypes[slot->type]; + break; + + // FIXME need more metadata + case GPU_SLOT_SAMPLED_TEXTURE: + entries[i].texture.sampleType = WGPUTextureSampleType_Float; + entries[i].texture.viewDimension = WGPUTextureViewDimension_2D; + entries[i].texture.multisampled = false; + break; + + // FIXME need more metadata + case GPU_SLOT_STORAGE_TEXTURE: + entries[i].storageTexture.access = WGPUStorageTextureAccess_WriteOnly; + entries[i].storageTexture.format = WGPUTextureFormat_Undefined; + entries[i].storageTexture.viewDimension = WGPUTextureViewDimension_2D; + break; + + // FIXME need more metadata? + case GPU_SLOT_SAMPLER: + entries[i].sampler.type = WGPUSamplerBindingType_Filtering; + break; + } + } + + return layout->handle = wgpuDeviceCreateBindGroupLayout(state.device, &(WGPUBindGroupLayoutDescriptor) { + .entryCount = info->count, + .entries = entries + }); +} + +void gpu_layout_destroy(gpu_layout* layout) { + wgpuBindGroupLayoutRelease(layout->handle); +} + +// Shader + +bool gpu_shader_init(gpu_shader* shader, gpu_shader_info* info) { + for (uint32_t i = 0; i < COUNTOF(info->stages) && info->stages[i].code; i++) { + WGPUShaderModuleSPIRVDescriptor spirv = { + .chain.sType = WGPUSType_ShaderModuleSPIRVDescriptor, + .codeSize = info->stages[i].length, + .code = info->stages[i].code + }; + + shader->handles[i] = wgpuDeviceCreateShaderModule(state.device, &(WGPUShaderModuleDescriptor) { + .nextInChain = &spirv.chain, + .label = info->label + }); + } + + uint32_t layoutCount = 0; + WGPUBindGroupLayout layouts[4]; + for (uint32_t i = 0; i < COUNTOF(info->layouts) && info->layouts[i]; i++) { + layouts[layoutCount++] = info->layouts[i]->handle; + } + + return shader->pipelineLayout = wgpuDeviceCreatePipelineLayout(state.device, &(WGPUPipelineLayoutDescriptor) { + .bindGroupLayoutCount = layoutCount, + .bindGroupLayouts = layouts + }); +} + +void gpu_shader_destroy(gpu_shader* shader) { + wgpuShaderModuleRelease(shader->handles[0]); + wgpuShaderModuleRelease(shader->handles[1]); + wgpuPipelineLayoutRelease(shader->pipelineLayout); +} + +// Bundle + +// Pipeline + +bool gpu_pipeline_init_graphics(gpu_pipeline* pipeline, gpu_pipeline_info* info) { + static const WGPUPrimitiveTopology topologies[] = { + [GPU_DRAW_POINTS] = WGPUPrimitiveTopology_PointList, + [GPU_DRAW_LINES] = WGPUPrimitiveTopology_LineList, + [GPU_DRAW_TRIANGLES] = WGPUPrimitiveTopology_TriangleList + }; + + static const WGPUVertexFormat attributeTypes[] = { + [GPU_TYPE_I8x4] = WGPUVertexFormat_Sint8x4, + [GPU_TYPE_U8x4] = WGPUVertexFormat_Uint8x4, + [GPU_TYPE_SN8x4] = WGPUVertexFormat_Snorm8x4, + [GPU_TYPE_UN8x4] = WGPUVertexFormat_Unorm8x4, + [GPU_TYPE_UN10x3] = WGPUVertexFormat_Undefined, + [GPU_TYPE_I16] = WGPUVertexFormat_Undefined, + [GPU_TYPE_I16x2] = WGPUVertexFormat_Sint16x2, + [GPU_TYPE_I16x4] = WGPUVertexFormat_Sint16x4, + [GPU_TYPE_U16] = WGPUVertexFormat_Undefined, + [GPU_TYPE_U16x2] = WGPUVertexFormat_Uint16x2, + [GPU_TYPE_U16x4] = WGPUVertexFormat_Uint16x4, + [GPU_TYPE_SN16x2] = WGPUVertexFormat_Snorm16x2, + [GPU_TYPE_SN16x4] = WGPUVertexFormat_Snorm16x4, + [GPU_TYPE_UN16x2] = WGPUVertexFormat_Unorm16x2, + [GPU_TYPE_UN16x4] = WGPUVertexFormat_Unorm16x4, + [GPU_TYPE_I32] = WGPUVertexFormat_Sint32, + [GPU_TYPE_I32x2] = WGPUVertexFormat_Sint32x2, + [GPU_TYPE_I32x3] = WGPUVertexFormat_Sint32x3, + [GPU_TYPE_I32x4] = WGPUVertexFormat_Sint32x4, + [GPU_TYPE_U32] = WGPUVertexFormat_Uint32, + [GPU_TYPE_U32x2] = WGPUVertexFormat_Uint32x2, + [GPU_TYPE_U32x3] = WGPUVertexFormat_Uint32x3, + [GPU_TYPE_U32x4] = WGPUVertexFormat_Uint32x4, + [GPU_TYPE_F16x2] = WGPUVertexFormat_Float16x2, + [GPU_TYPE_F16x4] = WGPUVertexFormat_Float16x4, + [GPU_TYPE_F32] = WGPUVertexFormat_Float32, + [GPU_TYPE_F32x2] = WGPUVertexFormat_Float32x2, + [GPU_TYPE_F32x3] = WGPUVertexFormat_Float32x3, + [GPU_TYPE_F32x4] = WGPUVertexFormat_Float32x4 + }; + + static const WGPUFrontFace frontFaces[] = { + [GPU_WINDING_CCW] = WGPUFrontFace_CCW, + [GPU_WINDING_CW] = WGPUFrontFace_CW + }; + + static const WGPUCullMode cullModes[] = { + [GPU_CULL_NONE] = WGPUCullMode_None, + [GPU_CULL_FRONT] = WGPUCullMode_Front, + [GPU_CULL_BACK] = WGPUCullMode_Back + }; + + static const WGPUCompareFunction compares[] = { + [GPU_COMPARE_NONE] = WGPUCompareFunction_Always, + [GPU_COMPARE_EQUAL] = WGPUCompareFunction_Equal, + [GPU_COMPARE_NEQUAL] = WGPUCompareFunction_NotEqual, + [GPU_COMPARE_LESS] = WGPUCompareFunction_Less, + [GPU_COMPARE_LEQUAL] = WGPUCompareFunction_LessEqual, + [GPU_COMPARE_GREATER] = WGPUCompareFunction_Greater, + [GPU_COMPARE_GEQUAL] = WGPUCompareFunction_GreaterEqual + }; + + static const WGPUStencilOperation stencilOps[] = { + [GPU_STENCIL_KEEP] = WGPUStencilOperation_Keep, + [GPU_STENCIL_ZERO] = WGPUStencilOperation_Zero, + [GPU_STENCIL_REPLACE] = WGPUStencilOperation_Replace, + [GPU_STENCIL_INCREMENT] = WGPUStencilOperation_IncrementClamp, + [GPU_STENCIL_DECREMENT] = WGPUStencilOperation_DecrementClamp, + [GPU_STENCIL_INCREMENT_WRAP] = WGPUStencilOperation_IncrementWrap, + [GPU_STENCIL_DECREMENT_WRAP] = WGPUStencilOperation_DecrementWrap, + [GPU_STENCIL_INVERT] = WGPUStencilOperation_Invert + }; + + static const WGPUBlendFactor blendFactors[] = { + [GPU_BLEND_ZERO] = WGPUBlendFactor_Zero, + [GPU_BLEND_ONE] = WGPUBlendFactor_One, + [GPU_BLEND_SRC_COLOR] = WGPUBlendFactor_Src, + [GPU_BLEND_ONE_MINUS_SRC_COLOR] = WGPUBlendFactor_OneMinusSrc, + [GPU_BLEND_SRC_ALPHA] = WGPUBlendFactor_SrcAlpha, + [GPU_BLEND_ONE_MINUS_SRC_ALPHA] = WGPUBlendFactor_OneMinusSrcAlpha, + [GPU_BLEND_DST_COLOR] = WGPUBlendFactor_Dst, + [GPU_BLEND_ONE_MINUS_DST_COLOR] = WGPUBlendFactor_OneMinusDst, + [GPU_BLEND_DST_ALPHA] = WGPUBlendFactor_DstAlpha, + [GPU_BLEND_ONE_MINUS_DST_ALPHA] = WGPUBlendFactor_OneMinusDstAlpha + }; + + static const WGPUBlendOperation blendOps[] = { + [GPU_BLEND_ADD] = WGPUBlendOperation_Add, + [GPU_BLEND_SUB] = WGPUBlendOperation_Subtract, + [GPU_BLEND_RSUB] = WGPUBlendOperation_ReverseSubtract, + [GPU_BLEND_MIN] = WGPUBlendOperation_Min, + [GPU_BLEND_MAX] = WGPUBlendOperation_Max + }; + + uint32_t totalAttributeCount = 0; + WGPUVertexAttribute attributes[16]; + WGPUVertexBufferLayout vertexBuffers[16]; + for (uint32_t i = 0; i < info->vertex.bufferCount; i++) { + vertexBuffers[i] = (WGPUVertexBufferLayout) { + .arrayStride = info->vertex.bufferStrides[i], + .stepMode = (info->vertex.instancedBuffers & (1 << i)) ? WGPUVertexStepMode_Instance : WGPUVertexStepMode_Vertex, + .attributeCount = 0, + .attributes = &attributes[totalAttributeCount] + }; + + for (uint32_t j = 0; j < info->vertex.attributeCount; j++) { + if (info->vertex.attributes[j].buffer == i) { + attributes[totalAttributeCount++] = (WGPUVertexAttribute) { + .format = attributeTypes[info->vertex.attributes[j].type], + .offset = info->vertex.attributes[j].offset, + .shaderLocation = info->vertex.attributes[j].location + }; + } + } + + totalAttributeCount += vertexBuffers[i].attributeCount; + } + + WGPUVertexState vertex = { + .module = info->shader->handles[0], + .entryPoint = "main", + .bufferCount = info->vertex.bufferCount, + .buffers = vertexBuffers + }; + + WGPUPrimitiveState primitive = { + .topology = topologies[info->drawMode], + .frontFace = frontFaces[info->rasterizer.winding], + .cullMode = cullModes[info->rasterizer.cullMode], + }; + + WGPUStencilFaceState stencil = { + .compare = compares[info->stencil.test], + .failOp = stencilOps[info->stencil.failOp], + .depthFailOp = stencilOps[info->stencil.depthFailOp], + .passOp = stencilOps[info->stencil.passOp] + }; + + WGPUDepthStencilState depth = { + .format = convertFormat(info->depth.format, false), + .depthWriteEnabled = info->depth.write, + .depthCompare = compares[info->depth.test], + .stencilFront = stencil, + .stencilBack = stencil, + .stencilReadMask = info->stencil.testMask, + .stencilWriteMask = info->stencil.writeMask, + .depthBias = info->rasterizer.depthOffset, + .depthBiasSlopeScale = info->rasterizer.depthOffsetSloped, + .depthBiasClamp = info->rasterizer.depthOffsetClamp + }; + + WGPUMultisampleState multisample = { + .count = info->multisample.count, + .alphaToCoverageEnabled = info->multisample.alphaToCoverage + }; + + WGPUBlendState blends[4]; + WGPUColorTargetState targets[4]; + for (uint32_t i = 0; i < info->attachmentCount; i++) { + targets[i] = (WGPUColorTargetState) { + .format = convertFormat(info->color[i].format, info->color[i].srgb), + .blend = info->color[i].blend.enabled ? &blends[i] : NULL, + .writeMask = info->color[i].mask + }; + + if (info->color[i].blend.enabled) { + blends[i] = (WGPUBlendState) { + .color.operation = blendOps[info->color[i].blend.color.op], + .color.srcFactor = blendFactors[info->color[i].blend.color.src], + .color.dstFactor = blendFactors[info->color[i].blend.color.dst], + .alpha.operation = blendOps[info->color[i].blend.alpha.op], + .alpha.srcFactor = blendFactors[info->color[i].blend.alpha.src], + .alpha.dstFactor = blendFactors[info->color[i].blend.alpha.dst] + }; + } + } + + WGPUFragmentState fragment = { + .module = info->shader->handles[1], + .entryPoint = "main", + .targetCount = info->attachmentCount, + .targets = targets + }; + + WGPURenderPipelineDescriptor pipelineInfo = { + .label = info->label, + .layout = info->shader->pipelineLayout, + .vertex = vertex, + .primitive = primitive, + .depthStencil = info->depth.format ? &depth : NULL, + .multisample = multisample, + .fragment = &fragment + }; + + return pipeline->render = wgpuDeviceCreateRenderPipeline(state.device, &pipelineInfo); +} + +bool gpu_pipeline_init_compute(gpu_pipeline* pipeline, gpu_compute_pipeline_info* info) { + +} + +void gpu_pipeline_destroy(gpu_pipeline* pipeline) { + if (pipeline->render) wgpuRenderPipelineRelease(pipeline->render); + if (pipeline->compute) wgpuComputePipelineRelease(pipeline->compute); +} + +// Entry + +bool gpu_init(gpu_config* config) { + state.device = emscripten_webgpu_get_device(); + return !!state.device; +} + +void gpu_destroy(void) { + if (state.device) wgpuDeviceDestroy(state.device); + memset(&state, 0, sizeof(state)); +} + +// Helpers + +static WGPUTextureFormat convertFormat(gpu_texture_format format, bool srgb) { + static const WGPUTextureFormat formats[][2] = { + [GPU_FORMAT_R8] = { WGPUTextureFormat_R8Unorm, WGPUTextureFormat_R8Unorm }, + [GPU_FORMAT_RG8] = { WGPUTextureFormat_RG8Unorm, WGPUTextureFormat_RG8Unorm }, + [GPU_FORMAT_RGBA8] = { WGPUTextureFormat_RGBA8Unorm, WGPUTextureFormat_RGBA8UnormSrgb }, + [GPU_FORMAT_R16] = { WGPUTextureFormat_Undefined, WGPUTextureFormat_Undefined }, + [GPU_FORMAT_RG16] = { WGPUTextureFormat_Undefined, WGPUTextureFormat_Undefined }, + [GPU_FORMAT_RGBA16] = { WGPUTextureFormat_Undefined, WGPUTextureFormat_Undefined }, + [GPU_FORMAT_R16F] = { WGPUTextureFormat_R16Float, WGPUTextureFormat_R16Float }, + [GPU_FORMAT_RG16F] = { WGPUTextureFormat_RG16Float, WGPUTextureFormat_RG16Float }, + [GPU_FORMAT_RGBA16F] = { WGPUTextureFormat_RGBA16Float, WGPUTextureFormat_RGBA16Float }, + [GPU_FORMAT_R32F] = { WGPUTextureFormat_R32Float, WGPUTextureFormat_R32Float }, + [GPU_FORMAT_RG32F] = { WGPUTextureFormat_RG32Float, WGPUTextureFormat_RG32Float }, + [GPU_FORMAT_RGBA32F] = { WGPUTextureFormat_RGBA32Float, WGPUTextureFormat_RGBA32Float }, + [GPU_FORMAT_RGB565] = { WGPUTextureFormat_Undefined, WGPUTextureFormat_Undefined }, + [GPU_FORMAT_RGB5A1] = { WGPUTextureFormat_Undefined, WGPUTextureFormat_Undefined }, + [GPU_FORMAT_RGB10A2] = {WGPUTextureFormat_RGB10A2Unorm, WGPUTextureFormat_RGB10A2Unorm }, + [GPU_FORMAT_RG11B10F] = { WGPUTextureFormat_RG11B10Ufloat, WGPUTextureFormat_RG11B10Ufloat }, + [GPU_FORMAT_D16] = { WGPUTextureFormat_Depth16Unorm, WGPUTextureFormat_Depth16Unorm }, + [GPU_FORMAT_D32F] = { WGPUTextureFormat_Depth32Float, WGPUTextureFormat_Depth32Float }, + [GPU_FORMAT_D24S8] = { WGPUTextureFormat_Depth24PlusStencil8, WGPUTextureFormat_Depth24PlusStencil8 }, + [GPU_FORMAT_D32FS8] = { WGPUTextureFormat_Depth32FloatStencil8, WGPUTextureFormat_Depth32FloatStencil8 }, + [GPU_FORMAT_BC1] = { WGPUTextureFormat_BC1RGBAUnorm, WGPUTextureFormat_BC1RGBAUnormSrgb }, + [GPU_FORMAT_BC2] = { WGPUTextureFormat_BC2RGBAUnorm, WGPUTextureFormat_BC2RGBAUnormSrgb }, + [GPU_FORMAT_BC3] = { WGPUTextureFormat_BC3RGBAUnorm, WGPUTextureFormat_BC3RGBAUnormSrgb }, + [GPU_FORMAT_BC4U] = { WGPUTextureFormat_BC4RUnorm, WGPUTextureFormat_BC4RUnorm }, + [GPU_FORMAT_BC4S] = { WGPUTextureFormat_BC4RSnorm, WGPUTextureFormat_BC4RSnorm }, + [GPU_FORMAT_BC5U] = { WGPUTextureFormat_BC5RGUnorm, WGPUTextureFormat_BC5RGUnorm }, + [GPU_FORMAT_BC5S] = { WGPUTextureFormat_BC5RGSnorm, WGPUTextureFormat_BC5RGSnorm }, + [GPU_FORMAT_BC6UF] = { WGPUTextureFormat_BC6HRGBUfloat, WGPUTextureFormat_BC6HRGBUfloat }, + [GPU_FORMAT_BC6SF] = { WGPUTextureFormat_BC6HRGBFloat, WGPUTextureFormat_BC6HRGBFloat }, + [GPU_FORMAT_BC7] = { WGPUTextureFormat_BC7RGBAUnorm, WGPUTextureFormat_BC7RGBAUnormSrgb }, + [GPU_FORMAT_ASTC_4x4] = { WGPUTextureFormat_ASTC4x4Unorm, WGPUTextureFormat_ASTC4x4UnormSrgb }, + [GPU_FORMAT_ASTC_5x4] = { WGPUTextureFormat_ASTC5x4Unorm, WGPUTextureFormat_ASTC5x4UnormSrgb }, + [GPU_FORMAT_ASTC_5x5] = { WGPUTextureFormat_ASTC5x5Unorm, WGPUTextureFormat_ASTC5x5UnormSrgb }, + [GPU_FORMAT_ASTC_6x5] = { WGPUTextureFormat_ASTC6x5Unorm, WGPUTextureFormat_ASTC6x5UnormSrgb }, + [GPU_FORMAT_ASTC_6x6] = { WGPUTextureFormat_ASTC6x6Unorm, WGPUTextureFormat_ASTC6x6UnormSrgb }, + [GPU_FORMAT_ASTC_8x5] = { WGPUTextureFormat_ASTC8x5Unorm, WGPUTextureFormat_ASTC8x5UnormSrgb }, + [GPU_FORMAT_ASTC_8x6] = { WGPUTextureFormat_ASTC8x6Unorm, WGPUTextureFormat_ASTC8x6UnormSrgb }, + [GPU_FORMAT_ASTC_8x8] = { WGPUTextureFormat_ASTC8x8Unorm, WGPUTextureFormat_ASTC8x8UnormSrgb }, + [GPU_FORMAT_ASTC_10x5] = { WGPUTextureFormat_ASTC10x5Unorm, WGPUTextureFormat_ASTC10x5UnormSrgb }, + [GPU_FORMAT_ASTC_10x6] = { WGPUTextureFormat_ASTC10x6Unorm, WGPUTextureFormat_ASTC10x6UnormSrgb }, + [GPU_FORMAT_ASTC_10x8] = { WGPUTextureFormat_ASTC10x8Unorm, WGPUTextureFormat_ASTC10x8UnormSrgb }, + [GPU_FORMAT_ASTC_10x10] = { WGPUTextureFormat_ASTC10x10Unorm, WGPUTextureFormat_ASTC10x10UnormSrgb }, + [GPU_FORMAT_ASTC_12x10] = { WGPUTextureFormat_ASTC12x10Unorm, WGPUTextureFormat_ASTC12x10UnormSrgb }, + [GPU_FORMAT_ASTC_12x12] = { WGPUTextureFormat_ASTC12x12Unorm, WGPUTextureFormat_ASTC12x12UnormSrgb } + }; + + return formats[format][srgb]; +} diff --git a/src/core/maf.h b/src/core/maf.h index 1338a224..c1fed4de 100644 --- a/src/core/maf.h +++ b/src/core/maf.h @@ -724,6 +724,27 @@ MAF mat4 mat4_target(mat4 m, vec3 from, vec3 to, vec3 up) { return m; } +MAF mat4 mat4_reflect(mat4 m, vec3 p, vec3 n) { + float d = vec3_dot(p, n); + m[0] = -2.f * n[0] * n[0] + 1.f; + m[1] = -2.f * n[0] * n[1]; + m[2] = -2.f * n[0] * n[2]; + m[3] = 0.f; + m[4] = -2.f * n[1] * n[0]; + m[5] = -2.f * n[1] * n[1] + 1.f; + m[6] = -2.f * n[1] * n[2]; + m[7] = 0.f; + m[8] = -2.f * n[2] * n[0]; + m[9] = -2.f * n[2] * n[1]; + m[10] = -2.f * n[2] * n[2] + 1.f; + m[11] = 0.f; + m[12] = 2.f * d * n[0]; + m[13] = 2.f * d * n[1]; + m[14] = 2.f * d * n[2]; + m[15] = 1.f; + return m; +} + // Apply matrix to a vec3 // Difference from mat4_mulVec4: w normalize is performed, w in vec3 is ignored MAF void mat4_transform(mat4 m, vec3 v) { diff --git a/src/core/os_android.c b/src/core/os_android.c index c13ea1df..e37073e8 100644 --- a/src/core/os_android.c +++ b/src/core/os_android.c @@ -401,20 +401,12 @@ bool os_is_key_down(os_key key) { // Private, must be declared manually to use -struct ANativeActivity* os_get_activity() { - return state.app->activity; +void* os_get_java_vm() { + return state.app->activity->vm; } -int os_get_activity_state() { - return state.app->activityState; -} - -ANativeWindow* os_get_native_window() { - return state.app->window; -} - -JNIEnv* os_get_jni() { - return state.jni; +void* os_get_jni_context() { + return state.app->activity->clazz; } const char** os_vk_get_instance_extensions(uint32_t* count) { diff --git a/src/core/os_wasm.c b/src/core/os_wasm.c index 903fff50..f02b1b3f 100644 --- a/src/core/os_wasm.c +++ b/src/core/os_wasm.c @@ -41,13 +41,14 @@ static EM_BOOL onFocusChanged(int type, const EmscriptenFocusEvent* data, void* } static EM_BOOL onResize(int type, const EmscriptenUiEvent* data, void* userdata) { - emscripten_webgl_get_drawing_buffer_size(state.context, &state.framebufferWidth, &state.framebufferHeight); - - int newWidth, newHeight; + int newWidth, newHeight, newFramebufferWidth, newFramebufferHeight; emscripten_get_canvas_element_size(CANVAS, &newWidth, &newHeight); if (state.width != (uint32_t) newWidth || state.height != (uint32_t) newHeight) { + emscripten_webgl_get_drawing_buffer_size(state.context, &newFramebufferWidth, &newFramebufferHeight); state.width = newWidth; state.height = newHeight; + state.framebufferWidth = newFramebufferWidth; + state.framebufferHeight = newFramebufferHeight; if (state.onWindowResize) { state.onWindowResize(state.width, state.height); return true; @@ -181,7 +182,7 @@ static EM_BOOL onKeyEvent(int type, const EmscriptenKeyboardEvent* data, void* u return false; } -bool os_init() { +bool os_init(void) { emscripten_set_beforeunload_callback(NULL, onBeforeUnload); emscripten_set_focus_callback(CANVAS, NULL, true, onFocusChanged); emscripten_set_blur_callback(CANVAS, NULL, true, onFocusChanged); @@ -194,7 +195,7 @@ bool os_init() { return true; } -void os_destroy() { +void os_destroy(void) { emscripten_set_beforeunload_callback(NULL, NULL); emscripten_set_focus_callback(CANVAS, NULL, true, NULL); emscripten_set_blur_callback(CANVAS, NULL, true, NULL); @@ -206,19 +207,19 @@ void os_destroy() { emscripten_set_keyup_callback(CANVAS, NULL, true, NULL); } -const char* os_get_name() { +const char* os_get_name(void) { return "Web"; } -uint32_t os_get_core_count() { +uint32_t os_get_core_count(void) { return 1; } -void os_open_console() { +void os_open_console(void) { // } -double os_get_time() { +double os_get_time(void) { return emscripten_get_now() / 1000.; } @@ -230,7 +231,7 @@ void os_request_permission(os_permission permission) { // } -void os_poll_events() { +void os_poll_events(void) { // } @@ -289,12 +290,18 @@ bool os_window_open(const os_window_config* flags) { } emscripten_webgl_make_context_current(state.context); - emscripten_webgl_get_drawing_buffer_size(state.context, &state.framebufferWidth, &state.framebufferHeight); - emscripten_get_canvas_element_size(CANVAS, &state.width, &state.height); + + int width, height, framebufferWidth, framebufferHeight; + emscripten_webgl_get_drawing_buffer_size(state.context, &framebufferWidth, &framebufferHeight); + emscripten_get_canvas_element_size(CANVAS, &width, &height); + state.width = width; + state.height = height; + state.framebufferWidth = framebufferWidth; + state.framebufferHeight = framebufferHeight; return true; } -bool os_window_is_open() { +bool os_window_is_open(void) { return state.context > 0; } diff --git a/src/modules/event/event.h b/src/modules/event/event.h index 0191a55b..fee4d997 100644 --- a/src/modules/event/event.h +++ b/src/modules/event/event.h @@ -28,6 +28,7 @@ typedef enum { TYPE_BOOLEAN, TYPE_NUMBER, TYPE_STRING, + TYPE_MINISTRING, TYPE_OBJECT } VariantType; @@ -38,6 +39,10 @@ typedef union { char* pointer; size_t length; } string; + struct { + uint8_t length; + char data[23]; + } ministring; struct { void* pointer; const char* type; diff --git a/src/modules/filesystem/file.c b/src/modules/filesystem/file.c deleted file mode 100644 index 07fda6f1..00000000 --- a/src/modules/filesystem/file.c +++ /dev/null @@ -1,91 +0,0 @@ -#include "filesystem/file.h" -#include "filesystem/filesystem.h" -#include "util.h" -#include -#include - -// Currently only read operations are supported by File and all files are read into memory fully on open - -typedef struct { - uint8_t* data; - size_t offset; - size_t size; -} FileInner; - -File* lovrFileInit(File* file ,const char* path) { - file->path = path; - file->handle = NULL; - file->mode = 0; - return file; -} - -void lovrFileDestroy(void* ref) { - File* file = ref; - if (file->handle) { - lovrFileClose(file); - } - free(file); -} - -bool lovrFileOpen(File* file, FileMode mode) { - lovrAssert(!file->handle, "File is already open"); - file->mode = mode; - - if (mode == OPEN_WRITE || mode == OPEN_APPEND) - return false; - - FileInner *fileInner = malloc(sizeof(FileInner)); - fileInner->offset = 0; - fileInner->data = lovrFilesystemRead(file->path, -1, &fileInner->size); - file->handle = fileInner; - - if (!fileInner->data) { - fileInner->size = 0; - return false; - } - - return true; -} - -void lovrFileClose(File* file) { - lovrAssert(file->handle, "File must be open to close it"); - FileInner *fileInner = (FileInner *)file->handle; - free(fileInner->data); - free(file->handle); - file->handle = NULL; -} - -size_t lovrFileRead(File* file, void* data, size_t bytes) { - lovrAssert(file->handle && file->mode == OPEN_READ, "File must be open for reading"); - FileInner *fileInner = (FileInner *)file->handle; - if (fileInner->offset + bytes > fileInner->size) - return 0; - memcpy(data, fileInner->data + fileInner->offset, bytes); - fileInner->offset += bytes; - return bytes; -} - -size_t lovrFileWrite(File* file, const void* data, size_t bytes) { - lovrThrow("Writing not supported"); -} - -size_t lovrFileGetSize(File* file) { - lovrAssert(file->handle, "File must be open to get its size"); - FileInner *fileInner = (FileInner *)file->handle; - return fileInner->size; -} - -bool lovrFileSeek(File* file, size_t position) { - lovrAssert(file->handle, "File must be open to seek"); - FileInner *fileInner = (FileInner *)file->handle; - if (position >= fileInner->size) // FIXME: Should seeking to fileInner->size exactly be allowed? - return false; - fileInner->offset = position; - return true; -} - -size_t lovrFileTell(File* file) { - lovrAssert(file->handle, "File must be open to tell"); - FileInner *fileInner = (FileInner *)file->handle; - return fileInner->offset; -} diff --git a/src/modules/filesystem/file.h b/src/modules/filesystem/file.h deleted file mode 100644 index 96ed5550..00000000 --- a/src/modules/filesystem/file.h +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include - -#pragma once - -typedef enum { - OPEN_READ, - OPEN_WRITE, - OPEN_APPEND -} FileMode; - -typedef struct { - const char* path; - void* handle; - FileMode mode; -} File; - -File* lovrFileInit(File* file, const char* filename); -void lovrFileDestroy(void* ref); -bool lovrFileOpen(File* file, FileMode mode); -void lovrFileClose(File* file); -size_t lovrFileRead(File* file, void* data, size_t bytes); -size_t lovrFileWrite(File* file, const void* data, size_t bytes); -size_t lovrFileGetSize(File* file); -bool lovrFileSeek(File* file, size_t position); -size_t lovrFileTell(File* file); diff --git a/src/modules/filesystem/filesystem.c b/src/modules/filesystem/filesystem.c index 7a2a5d77..ebd86738 100644 --- a/src/modules/filesystem/filesystem.c +++ b/src/modules/filesystem/filesystem.c @@ -433,7 +433,10 @@ const char* lovrFilesystemGetRequirePath() { } void lovrFilesystemSetRequirePath(const char* requirePath) { - strncpy(state.requirePath, requirePath, sizeof(state.requirePath) - 1); + size_t length = strlen(requirePath); + lovrCheck(length < sizeof(state.requirePath), "Require path is too long"); + memcpy(state.requirePath, requirePath, length); + state.requirePath[length] = '\0'; } // Archive: dir diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index b10e2978..77b18b48 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -497,23 +497,23 @@ bool lovrGraphicsInit(GraphicsConfig* config) { } gpu_slot builtinSlots[] = { - { 0, GPU_SLOT_UNIFORM_BUFFER, GPU_STAGE_ALL }, // Globals - { 1, GPU_SLOT_UNIFORM_BUFFER, GPU_STAGE_ALL }, // Cameras - { 2, GPU_SLOT_UNIFORM_BUFFER, GPU_STAGE_ALL }, // Draw data - { 3, GPU_SLOT_SAMPLER, GPU_STAGE_ALL }, // Default sampler + { 0, GPU_SLOT_UNIFORM_BUFFER, GPU_STAGE_GRAPHICS }, // Globals + { 1, GPU_SLOT_UNIFORM_BUFFER, GPU_STAGE_GRAPHICS }, // Cameras + { 2, GPU_SLOT_UNIFORM_BUFFER, GPU_STAGE_GRAPHICS }, // Draw data + { 3, GPU_SLOT_SAMPLER, GPU_STAGE_GRAPHICS } // Default sampler }; state.builtinLayout = getLayout(builtinSlots, COUNTOF(builtinSlots)); gpu_slot materialSlots[] = { - { 0, GPU_SLOT_UNIFORM_BUFFER, GPU_STAGE_VERTEX | GPU_STAGE_FRAGMENT }, // Data - { 1, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_VERTEX | GPU_STAGE_FRAGMENT }, // Color - { 2, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_VERTEX | GPU_STAGE_FRAGMENT }, // Glow - { 3, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_VERTEX | GPU_STAGE_FRAGMENT }, // Occlusion - { 4, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_VERTEX | GPU_STAGE_FRAGMENT }, // Metalness - { 5, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_VERTEX | GPU_STAGE_FRAGMENT }, // Roughness - { 6, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_VERTEX | GPU_STAGE_FRAGMENT }, // Clearcoat - { 7, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_VERTEX | GPU_STAGE_FRAGMENT } // Normal + { 0, GPU_SLOT_UNIFORM_BUFFER, GPU_STAGE_GRAPHICS }, // Data + { 1, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_GRAPHICS }, // Color + { 2, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_GRAPHICS }, // Glow + { 3, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_GRAPHICS }, // Occlusion + { 4, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_GRAPHICS }, // Metalness + { 5, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_GRAPHICS }, // Roughness + { 6, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_GRAPHICS }, // Clearcoat + { 7, GPU_SLOT_SAMPLED_TEXTURE, GPU_STAGE_GRAPHICS } // Normal }; state.materialLayout = getLayout(materialSlots, COUNTOF(materialSlots)); @@ -2066,6 +2066,7 @@ Material* lovrMaterialCreate(const MaterialInfo* info) { lovrRetain(textures[i]); Texture* texture = textures[i] ? textures[i] : state.defaultTexture; lovrCheck(i == 0 || texture->info.type == TEXTURE_2D, "Material textures must be 2D"); + lovrCheck(texture->info.usage & TEXTURE_SAMPLE, "Textures must be created with the 'sample' usage to use them in Materials"); bindings[i + 1] = (gpu_binding) { i + 1, GPU_SLOT_SAMPLED_TEXTURE, .texture = texture->gpu }; material->hasWritableTexture |= texture->info.usage != TEXTURE_SAMPLE; } @@ -3090,6 +3091,8 @@ bool lovrReadbackWait(Readback* readback) { return false; } + beginFrame(); + bool waited = gpu_wait_tick(readback->tick); if (waited) { @@ -5506,7 +5509,7 @@ static void releasePassResources(void) { lovrRelease(access->texture, lovrTextureDestroy); } - if (pass->info.type == PASS_RENDER) { + if (pass->info.type == PASS_RENDER || pass->info.type == PASS_COMPUTE) { for (size_t j = 0; j <= pass->pipelineIndex; j++) { Pipeline* pipeline = pass->pipeline - j; lovrRelease(pipeline->font, lovrFontDestroy); diff --git a/src/modules/headset/headset_openxr.c b/src/modules/headset/headset_openxr.c index 9d478dd6..b2ae52d1 100644 --- a/src/modules/headset/headset_openxr.c +++ b/src/modules/headset/headset_openxr.c @@ -19,8 +19,8 @@ #include #elif defined(__ANDROID__) #define XR_USE_PLATFORM_ANDROID -struct ANativeActivity* os_get_activity(void); -#include +void* os_get_java_vm(void); +void* os_get_jni_context(void); #include #endif @@ -180,6 +180,7 @@ static struct { bool handTrackingAim; bool handTrackingMesh; bool controllerModel; + bool headless; bool keyboardTracking; bool overlay; bool refreshRate; @@ -334,11 +335,10 @@ static bool openxr_init(HeadsetConfig* config) { return false; } - ANativeActivity* activity = os_get_activity(); XrLoaderInitInfoAndroidKHR loaderInfo = { .type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR, - .applicationVM = activity->vm, - .applicationContext = activity->clazz + .applicationVM = os_get_java_vm(), + .applicationContext = os_get_jni_context() }; if (XR_FAILED(xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*) &loaderInfo))) { @@ -367,9 +367,10 @@ static bool openxr_init(HeadsetConfig* config) { { "XR_FB_hand_tracking_aim", &state.features.handTrackingAim, true }, { "XR_FB_hand_tracking_mesh", &state.features.handTrackingMesh, true }, { "XR_MSFT_controller_model", &state.features.controllerModel, true }, + { "XR_FB_keyboard_tracking", &state.features.keyboardTracking, true }, + { "XR_MND_headless", &state.features.headless, true }, { "XR_EXTX_overlay", &state.features.overlay, config->overlay }, - { "XR_HTCX_vive_tracker_interaction", &state.features.viveTrackers, true }, - { "XR_FB_keyboard_tracking", &state.features.keyboardTracking, true } + { "XR_HTCX_vive_tracker_interaction", &state.features.viveTrackers, true } }; uint32_t enabledExtensionCount = 0; @@ -827,37 +828,47 @@ static bool openxr_init(HeadsetConfig* config) { } static void openxr_start(void) { - { // Session -#ifdef LOVR_VK - XrGraphicsRequirementsVulkanKHR requirements = { .type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR, NULL }; - PFN_xrGetVulkanGraphicsRequirements2KHR xrGetVulkanGraphicsRequirements2KHR; - XR_LOAD(xrGetVulkanGraphicsRequirements2KHR); - XR(xrGetVulkanGraphicsRequirements2KHR(state.instance, state.system, &requirements)); - if (XR_VERSION_MAJOR(requirements.minApiVersionSupported) > 1 || XR_VERSION_MINOR(requirements.minApiVersionSupported) > 1) { - lovrThrow("OpenXR Vulkan version not supported"); - } - - uint32_t queueFamilyIndex, queueIndex; - gpu_vk_get_queue(&queueFamilyIndex, &queueIndex); - - XrGraphicsBindingVulkanKHR graphicsBinding = { - .type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR, - .instance = (VkInstance) gpu_vk_get_instance(), - .physicalDevice = (VkPhysicalDevice) gpu_vk_get_physical_device(), - .device = (VkDevice) gpu_vk_get_device(), - .queueFamilyIndex = queueFamilyIndex, - .queueIndex = queueIndex - }; +#ifdef LOVR_DISABLE_GRAPHICS + bool hasGraphics = false; #else -#error "Unsupported renderer" + bool hasGraphics = lovrGraphicsIsInitialized(); #endif + { // Session XrSessionCreateInfo info = { .type = XR_TYPE_SESSION_CREATE_INFO, - .next = &graphicsBinding, .systemId = state.system }; +#if !defined(LOVR_DISABLE_GRAPHICS) && defined(LOVR_VK) + XrGraphicsBindingVulkanKHR graphicsBinding = { + .type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR, + .next = info.next + }; + + XrGraphicsRequirementsVulkanKHR requirements = { + .type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR + }; + + if (hasGraphics) { + PFN_xrGetVulkanGraphicsRequirements2KHR xrGetVulkanGraphicsRequirements2KHR; + XR_LOAD(xrGetVulkanGraphicsRequirements2KHR); + + XR(xrGetVulkanGraphicsRequirements2KHR(state.instance, state.system, &requirements)); + if (XR_VERSION_MAJOR(requirements.minApiVersionSupported) > 1 || XR_VERSION_MINOR(requirements.minApiVersionSupported) > 1) { + lovrThrow("OpenXR Vulkan version not supported"); + } + + graphicsBinding.instance = (VkInstance) gpu_vk_get_instance(); + graphicsBinding.physicalDevice = (VkPhysicalDevice) gpu_vk_get_physical_device(); + graphicsBinding.device = (VkDevice) gpu_vk_get_device(); + gpu_vk_get_queue(&graphicsBinding.queueFamilyIndex, &graphicsBinding.queueIndex); + info.next = &graphicsBinding; + } +#endif + + lovrAssert(hasGraphics || state.features.headless, "Graphics module is not available, and headless headset is not supported"); + #ifdef XR_EXTX_overlay XrSessionCreateInfoOverlayEXTX overlayInfo = { .type = XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX, @@ -922,7 +933,8 @@ static void openxr_start(void) { } } - { // Swapchain + // Swapchain + if (hasGraphics) { state.depthFormat = state.config.stencil ? FORMAT_D32FS8 : FORMAT_D32F; if (state.config.stencil && !lovrGraphicsIsFormatSupported(state.depthFormat, TEXTURE_FEATURE_RENDER)) {