Merge branch 'master' into dev

This commit is contained in:
bjorn 2022-11-09 19:06:02 -08:00
commit 29f2a66bf0
22 changed files with 300 additions and 176 deletions

View File

@ -60,7 +60,6 @@ if(EMSCRIPTEN)
set(LOVR_USE_WEBGPU ON)
set(LOVR_USE_VULKAN OFF)
elseif(ANDROID)
set(ANDROID_FLAVOR "oculus" CACHE STRING "Which Android flavor to build (oculus or pico)")
find_package(Java REQUIRED)
set(LOVR_USE_DESKTOP OFF)
if(LOVR_BUILD_EXE)
@ -189,8 +188,6 @@ if(LOVR_USE_GLSLANG)
"${CMAKE_CURRENT_SOURCE_DIR}/deps/glslang/StandAlone/resource_limits_c.cpp"
)
target_link_libraries(lovr_glslang PUBLIC glslang)
set(LOVR_GLSLANG lovr_glslang SPIRV)
endif()
@ -206,15 +203,8 @@ if(LOVR_ENABLE_HEADSET AND LOVR_USE_OPENXR)
if(ANDROID)
add_library(openxr_loader SHARED IMPORTED)
set(LOVR_OPENXR openxr_loader)
if(ANDROID_FLAVOR STREQUAL "oculus")
set(LOVR_OPENXR_OCULUS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/oculus-openxr" CACHE STRING "The path to the Oculus OpenXR loader")
set_target_properties(openxr_loader PROPERTIES IMPORTED_LOCATION "${LOVR_OPENXR_OCULUS_PATH}/Libs/Android/${ANDROID_ABI}/Release/libopenxr_loader.so")
elseif(ANDROID_FLAVOR STREQUAL "pico")
set(LOVR_OPENXR_PICO_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/pico-openxr" CACHE STRING "The path to the Pico OpenXR loader")
set_target_properties(openxr_loader PROPERTIES IMPORTED_LOCATION "${LOVR_OPENXR_PICO_PATH}/Libs/Android/${ANDROID_ABI}/libopenxr_loader.so")
else()
message(FATAL_ERROR "Unsupported Android flavor (expected: oculus or pico)")
endif()
set(LOVR_OPENXR_OCULUS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/oculus-openxr" CACHE STRING "The path to the Oculus OpenXR loader")
set_target_properties(openxr_loader PROPERTIES IMPORTED_LOCATION "${LOVR_OPENXR_OCULUS_PATH}/Libs/Android/${ANDROID_ABI}/Release/libopenxr_loader.so")
else()
if(LOVR_SYSTEM_OPENXR)
pkg_search_module(OPENXR openxr)
@ -725,7 +715,7 @@ elseif(ANDROID)
if(LOVR_BUILD_EXE)
set(ANDROID_JAR "${ANDROID_SDK}/platforms/${ANDROID_PLATFORM}/android.jar")
set(ANDROID_TOOLS "${ANDROID_SDK}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}")
set(ANDROID_MANIFEST "${CMAKE_CURRENT_SOURCE_DIR}/etc/AndroidManifest_${ANDROID_FLAVOR}.xml" CACHE STRING "The AndroidManifest.xml file to use")
set(ANDROID_MANIFEST "${CMAKE_CURRENT_SOURCE_DIR}/etc/AndroidManifest.xml" CACHE STRING "The AndroidManifest.xml file to use")
# If assets are included in the apk then add '-A assets' to aapt, otherwise don't add any flags
if(ANDROID_ASSETS)

View File

@ -13,16 +13,16 @@ You can use LÖVR to easily create VR experiences without much setup or programm
[**Homepage**](https://lovr.org) | [**Documentation**](https://lovr.org/docs) | [**FAQ**](https://lovr.org/docs/FAQ)
<p align="left">
<span><img src="http://lovr.org/static/img/wattle.jpg" width="32.5%"/></span>
<span><img src="http://lovr.org/static/img/levrage.jpg" width="32.5%"/></span>
<span><img src="http://lovr.org/static/img/planets.jpg" width="32.5%"/></span>
<span><img src="http://lovr.org/static/img/screen1.jpg" width="32.5%"/></span>
<span><img src="http://lovr.org/static/img/screen2.jpg" width="32.5%"/></span>
<span><img src="http://lovr.org/static/img/screen3.jpg" width="32.5%"/></span>
</p>
Features
---
- **Cross-Platform** - Runs on Windows, Mac, Linux, Android, WebXR.
- **Cross-Device** - Supports Vive/Index, Oculus Rift/Quest, Pico, Windows MR, and has a VR simulator.
- **Cross-Device** - Supports Vive/Index, Oculus Rift/Quest, Windows MR, and has a VR simulator.
- **Beginner-friendly** - Simple VR scenes can be created in just a few lines of Lua.
- **Fast** - Writen in C11 and scripted with LuaJIT, includes optimized single-pass stereo rendering.
- **Asset Import** - Supports 3D models (glTF, OBJ), skeletal animation, HDR textures, cubemaps, fonts, etc.

View File

@ -41,7 +41,6 @@ config = {
buildtools = '30.0.3',
keystore = '/path/to/keystore',
keystorepass = 'pass:password',
flavor = 'oculus',
manifest = nil,
package = nil,
project = nil
@ -54,7 +53,6 @@ config = {
-- sanitize adds checks for memory leaks and undefined behavior (reduces performance)
-- strict will make warnings fail the build
-- luajit and headsets.openxr should be a path to a folder with the lib (tup can't build them yet)
-- android.flavor can be 'oculus' or 'pico'
-- android.package should be something like 'org.lovr.app'
-- android.project is a path to a lovr project folder that will be included in the apk
-- tup.config can also be used to override properties without modifying this file:
@ -494,7 +492,7 @@ if target == 'android' then
unsigned = 'bin/.lovr.apk.unsigned'
apk = 'bin/lovr.apk'
manifest = config.android.manifest or ('etc/AndroidManifest_%s.xml'):format(config.android.flavor)
manifest = config.android.manifest or 'etc/AndroidManifest.xml'
package = config.android.package and #config.android.package > 0 and ('--rename-manifest-package ' .. config.android.package) or ''
project = config.android.project and #config.android.project > 0 and ('-A ' .. config.android.project) or ''

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.lovr.app">
<uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
<uses-feature android:name="android.hardware.vr.headtracking" android:required="true"/>
<uses-feature android:glEsVersion="0x00030002" android:required="true"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<application android:allowBackup="false" android:label="LÖVR">
<meta-data android:name="pvr.app.type" android:value="vr"/>
<activity android:name="Activity" android:launchMode="singleTask" android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<meta-data android:name="android.app.lib_name" android:value="lovr"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -135,7 +135,7 @@ function lovr.run()
if lovr.system.isWindowOpen() then
if lovr.mirror then
local pass = lovr.graphics.getWindowPass()
local skip = lovr.mirror(pass)
local skip = not pass or lovr.mirror(pass)
if not skip then lovr.graphics.submit(pass) end
end
lovr.graphics.present()
@ -213,9 +213,11 @@ function lovr.errhand(message)
if lovr.system.isWindowOpen() then
local pass = lovr.graphics.getWindowPass()
render(pass)
lovr.graphics.submit(pass)
lovr.graphics.present()
if pass then
render(pass)
lovr.graphics.submit(pass)
lovr.graphics.present()
end
end
end
end

View File

@ -10,6 +10,7 @@ StringEntry lovrShapeType[] = {
[SHAPE_CAPSULE] = ENTRY("capsule"),
[SHAPE_CYLINDER] = ENTRY("cylinder"),
[SHAPE_MESH] = ENTRY("mesh"),
[SHAPE_TERRAIN] = ENTRY("terrain"),
{ 0 }
};

View File

@ -13,6 +13,7 @@ void luax_pushshape(lua_State* L, Shape* shape) {
case SHAPE_CAPSULE: luax_pushtype(L, CapsuleShape, shape); break;
case SHAPE_CYLINDER: luax_pushtype(L, CylinderShape, shape); break;
case SHAPE_MESH: luax_pushtype(L, MeshShape, shape); break;
case SHAPE_TERRAIN: luax_pushtype(L, TerrainShape, shape); break;
default: lovrUnreachable();
}
}

View File

@ -1,5 +1,6 @@
#include "api.h"
#include "physics/physics.h"
#include "data/image.h"
#include "util.h"
#include <lua.h>
#include <lauxlib.h>
@ -42,6 +43,16 @@ static void raycastCallback(Shape* shape, float x, float y, float z, float nx, f
lua_call(L, 7, 0);
}
static float terrainCallback(lua_State * L, int fn_index, float x, float z) {
lua_pushvalue(L, fn_index);
lua_pushnumber(L, x);
lua_pushnumber(L, z);
lua_call(L, 2, 1);
float height = luax_checkfloat(L, -1);
lua_remove(L, -1);
return height;
}
static int l_lovrWorldNewCollider(lua_State* L) {
World* world = luax_checktype(L, 1, World);
float position[4];
@ -148,6 +159,49 @@ static int l_lovrWorldNewMeshCollider(lua_State* L) {
return 1;
}
static int l_lovrWorldNewTerrainCollider(lua_State* L) {
World* world = luax_checktype(L, 1, World);
TerrainShape* shape;
float horizontalScale = luax_checkfloat(L, 2);
int type = lua_type(L, 3);
if (type == LUA_TNIL || type == LUA_TNONE) {
float vertices[4] = {0.f};
shape = lovrTerrainShapeCreate(vertices, 2, 2, horizontalScale, 1.f);
} else if (type == LUA_TFUNCTION) {
unsigned samples = luax_optu32(L, 4, 100);
float* vertices = malloc(sizeof(float) * samples * samples);
for (unsigned i = 0; i < samples * samples; i++) {
float x = horizontalScale * (-0.5f + ((float) (i % samples)) / samples);
float z = horizontalScale * (-0.5f + ((float) (i / samples)) / samples);
vertices[i] = terrainCallback(L, 3, x, z);
}
shape = lovrTerrainShapeCreate(vertices, samples, samples, horizontalScale, 1.f);
free(vertices);
} else if (type == LUA_TUSERDATA) {
Image* image = luax_checktype(L, 3, Image);
uint32_t imageWidth = lovrImageGetWidth(image, 0);
uint32_t imageHeight = lovrImageGetHeight(image, 0);
float verticalScale = luax_optfloat(L, 4, 1.f);
float* vertices = malloc(sizeof(float) * imageWidth * imageHeight);
for (int y = 0; y < imageHeight; y++) {
for (int x = 0; x < imageWidth; x++) {
float pixel[4];
lovrImageGetPixel(image, x, y, pixel);
vertices[x + y * imageWidth] = pixel[0];
}
}
shape = lovrTerrainShapeCreate(vertices, imageWidth, imageHeight, horizontalScale, verticalScale);
free(vertices);
}
Collider* collider = lovrColliderCreate(world, 0, 0, 0);
lovrColliderAddShape(collider, shape);
lovrColliderSetKinematic(collider, true);
luax_pushtype(L, Collider, collider);
lovrRelease(collider, lovrColliderDestroy);
lovrRelease(shape, lovrShapeDestroy);
return 1;
}
static int l_lovrWorldGetColliders(lua_State* L) {
World* world = luax_checktype(L, 1, World);
@ -388,6 +442,7 @@ const luaL_Reg lovrWorld[] = {
{ "newCylinderCollider", l_lovrWorldNewCylinderCollider },
{ "newSphereCollider", l_lovrWorldNewSphereCollider },
{ "newMeshCollider", l_lovrWorldNewMeshCollider },
{ "newTerrainCollider", l_lovrWorldNewTerrainCollider },
{ "getColliders", l_lovrWorldGetColliders },
{ "destroy", l_lovrWorldDestroy },
{ "update", l_lovrWorldUpdate },

View File

@ -165,18 +165,24 @@ static int l_lovrSystemIsWindowOpen(lua_State* L) {
}
static int l_lovrSystemGetWindowWidth(lua_State* L) {
lua_pushnumber(L, lovrSystemGetWindowWidth());
uint32_t width, height;
lovrSystemGetWindowSize(&width, &height);
lua_pushnumber(L, width);
return 1;
}
static int l_lovrSystemGetWindowHeight(lua_State* L) {
lua_pushnumber(L, lovrSystemGetWindowHeight());
uint32_t width, height;
lovrSystemGetWindowSize(&width, &height);
lua_pushnumber(L, height);
return 1;
}
static int l_lovrSystemGetWindowDimensions(lua_State* L) {
lua_pushnumber(L, lovrSystemGetWindowWidth());
lua_pushnumber(L, lovrSystemGetWindowHeight());
uint32_t width, height;
lovrSystemGetWindowSize(&width, &height);
lua_pushnumber(L, width);
lua_pushnumber(L, height);
return 2;
}

View File

@ -142,6 +142,7 @@ bool gpu_texture_init(gpu_texture* texture, gpu_texture_info* info);
bool gpu_texture_init_view(gpu_texture* texture, gpu_texture_view_info* info);
void gpu_texture_destroy(gpu_texture* texture);
gpu_texture* gpu_surface_acquire(void);
void gpu_surface_resize(uint32_t width, uint32_t height);
void gpu_xr_acquire(gpu_stream* stream, gpu_texture* texture);
void gpu_xr_release(gpu_stream* stream, gpu_texture* texture);

View File

@ -169,11 +169,13 @@ static struct {
VkQueue queue;
uint32_t queueFamilyIndex;
VkSurfaceKHR surface;
VkSurfaceCapabilitiesKHR surfaceCapabilities;
VkSurfaceFormatKHR surfaceFormat;
bool swapchainValid;
VkSwapchainKHR swapchain;
VkFormat surfaceFormat;
VkSemaphore surfaceSemaphore;
uint32_t currentSurfaceTexture;
gpu_texture surfaceTextures[8];
VkSemaphore swapchainSemaphore;
uint32_t currentSwapchainTexture;
gpu_texture swapchainTextures[8];
VkPipelineCache pipelineCache;
VkDebugUtilsMessengerEXT messenger;
gpu_cache_entry renderpasses[16][4];
@ -208,6 +210,7 @@ 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 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]);
static VkImageLayout getNaturalLayout(uint32_t usage, VkImageAspectFlags aspect);
@ -738,11 +741,28 @@ void gpu_texture_destroy(gpu_texture* texture) {
gpu_release(state.memory + texture->memory);
}
gpu_texture* gpu_surface_acquire(void) {
gpu_texture* gpu_surface_acquire() {
if (!state.swapchainValid) {
return NULL;
}
gpu_tick* tick = &state.ticks[state.tick[CPU] & TICK_MASK];
state.surfaceSemaphore = tick->semaphores[0];
VK(vkAcquireNextImageKHR(state.device, state.swapchain, UINT64_MAX, state.surfaceSemaphore, VK_NULL_HANDLE, &state.currentSurfaceTexture), "Surface image acquisition failed") return NULL;
return &state.surfaceTextures[state.currentSurfaceTexture];
VkResult result = vkAcquireNextImageKHR(state.device, state.swapchain, UINT64_MAX, tick->semaphores[0], VK_NULL_HANDLE, &state.currentSwapchainTexture);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
state.currentSwapchainTexture = ~0u;
state.swapchainValid = false;
return NULL;
} else {
vcheck(result, "Failed to acquire swapchain");
}
state.swapchainSemaphore = tick->semaphores[0];
return &state.swapchainTextures[state.currentSwapchainTexture];
}
void gpu_surface_resize(uint32_t width, uint32_t height) {
createSwapchain(width, height);
}
// The barriers here are a bit lazy (oversynchronized) and can be improved
@ -1840,19 +1860,21 @@ bool gpu_init(gpu_config* config) {
uint32_t 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 + 2 <= COUNTOF(extensions), "Too many instance extensions") return gpu_destroy(), false;
extensions[extensionCount++] = "VK_KHR_get_physical_device_properties2";
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;
}
}
#endif
VkInstanceCreateInfo instanceInfo = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
#ifdef VK_KHR_portability_enumeration
.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR,
#endif
.flags = instanceFlags,
.pApplicationInfo = &(VkApplicationInfo) {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pEngineName = config->engineName,
@ -2217,68 +2239,26 @@ bool gpu_init(gpu_config* config) {
}
}
// Swapchain
if (state.surface) {
VkSurfaceCapabilitiesKHR surfaceCapabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(state.adapter, state.surface, &surfaceCapabilities);
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(state.adapter, state.surface, &state.surfaceCapabilities);
VkSurfaceFormatKHR formats[16];
VkSurfaceFormatKHR formats[32];
uint32_t formatCount = COUNTOF(formats);
VkSurfaceFormatKHR surfaceFormat = { .format = VK_FORMAT_UNDEFINED };
vkGetPhysicalDeviceSurfaceFormatsKHR(state.adapter, state.surface, &formatCount, formats);
for (uint32_t i = 0; i < formatCount; i++) {
if (formats[i].format == VK_FORMAT_R8G8B8A8_SRGB || formats[i].format == VK_FORMAT_B8G8R8A8_SRGB) {
surfaceFormat = formats[i];
state.surfaceFormat = formats[i];
break;
}
}
VK(surfaceFormat.format == VK_FORMAT_UNDEFINED ? VK_ERROR_FORMAT_NOT_SUPPORTED : VK_SUCCESS, "No supported surface formats") return gpu_destroy(), false;
state.surfaceFormat = surfaceFormat.format;
VK(state.surfaceFormat.format == VK_FORMAT_UNDEFINED ? VK_ERROR_FORMAT_NOT_SUPPORTED : VK_SUCCESS, "No supported surface formats") return gpu_destroy(), false;
VkSwapchainCreateInfoKHR swapchainInfo = {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = state.surface,
.minImageCount = surfaceCapabilities.minImageCount,
.imageFormat = surfaceFormat.format,
.imageColorSpace = surfaceFormat.colorSpace,
.imageExtent = surfaceCapabilities.currentExtent,
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
.presentMode = state.config.vk.vsync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR,
.clipped = VK_TRUE
};
uint32_t width = state.surfaceCapabilities.currentExtent.width;
uint32_t height = state.surfaceCapabilities.currentExtent.height;
VK(vkCreateSwapchainKHR(state.device, &swapchainInfo, NULL, &state.swapchain), "Swapchain creation failed") return gpu_destroy(), false;
uint32_t imageCount;
VkImage images[COUNTOF(state.surfaceTextures)];
VK(vkGetSwapchainImagesKHR(state.device, state.swapchain, &imageCount, NULL), "Failed to get swapchain images") return gpu_destroy(), false;
VK(imageCount > COUNTOF(images) ? VK_ERROR_TOO_MANY_OBJECTS : VK_SUCCESS, "Failed to get swapchain images") return gpu_destroy(), false;
VK(vkGetSwapchainImagesKHR(state.device, state.swapchain, &imageCount, images), "Failed to get swapchain images") return gpu_destroy(), false;
for (uint32_t i = 0; i < imageCount; i++) {
gpu_texture* texture = &state.surfaceTextures[i];
texture->handle = images[i];
texture->aspect = VK_IMAGE_ASPECT_COLOR_BIT;
texture->layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
texture->samples = 1;
texture->memory = ~0u;
texture->layers = 1;
texture->format = GPU_FORMAT_SURFACE;
texture->srgb = true;
gpu_texture_view_info view = {
.source = texture,
.type = GPU_TEXTURE_2D
};
CHECK(gpu_texture_init_view(texture, &view), "Swapchain texture view creation failed") return gpu_destroy(), false;
}
createSwapchain(width, height);
}
// Ticks
@ -2336,7 +2316,7 @@ bool gpu_init(gpu_config* config) {
VK(vkCreatePipelineCache(state.device, &cacheInfo, NULL, &state.pipelineCache), "Pipeline cache creation failed") return gpu_destroy(), false;
state.tick[CPU] = COUNTOF(state.ticks) - 1;
state.currentSurfaceTexture = ~0u;
state.currentSwapchainTexture = ~0u;
return true;
}
@ -2369,8 +2349,8 @@ void gpu_destroy(void) {
for (uint32_t i = 0; i < COUNTOF(state.memory); i++) {
if (state.memory[i].handle) vkFreeMemory(state.device, state.memory[i].handle, NULL);
}
for (uint32_t i = 0; i < COUNTOF(state.surfaceTextures); i++) {
if (state.surfaceTextures[i].view) vkDestroyImageView(state.device, state.surfaceTextures[i].view, NULL);
for (uint32_t i = 0; i < COUNTOF(state.swapchainTextures); i++) {
if (state.swapchainTextures[i].view) vkDestroyImageView(state.device, state.swapchainTextures[i].view, NULL);
}
if (state.swapchain) vkDestroySwapchainKHR(state.device, state.swapchain, NULL);
if (state.device) vkDestroyDevice(state.device, NULL);
@ -2409,15 +2389,15 @@ void gpu_submit(gpu_stream** streams, uint32_t count) {
VkSubmitInfo submit = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = !!state.surfaceSemaphore,
.pWaitSemaphores = &state.surfaceSemaphore,
.waitSemaphoreCount = !!state.swapchainSemaphore,
.pWaitSemaphores = &state.swapchainSemaphore,
.pWaitDstStageMask = &waitStage,
.commandBufferCount = count,
.pCommandBuffers = commands
};
VK(vkQueueSubmit(state.queue, 1, &submit, tick->fence), "Queue submit failed") {}
state.surfaceSemaphore = VK_NULL_HANDLE;
state.swapchainSemaphore = VK_NULL_HANDLE;
}
void gpu_present() {
@ -2437,11 +2417,18 @@ void gpu_present() {
.pWaitSemaphores = &semaphore,
.swapchainCount = 1,
.pSwapchains = &state.swapchain,
.pImageIndices = &state.currentSurfaceTexture
.pImageIndices = &state.currentSwapchainTexture
};
VK(vkQueuePresentKHR(state.queue, &present), "Queue present failed") {}
state.currentSurfaceTexture = ~0u;
VkResult result = vkQueuePresentKHR(state.queue, &present);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
state.swapchainValid = false;
} else {
vcheck(result, "Queue present failed");
}
state.currentSwapchainTexture = ~0u;
}
bool gpu_is_complete(uint32_t tick) {
@ -2599,6 +2586,76 @@ static void expunge() {
}
}
static void createSwapchain(uint32_t width, uint32_t height) {
if (width == 0 || height == 0) {
state.swapchainValid = false;
return;
}
VkSwapchainKHR oldSwapchain = state.swapchain;
if (oldSwapchain) {
vkDeviceWaitIdle(state.device);
}
VkSwapchainCreateInfoKHR swapchainInfo = {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = state.surface,
.minImageCount = state.surfaceCapabilities.minImageCount,
.imageFormat = state.surfaceFormat.format,
.imageColorSpace = state.surfaceFormat.colorSpace,
.imageExtent = { width, height },
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
.presentMode = state.config.vk.vsync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR,
.clipped = VK_TRUE,
.oldSwapchain = oldSwapchain
};
VK(vkCreateSwapchainKHR(state.device, &swapchainInfo, NULL, &state.swapchain), "Swapchain creation failed") return;
if (oldSwapchain) {
for (uint32_t i = 0; i < COUNTOF(state.swapchainTextures); i++) {
if (state.swapchainTextures[i].view) {
vkDestroyImageView(state.device, state.swapchainTextures[i].view, NULL);
}
}
memset(state.swapchainTextures, 0, sizeof(state.swapchainTextures));
vkDestroySwapchainKHR(state.device, oldSwapchain, NULL);
}
uint32_t imageCount;
VkImage images[COUNTOF(state.swapchainTextures)];
VK(vkGetSwapchainImagesKHR(state.device, state.swapchain, &imageCount, NULL), "Failed to get swapchain images") return;
VK(imageCount > COUNTOF(images) ? VK_ERROR_TOO_MANY_OBJECTS : VK_SUCCESS, "Failed to get swapchain images") return;
VK(vkGetSwapchainImagesKHR(state.device, state.swapchain, &imageCount, images), "Failed to get swapchain images") return;
for (uint32_t i = 0; i < imageCount; i++) {
gpu_texture* texture = &state.swapchainTextures[i];
texture->handle = images[i];
texture->aspect = VK_IMAGE_ASPECT_COLOR_BIT;
texture->layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
texture->samples = 1;
texture->memory = ~0u;
texture->layers = 1;
texture->format = GPU_FORMAT_SURFACE;
texture->srgb = true;
gpu_texture_view_info view = {
.source = texture,
.type = GPU_TEXTURE_2D
};
CHECK(gpu_texture_init_view(texture, &view), "Swapchain texture view creation failed") return;
}
state.swapchainValid = true;
}
// Ugliness until we can use dynamic rendering
static VkRenderPass getCachedRenderPass(gpu_pass_info* pass, bool exact) {
bool depth = pass->depth.layout != VK_IMAGE_LAYOUT_UNDEFINED;
@ -2682,7 +2739,7 @@ static VkRenderPass getCachedRenderPass(gpu_pass_info* pass, bool exact) {
for (uint32_t i = 0; i < count; i++) {
references[i].attachment = i;
references[i].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
bool surface = pass->color[i].format == state.surfaceFormat;
bool surface = pass->color[i].format == state.surfaceFormat.format; // FIXME
bool discard = surface || pass->color[i].load != GPU_LOAD_OP_KEEP;
attachments[i] = (VkAttachmentDescription) {
.format = pass->color[i].format,
@ -2699,7 +2756,7 @@ static VkRenderPass getCachedRenderPass(gpu_pass_info* pass, bool exact) {
uint32_t index = count + i;
references[index].attachment = index;
references[index].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
bool surface = pass->color[i].format == state.surfaceFormat;
bool surface = pass->color[i].format == state.surfaceFormat.format; // FIXME
attachments[index] = (VkAttachmentDescription) {
.format = pass->color[i].format,
.samples = VK_SAMPLE_COUNT_1_BIT,
@ -2877,7 +2934,7 @@ static VkFormat convertFormat(gpu_texture_format format, int colorspace) {
};
if (format == GPU_FORMAT_SURFACE) {
return state.surfaceFormat;
return state.surfaceFormat.format;
}
return formats[format][colorspace];

View File

@ -128,7 +128,7 @@ typedef enum {
typedef void fn_gl_proc(void);
typedef void fn_quit(void);
typedef void fn_focus(bool focused);
typedef void fn_resize(int width, int height);
typedef void fn_resize(uint32_t width, uint32_t height);
typedef void fn_key(os_button_action action, os_key key, uint32_t scancode, bool repeat);
typedef void fn_text(uint32_t codepoint);
typedef void fn_permission(os_permission permission, bool granted);
@ -157,8 +157,8 @@ void os_on_permission(fn_permission* callback);
bool os_window_open(const os_window_config* config);
bool os_window_is_open(void);
void os_window_get_size(int* width, int* height);
void os_window_get_fbsize(int* width, int* height);
void os_window_get_size(uint32_t* width, uint32_t* height);
void os_window_get_fbsize(uint32_t* width, uint32_t* height);
size_t os_get_home_directory(char* buffer, size_t size);
size_t os_get_data_directory(char* buffer, size_t size);

View File

@ -319,12 +319,12 @@ bool os_window_is_open() {
return false;
}
void os_window_get_size(int* width, int* height) {
void os_window_get_size(uint32_t* width, uint32_t* height) {
if (width) *width = 0;
if (height) *height = 0;
}
void os_window_get_fbsize(int* width, int* height) {
void os_window_get_fbsize(uint32_t* width, uint32_t* height) {
*width = 0;
*height = 0;
}

View File

@ -248,21 +248,27 @@ bool os_window_is_open() {
return glfwState.window;
}
void os_window_get_size(int* width, int* height) {
void os_window_get_size(uint32_t* width, uint32_t* height) {
if (glfwState.window) {
glfwGetWindowSize(glfwState.window, width, height);
int w, h;
glfwGetWindowSize(glfwState.window, &w, &h);
*width = w;
*height = h;
} else {
if (*width) *width = 0;
if (*height) *height = 0;
*width = 0;
*height = 0;
}
}
void os_window_get_fbsize(int* width, int* height) {
void os_window_get_fbsize(uint32_t* width, uint32_t* height) {
if (glfwState.window) {
glfwGetFramebufferSize(glfwState.window, width, height);
int w, h;
glfwGetFramebufferSize(glfwState.window, &w, &h);
*width = w;
*height = h;
} else {
if (*width) *width = 0;
if (*height) *height = 0;
*width = 0;
*height = 0;
}
}

View File

@ -19,10 +19,10 @@ static struct {
os_mouse_mode mouseMode;
long mouseX;
long mouseY;
int width;
int height;
int framebufferWidth;
int framebufferHeight;
uint32_t width;
uint32_t height;
uint32_t framebufferWidth;
uint32_t framebufferHeight;
} state;
static const char* onBeforeUnload(int type, const void* unused, void* userdata) {
@ -45,7 +45,7 @@ static EM_BOOL onResize(int type, const EmscriptenUiEvent* data, void* userdata)
int newWidth, newHeight;
emscripten_get_canvas_element_size(CANVAS, &newWidth, &newHeight);
if (state.width != newWidth || state.height != newHeight) {
if (state.width != (uint32_t) newWidth || state.height != (uint32_t) newHeight) {
state.width = newWidth;
state.height = newHeight;
if (state.onWindowResize) {
@ -298,12 +298,12 @@ bool os_window_is_open() {
return state.context > 0;
}
void os_window_get_size(int* width, int* height) {
void os_window_get_size(uint32_t* width, uint32_t* height) {
*width = state.width;
*height = state.height;
}
void os_window_get_fbsize(int* width, int* height) {
void os_window_get_fbsize(uint32_t* width, uint32_t* height) {
*width = state.framebufferWidth;
*height = state.framebufferHeight;
}

View File

@ -3,6 +3,7 @@
#include "data/image.h"
#include "data/modelData.h"
#include "data/rasterizer.h"
#include "event/event.h"
#include "headset/headset.h"
#include "math/math.h"
#include "core/gpu.h"
@ -426,6 +427,7 @@ static void trackTexture(Pass* pass, Texture* texture, gpu_phase phase, gpu_cach
static void trackMaterial(Pass* pass, Material* material, gpu_phase phase, gpu_cache cache);
static void updateModelTransforms(Model* model, uint32_t nodeIndex, float* parent);
static void checkShaderFeatures(uint32_t* features, uint32_t count);
static void onResize(uint32_t width, uint32_t height);
static void onMessage(void* context, const char* message, bool severe);
// Entry
@ -450,6 +452,7 @@ bool lovrGraphicsInit(GraphicsConfig* config) {
gpu.vk.cacheSize = config->cacheSize;
if (os_window_is_open()) {
os_on_resize(onResize);
gpu.vk.getInstanceExtensions = os_vk_get_instance_extensions;
gpu.vk.createSurface = os_vk_create_surface;
gpu.vk.surface = true;
@ -632,17 +635,12 @@ bool lovrGraphicsInit(GraphicsConfig* config) {
state.window = malloc(sizeof(Texture));
lovrAssert(state.window, "Out of memory");
int width, height;
os_window_get_fbsize(&width, &height);
state.window->ref = 1;
state.window->gpu = NULL;
state.window->renderView = NULL;
state.window->info = (TextureInfo) {
.type = TEXTURE_2D,
.format = GPU_FORMAT_SURFACE,
.width = width,
.height = height,
.layers = 1,
.mipmaps = 1,
.samples = 1,
@ -650,6 +648,8 @@ bool lovrGraphicsInit(GraphicsConfig* config) {
.srgb = true
};
os_window_get_size(&state.window->info.width, &state.window->info.height);
state.depthFormat = config->stencil ? FORMAT_D32FS8 : FORMAT_D32F;
if (config->stencil && !lovrGraphicsIsFormatSupported(state.depthFormat, TEXTURE_FEATURE_RENDER)) {
@ -1104,7 +1104,7 @@ Buffer* lovrGraphicsGetBuffer(BufferInfo* info, void** data) {
arr_push(&state.scratchBufferHandles, handles);
}
uint32_t index = state.scratchBufferIndex++;
size_t index = state.scratchBufferIndex++;
Buffer* buffer = &state.scratchBuffers.data[index / BUFFERS_PER_CHUNK][index % BUFFERS_PER_CHUNK];
buffer->ref = 1;
@ -1191,8 +1191,14 @@ void lovrBufferClear(Buffer* buffer, uint32_t offset, uint32_t size) {
Texture* lovrGraphicsGetWindowTexture() {
if (!state.window->gpu) {
beginFrame();
state.window->gpu = gpu_surface_acquire();
state.window->renderView = state.window->gpu;
// Window texture may be unavailable during a resize
if (!state.window->gpu) {
return NULL;
}
}
return state.window;
@ -3209,10 +3215,17 @@ static void lovrPassCheckValid(Pass* pass) {
Pass* lovrGraphicsGetWindowPass() {
if (!state.windowPass && state.window) {
Texture* window = lovrGraphicsGetWindowTexture();
// The window texture (and therefore the window pass) may become unavailable during a resize
if (!window) {
return NULL;
}
PassInfo info = {
.type = PASS_RENDER,
.canvas.count = 1,
.canvas.textures[0] = state.window,
.canvas.textures[0] = window,
.canvas.depth.format = state.depthFormat,
.canvas.samples = state.config.antialias ? 4 : 1,
.label = "Window"
@ -3333,10 +3346,6 @@ Pass* lovrGraphicsGetPass(PassInfo* info) {
};
for (uint32_t i = 0; i < canvas->count; i++) {
if (canvas->textures[i] == state.window) {
canvas->textures[i] = lovrGraphicsGetWindowTexture(); // Make sure swapchain handle is updated
}
if (t->samples == 1 && canvas->samples > 1) {
scratchTextureInfo.format = canvas->textures[i]->info.format;
scratchTextureInfo.srgb = canvas->textures[i]->info.srgb;
@ -5891,6 +5900,19 @@ static void checkShaderFeatures(uint32_t* features, uint32_t count) {
}
}
static void onResize(uint32_t width, uint32_t height) {
state.window->info.width = width;
state.window->info.height = height;
gpu_surface_resize(width, height);
lovrEventPush((Event) {
.type = EVENT_RESIZE,
.data.resize.width = width,
.data.resize.height = height
});
}
static void onMessage(void* context, const char* message, bool severe) {
if (severe) {
lovrThrow("GPU error: %s", message);

View File

@ -84,10 +84,7 @@ static double desktop_getDeltaTime(void) {
}
static void desktop_getDisplayDimensions(uint32_t* width, uint32_t* height) {
int w, h;
os_window_get_fbsize(&w, &h);
*width = (uint32_t) w;
*height = (uint32_t) h;
os_window_get_fbsize(width, height);
}
static uint32_t desktop_getViewCount(void) {
@ -244,10 +241,10 @@ static double desktop_update(void) {
float turnspeed = 3.f * (float) dt;
float damping = MAX(1.f - 20.f * (float) dt, 0);
int width, height;
double mx, my;
os_window_get_fbsize(&width, &height);
uint32_t width, height;
os_get_mouse_position(&mx, &my);
os_window_get_fbsize(&width, &height);
double aspect = (width > 0 && height > 0) ? ((double) width / height) : 1.;

View File

@ -798,6 +798,9 @@ void lovrShapeDestroyData(Shape* shape) {
dGeomTriMeshDataDestroy(dataID);
free(shape->vertices);
free(shape->indices);
} else if (shape->type == SHAPE_TERRAIN) {
dHeightfieldDataID dataID = dGeomHeightfieldGetHeightfieldData(shape->id);
dGeomHeightfieldDataDestroy(dataID);
}
dGeomDestroy(shape->id);
shape->id = NULL;
@ -1049,6 +1052,20 @@ MeshShape* lovrMeshShapeCreate(int vertexCount, float* vertices, int indexCount,
return mesh;
}
TerrainShape* lovrTerrainShapeCreate(float* vertices, int widthSamples, int depthSamples, float horizontalScale, float verticalScale) {
const float thickness = 10.f;
TerrainShape* terrain = calloc(1, sizeof(TerrainShape));
lovrAssert(terrain, "Out of memory");
terrain->ref = 1;
dHeightfieldDataID dataID = dGeomHeightfieldDataCreate();
dGeomHeightfieldDataBuildSingle(dataID, vertices, 1, horizontalScale, horizontalScale,
widthSamples, depthSamples, verticalScale, 0.f, thickness, 0);
terrain->id = dCreateHeightfield(0, dataID, 1);
terrain->type = SHAPE_TERRAIN;
dGeomSetData(terrain->id, terrain);
return terrain;
}
void lovrJointDestroy(void* ref) {
Joint* joint = ref;
lovrJointDestroyData(joint);

View File

@ -18,6 +18,7 @@ typedef Shape BoxShape;
typedef Shape CapsuleShape;
typedef Shape CylinderShape;
typedef Shape MeshShape;
typedef Shape TerrainShape;
typedef Joint BallJoint;
typedef Joint DistanceJoint;
@ -131,6 +132,7 @@ typedef enum {
SHAPE_CAPSULE,
SHAPE_CYLINDER,
SHAPE_MESH,
SHAPE_TERRAIN,
} ShapeType;
void lovrShapeDestroy(void* ref);
@ -171,6 +173,7 @@ float lovrCylinderShapeGetLength(CylinderShape* cylinder);
void lovrCylinderShapeSetLength(CylinderShape* cylinder, float length);
MeshShape* lovrMeshShapeCreate(int vertexCount, float vertices[], int indexCount, uint32_t indices[]);
TerrainShape* lovrTerrainShapeCreate(float* vertices, int widthSamples, int depthSamples, float horizontalScale, float verticalScale);
// These tokens need to exist for Lua bindings
#define lovrSphereShapeDestroy lovrShapeDestroy
@ -178,6 +181,7 @@ MeshShape* lovrMeshShapeCreate(int vertexCount, float vertices[], int indexCount
#define lovrCapsuleShapeDestroy lovrShapeDestroy
#define lovrCylinderShapeDestroy lovrShapeDestroy
#define lovrMeshShapeDestroy lovrShapeDestroy
#define lovrTerrainShapeDestroy lovrShapeDestroy
// Joints

View File

@ -6,8 +6,6 @@
static struct {
bool initialized;
int windowWidth;
int windowHeight;
bool pressedKeys[KEY_COUNT];
} state;
@ -45,11 +43,6 @@ static void onQuit(void) {
});
}
static void onResize(int width, int height) {
state.windowWidth = width;
state.windowHeight = height;
}
bool lovrSystemInit() {
if (state.initialized) return false;
os_on_key(onKey);
@ -85,25 +78,19 @@ void lovrSystemRequestPermission(Permission permission) {
void lovrSystemOpenWindow(os_window_config* window) {
lovrAssert(os_window_open(window), "Could not open window");
os_on_resize(onResize);
os_on_quit(onQuit);
os_window_get_fbsize(&state.windowWidth, &state.windowHeight);
}
bool lovrSystemIsWindowOpen() {
return os_window_is_open();
}
uint32_t lovrSystemGetWindowWidth() {
return state.windowWidth;
}
uint32_t lovrSystemGetWindowHeight() {
return state.windowHeight;
void lovrSystemGetWindowSize(uint32_t* width, uint32_t* height) {
os_window_get_fbsize(width, height);
}
float lovrSystemGetWindowDensity() {
int width, height, fbwidth, fbheight;
uint32_t width, height, fbwidth, fbheight;
os_window_get_size(&width, &height);
os_window_get_fbsize(&fbwidth, &fbheight);
return (width == 0 || fbwidth == 0) ? 0.f : (float) fbwidth / width;

View File

@ -16,7 +16,6 @@ uint32_t lovrSystemGetCoreCount(void);
void lovrSystemRequestPermission(Permission permission);
void lovrSystemOpenWindow(struct os_window_config* config);
bool lovrSystemIsWindowOpen(void);
uint32_t lovrSystemGetWindowWidth(void);
uint32_t lovrSystemGetWindowHeight(void);
void lovrSystemGetWindowSize(uint32_t* width, uint32_t* height);
float lovrSystemGetWindowDensity(void);
bool lovrSystemIsKeyDown(int keycode);