Make Pass a regular object;

It uses newPass instead of getPass.  Temporary objects had lifetime
issues that were nearly impossible to solve.  And normal objects are
easier to understand because they behave like all other LÖVR objects.

However, Pass commands are not retained from frame to frame.  Pass
objects must be re-recorded before every submit, and must be reset
before being recorded again.

Pass objects now provide a natural place for render-pass-related info
like clears and texture handles.  They also allow more information to be
precomputed which should reduce overhead a bit.

It is now possible to request a stencil buffer and antialiasing on the
window and headset textures, via conf.lua.

lovr.graphics.setBackground should instead set the clear color on the
window pass.  Though we're still going to try to do spherical harmonics
in some capacity.

There are still major issues with OpenXR that are going to be ironed
out, and the desktop driver hasn't been converted over to the new
headset Pass system yet.  So lovr.headset integration is a bit WIP.
This commit is contained in:
bjorn 2022-08-02 22:00:11 -07:00
parent 289f08b0df
commit 4ee092e81b
13 changed files with 990 additions and 686 deletions

View File

@ -24,13 +24,16 @@ function lovr.boot()
},
graphics = {
debug = false,
vsync = false
vsync = false,
stencil = false,
antialias = true
},
headset = {
drivers = { 'openxr', 'webxr', 'desktop' },
supersample = false,
offset = 1.7,
msaa = 4,
stencil = false,
antialias = true,
overlay = false
},
math = {
@ -116,20 +119,16 @@ function lovr.run()
if lovr.headset then dt = lovr.headset.update() end
if lovr.update then lovr.update(dt) end
if lovr.graphics then
local headset = lovr.headset and lovr.headset.getTexture()
if headset then
local pass = lovr.graphics.getPass('render', headset)
local near, far = lovr.headset.getClipDistance()
for i = 1, lovr.headset.getViewCount() do
pass:setViewPose(i, lovr.headset.getViewPose(i))
local left, right, up, down = lovr.headset.getViewAngles(i)
pass:setProjection(i, left, right, up, down, near, far)
if lovr.headset then
local pass = lovr.headset.getPass()
if pass then
local skip = lovr.draw and lovr.draw(pass)
if not skip then lovr.graphics.submit(pass) end
end
local skip = lovr.draw and lovr.draw(pass)
if not skip then lovr.graphics.submit(pass) end
end
if lovr.system.isWindowOpen() then
local pass = lovr.graphics.getPass('render', 'window')
local pass = lovr.graphics.getWindowPass()
pass:reset()
local skip = lovr.mirror(pass)
if not skip then lovr.graphics.submit(pass) end
end
@ -145,13 +144,7 @@ function lovr.mirror(pass)
if texture then
pass:fill(texture)
else
local near, far = lovr.headset.getClipDistance()
for i = 1, lovr.headset.getViewCount() do
pass:setViewPose(i, lovr.headset.getViewPose(i))
local left, right, up, down = lovr.headset.getViewAngles(i)
pass:setProjection(i, left, right, up, down, near, far)
end
return lovr.draw and lovr.draw(pass)
return true
end
else
return lovr.draw and lovr.draw(pass)

View File

@ -14,7 +14,7 @@ function lovr.load()
return
end
lovr.graphics.setBackground(0x20232c)
lovr.graphics.getWindowPass():setClear(0x20232c)
--[=[
logo = lovr.graphics.newShader([[

View File

@ -464,18 +464,16 @@ static Canvas luax_checkcanvas(lua_State* L, int index) {
.samples = 4
};
for (uint32_t i = 0; i < 4; i++) {
lovrGraphicsGetBackground(canvas.clears[i]); // srgb conversion here does not spark joy
}
if (lua_type(L, index) == LUA_TSTRING && !strcmp(lua_tostring(L, index), "window")) {
canvas.textures[0] = lovrGraphicsGetWindowTexture();
canvas.count = 1;
} else if (lua_isuserdata(L, index)) {
canvas.textures[0] = luax_checktype(L, index, Texture);
canvas.count = 1;
} else if (!lua_istable(L, index)) {
luax_typeerror(L, index, "Texture or table");
} else {
for (uint32_t i = 0; i < 4; i++) {
for (uint32_t i = 0; i < 4; i++, canvas.count++) {
lua_rawgeti(L, index, i + 1);
if (lua_isnil(L, -1)) {
break;
@ -600,24 +598,59 @@ uint32_t luax_checkcomparemode(lua_State* L, int index) {
return luax_checkenum(L, index, CompareMode, "none");
}
static void luax_writeshadercache(void) {
size_t size;
lovrGraphicsGetShaderCache(NULL, &size);
if (size == 0) {
return;
}
void* data = malloc(size);
if (!data) {
return;
}
lovrGraphicsGetShaderCache(data, &size);
if (size > 0) {
luax_writefile(".lovrshadercache", data, size);
}
free(data);
}
static int l_lovrGraphicsInit(lua_State* L) {
bool debug = false;
bool vsync = false;
GraphicsConfig config = {
.debug = false,
.vsync = false,
.stencil = false,
.antialias = true
};
luax_pushconf(L);
lua_getfield(L, -1, "graphics");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "debug");
debug = lua_toboolean(L, -1);
config.debug = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "vsync");
vsync = lua_toboolean(L, -1);
config.vsync = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "stencil");
config.stencil = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "antialias");
config.antialias = lua_toboolean(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 2);
if (lovrGraphicsInit(debug, vsync)) {
if (lovrGraphicsInit(&config)) {
luax_atexit(L, lovrGraphicsDestroy);
}
@ -770,21 +803,10 @@ static int l_lovrGraphicsIsFormatSupported(lua_State* L) {
return 1;
}
static int l_lovrGraphicsGetBackground(lua_State* L) {
float color[4];
lovrGraphicsGetBackground(color);
lua_pushnumber(L, color[0]);
lua_pushnumber(L, color[1]);
lua_pushnumber(L, color[2]);
lua_pushnumber(L, color[3]);
return 4;
}
static int l_lovrGraphicsSetBackground(lua_State* L) {
float color[4];
luax_readcolor(L, 1, color);
lovrGraphicsSetBackground(color);
return 0;
static int l_lovrGraphicsGetWindowPass(lua_State* L) {
Pass* pass = lovrGraphicsGetWindowPass();
luax_pushtype(L, Pass, pass);
return 1;
}
static int l_lovrGraphicsGetDefaultFont(lua_State* L) {
@ -1425,13 +1447,24 @@ static int l_lovrGraphicsNewTally(lua_State* L) {
return 1;
}
static int l_lovrGraphicsGetPass(lua_State* L) {
PassInfo info;
info.type = luax_checkenum(L, 1, PassType, NULL);
if (info.type == PASS_RENDER) info.canvas = luax_checkcanvas(L, 2);
info.label = lua_tostring(L, info.type == PASS_RENDER ? 3 : 2);
static int l_lovrGraphicsNewPass(lua_State* L) {
PassInfo info = { 0 };
Pass* pass = lovrGraphicsGetPass(&info);
info.type = luax_checkenum(L, 1, PassType, NULL);
if (info.type == PASS_RENDER) {
info.canvas = luax_checkcanvas(L, 2);
}
if (lua_istable(L, 2)) {
lua_getfield(L, 2, "label");
info.label = lua_tostring(L, -1);
lua_pop(L, 1);
} else {
info.label = NULL;
}
Pass* pass = lovrPassCreate(&info);
luax_pushtype(L, Pass, pass);
lovrRelease(pass, lovrPassDestroy);
return 1;
@ -1446,8 +1479,7 @@ static const luaL_Reg lovrGraphics[] = {
{ "getLimits", l_lovrGraphicsGetLimits },
{ "getStats", l_lovrGraphicsGetStats },
{ "isFormatSupported", l_lovrGraphicsIsFormatSupported },
{ "getBackground", l_lovrGraphicsGetBackground },
{ "setBackground", l_lovrGraphicsSetBackground },
{ "getWindowPass", l_lovrGraphicsGetWindowPass },
{ "getDefaultFont", l_lovrGraphicsGetDefaultFont },
{ "getBuffer", l_lovrGraphicsGetBuffer },
{ "newBuffer", l_lovrGraphicsNewBuffer },
@ -1459,7 +1491,7 @@ static const luaL_Reg lovrGraphics[] = {
{ "newFont", l_lovrGraphicsNewFont },
{ "newModel", l_lovrGraphicsNewModel },
{ "newTally", l_lovrGraphicsNewTally },
{ "getPass", l_lovrGraphicsGetPass },
{ "newPass", l_lovrGraphicsNewPass },
{ NULL, NULL }
};

View File

@ -16,6 +16,176 @@ static int l_lovrPassGetType(lua_State* L) {
return 1;
}
static int l_lovrPassGetWidth(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
uint32_t width = lovrPassGetWidth(pass);
lua_pushinteger(L, width);
return 1;
}
static int l_lovrPassGetHeight(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
uint32_t height = lovrPassGetHeight(pass);
lua_pushinteger(L, height);
return 1;
}
static int l_lovrPassGetDimensions(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
uint32_t width = lovrPassGetWidth(pass);
uint32_t height = lovrPassGetHeight(pass);
lua_pushinteger(L, width);
lua_pushinteger(L, height);
return 2;
}
static int l_lovrPassGetViewCount(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
uint32_t views = lovrPassGetViewCount(pass);
lua_pushinteger(L, views);
return 1;
}
static int l_lovrPassGetSampleCount(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
uint32_t samples = lovrPassGetSampleCount(pass);
lua_pushinteger(L, samples);
return 1;
}
static int l_lovrPassGetTarget(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
int count = (int) lovrPassGetInfo(pass)->canvas.count;
Texture *color[4], *depth;
lovrPassGetTarget(pass, color, &depth);
lua_createtable(L, count, !!depth);
for (int i = 0; i < count; i++) {
luax_pushtype(L, Texture, color[i]);
lua_rawseti(L, -2, i + 1);
}
if (depth) {
luax_pushtype(L, Texture, depth);
lua_setfield(L, -2, "depth");
}
return 1;
}
static int l_lovrPassSetTarget(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
int count = (int) lovrPassGetInfo(pass)->canvas.count;
Texture* color[4];
Texture* depth = NULL;
if (lua_istable(L, 2)) {
for (int i = 0; i < count; i++) {
lua_rawgeti(L, 2, i + 1);
color[i] = luax_totype(L, -1, Texture);
lovrAssert(color[i], "Expected a Texture for color target #%d", i + 1);
lua_pop(L, 1);
}
lua_getfield(L, 2, "depth");
depth = luax_totype(L, -1, Texture);
lua_pop(L, 1);
} else {
for (int i = 0; i < count; i++) {
color[i] = luax_totype(L, -1, Texture);
lovrAssert(color[i], "Expected a Texture for color target #%d", i + 1);
}
}
lovrPassSetTarget(pass, color, depth);
return 0;
}
static int l_lovrPassGetClear(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
int count = (int) lovrPassGetInfo(pass)->canvas.count;
float color[4][4];
float depth;
uint8_t stencil;
lovrPassGetClear(pass, color, &depth, &stencil);
lua_createtable(L, count, 2);
for (int i = 0; i < count; i++) {
lua_createtable(L, 4, 0);
for (int j = 0; j < 4; j++) {
lua_pushnumber(L, color[i][j]);
lua_rawseti(L, -2, j + 1);
}
lua_rawseti(L, -2, i + 1);
}
lua_pushnumber(L, depth);
lua_setfield(L, -2, "depth");
lua_pushinteger(L, stencil);
lua_setfield(L, -2, "stencil");
return 1;
}
static int l_lovrPassSetClear(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
int count = (int) lovrPassGetInfo(pass)->canvas.count;
float color[4][4];
float depth;
uint8_t stencil;
lovrPassGetClear(pass, color, &depth, &stencil);
bool table = lua_istable(L, 2);
if (count == 1) {
if (table && luax_len(L, 2) == 1) {
lua_rawgeti(L, 2, 1);
luax_optcolor(L, -1, color[0]);
lua_pop(L, 1);
} else {
luax_readcolor(L, 2, color[0]);
}
} else if (lua_gettop(L) > 2) {
for (int i = 0; i < count; i++) {
if (!lua_isnoneornil(L, 2 + i)) {
luax_optcolor(L, 2 + i, color[i]);
}
}
} else {
for (int i = 0; i < count; i++) {
lua_rawgeti(L, 2, i + 1);
if (!lua_isnil(L, -1)) luax_optcolor(L, -1, color[i]);
lua_pop(L, 1);
}
}
if (table) {
lua_getfield(L, 2, "depth");
depth = luax_optfloat(L, -1, depth);
lua_pop(L, 1);
lua_getfield(L, 2, "stencil");
stencil = luaL_optinteger(L, -1, stencil);
stencil = CLAMP(stencil, 0, 255);
lua_pop(L, 1);
}
lovrPassSetClear(pass, color, depth, stencil);
return 0;
}
static int l_lovrPassReset(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
lovrPassReset(pass);
return 0;
}
static int l_lovrPassGetViewPose(lua_State* L) {
Pass* pass = luax_checktype(L, 1, Pass);
uint32_t view = luaL_checkinteger(L, 2) - 1;
@ -927,6 +1097,18 @@ static int l_lovrPassTock(lua_State* L) {
const luaL_Reg lovrPass[] = {
{ "getType", l_lovrPassGetType },
{ "getWidth", l_lovrPassGetWidth },
{ "getHeight", l_lovrPassGetHeight },
{ "getDimensions", l_lovrPassGetDimensions },
{ "getViewCount", l_lovrPassGetViewCount },
{ "getSampleCount", l_lovrPassGetSampleCount },
{ "getTarget", l_lovrPassGetTarget },
{ "setTarget", l_lovrPassSetTarget },
{ "getClear", l_lovrPassGetClear },
{ "setClear", l_lovrPassSetClear },
{ "reset", l_lovrPassReset },
{ "getViewPose", l_lovrPassGetViewPose },
{ "setViewPose", l_lovrPassSetViewPose },

View File

@ -503,6 +503,12 @@ static int l_lovrHeadsetGetTexture(lua_State* L) {
return 1;
}
static int l_lovrHeadsetGetPass(lua_State* L) {
Pass* pass = lovrHeadsetInterface->getPass();
luax_pushtype(L, Pass, pass);
return 1;
}
static int l_lovrHeadsetSubmit(lua_State* L) {
lovrHeadsetInterface->submit();
return 0;
@ -591,6 +597,7 @@ static const luaL_Reg lovrHeadset[] = {
{ "newModel", l_lovrHeadsetNewModel },
{ "animate", l_lovrHeadsetAnimate },
{ "getTexture", l_lovrHeadsetGetTexture },
{ "getPass", l_lovrHeadsetGetPass },
{ "submit", l_lovrHeadsetSubmit },
{ "isFocused", l_lovrHeadsetIsFocused },
{ "update", l_lovrHeadsetUpdate },
@ -604,12 +611,17 @@ int luaopen_lovr_headset(lua_State* L) {
lua_newtable(L);
luax_register(L, lovrHeadset);
size_t driverCount = 0;
HeadsetDriver drivers[8];
float supersample = 1.f;
float offset = 1.7f;
int msaa = 4;
bool overlay = false;
HeadsetConfig config = {
.drivers = drivers,
.driverCount = 0,
.supersample = 1.f,
.offset = 1.7f,
.stencil = false,
.antialias = true,
.overlay = false
};
luax_pushconf(L);
if (lua_istable(L, -1)) {
@ -621,8 +633,8 @@ int luaopen_lovr_headset(lua_State* L) {
int n = luax_len(L, -1);
for (int i = 0; i < n; i++) {
lua_rawgeti(L, -1, i + 1);
drivers[driverCount++] = luax_checkenum(L, -1, HeadsetDriver, NULL);
lovrAssert(driverCount < COUNTOF(drivers), "Too many headset drivers specified in conf.lua");
config.drivers[config.driverCount++] = luax_checkenum(L, -1, HeadsetDriver, NULL);
lovrAssert(config.driverCount < COUNTOF(drivers), "Too many headset drivers specified in conf.lua");
lua_pop(L, 1);
}
lua_pop(L, 1);
@ -630,25 +642,30 @@ int luaopen_lovr_headset(lua_State* L) {
// Supersample
lua_getfield(L, -1, "supersample");
if (lua_type(L, -1) == LUA_TBOOLEAN) {
supersample = lua_toboolean(L, -1) ? 2.f : 1.f;
config.supersample = lua_toboolean(L, -1) ? 2.f : 1.f;
} else {
supersample = luax_optfloat(L, -1, 1.f);
config.supersample = luax_optfloat(L, -1, 1.f);
}
lua_pop(L, 1);
// Offset
lua_getfield(L, -1, "offset");
offset = luax_optfloat(L, -1, 1.7f);
config.offset = luax_optfloat(L, -1, 1.7f);
lua_pop(L, 1);
// MSAA
lua_getfield(L, -1, "msaa");
msaa = luaL_optinteger(L, -1, 4);
// 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);
// Overlay
lua_getfield(L, -1, "overlay");
overlay = lua_toboolean(L, -1);
config.overlay = lua_toboolean(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
@ -656,6 +673,6 @@ int luaopen_lovr_headset(lua_State* L) {
lua_pop(L, 1);
luax_atexit(L, lovrHeadsetDestroy);
lovrHeadsetInit(drivers, driverCount, supersample, offset, msaa, overlay);
lovrHeadsetInit(&config);
return 1;
}

View File

@ -9,6 +9,7 @@ typedef struct gpu_layout gpu_layout;
typedef struct gpu_shader gpu_shader;
typedef struct gpu_bundle_pool gpu_bundle_pool;
typedef struct gpu_bundle gpu_bundle;
typedef struct gpu_pass gpu_pass;
typedef struct gpu_pipeline gpu_pipeline;
typedef struct gpu_tally gpu_tally;
typedef struct gpu_stream gpu_stream;
@ -20,6 +21,7 @@ size_t gpu_sizeof_layout(void);
size_t gpu_sizeof_shader(void);
size_t gpu_sizeof_bundle_pool(void);
size_t gpu_sizeof_bundle(void);
size_t gpu_sizeof_pass(void);
size_t gpu_sizeof_pipeline(void);
size_t gpu_sizeof_tally(void);
@ -264,6 +266,48 @@ bool gpu_bundle_pool_init(gpu_bundle_pool* pool, gpu_bundle_pool_info* info);
void gpu_bundle_pool_destroy(gpu_bundle_pool* pool);
void gpu_bundle_write(gpu_bundle** bundles, gpu_bundle_info* info, uint32_t count);
// Pass
typedef enum {
GPU_LOAD_OP_CLEAR,
GPU_LOAD_OP_DISCARD,
GPU_LOAD_OP_KEEP
} gpu_load_op;
typedef enum {
GPU_SAVE_OP_KEEP,
GPU_SAVE_OP_DISCARD
} gpu_save_op;
typedef struct {
gpu_texture_format format;
gpu_load_op load;
gpu_save_op save;
uint8_t usage;
uint8_t resolveUsage;
bool srgb;
} gpu_pass_color_info;
typedef struct {
gpu_texture_format format;
gpu_load_op load;
gpu_save_op save;
uint32_t usage;
} gpu_pass_depth_info;
typedef struct {
gpu_pass_color_info color[4];
gpu_pass_depth_info depth;
uint32_t count;
uint32_t views;
uint32_t samples;
bool resolve;
const char* label;
} gpu_pass_info;
bool gpu_pass_init(gpu_pass* pass, gpu_pass_info* info);
void gpu_pass_destroy(gpu_pass* pass);
// Pipeline
typedef enum {
@ -417,13 +461,12 @@ typedef struct {
} gpu_blend_state;
typedef struct {
gpu_texture_format format;
bool srgb;
gpu_blend_state blend;
uint8_t mask;
} gpu_color_state;
typedef struct {
gpu_pass* pass;
gpu_shader* shader;
gpu_shader_flag* flags;
uint32_t flagCount;
@ -468,37 +511,23 @@ void gpu_tally_destroy(gpu_tally* tally);
// Stream
typedef enum {
GPU_LOAD_OP_LOAD,
GPU_LOAD_OP_CLEAR,
GPU_LOAD_OP_DISCARD
} gpu_load_op;
typedef enum {
GPU_SAVE_OP_SAVE,
GPU_SAVE_OP_DISCARD
} gpu_save_op;
typedef struct {
gpu_texture* texture;
gpu_texture* resolve;
gpu_load_op load;
gpu_save_op save;
float clear[4];
} gpu_color_attachment;
typedef struct {
gpu_texture* texture;
gpu_load_op load, stencilLoad;
gpu_save_op save, stencilSave;
struct { float depth; uint8_t stencil; } clear;
} gpu_depth_attachment;
typedef struct {
gpu_pass* pass;
gpu_color_attachment color[4];
gpu_depth_attachment depth;
uint32_t size[2];
} gpu_canvas;
} gpu_render_target;
typedef enum {
GPU_INDEX_U16,
@ -543,9 +572,9 @@ typedef struct {
gpu_cache clear;
} gpu_barrier;
gpu_stream* gpu_stream_begin(const char* label);
gpu_stream* gpu_stream_begin();
void gpu_stream_end(gpu_stream* stream);
void gpu_render_begin(gpu_stream* stream, gpu_canvas* canvas);
void gpu_render_begin(gpu_stream* stream, gpu_render_target* target);
void gpu_render_end(gpu_stream* stream);
void gpu_compute_begin(gpu_stream* stream);
void gpu_compute_end(gpu_stream* stream);

View File

@ -51,6 +51,10 @@ struct gpu_bundle {
VkDescriptorSet handle;
};
struct gpu_pass {
VkRenderPass handle;
};
struct gpu_pipeline {
VkPipeline handle;
};
@ -70,6 +74,7 @@ size_t gpu_sizeof_layout() { return sizeof(gpu_layout); }
size_t gpu_sizeof_shader() { return sizeof(gpu_shader); }
size_t gpu_sizeof_bundle_pool() { return sizeof(gpu_bundle_pool); }
size_t gpu_sizeof_bundle() { return sizeof(gpu_bundle); }
size_t gpu_sizeof_pass() { return sizeof(gpu_pass); }
size_t gpu_sizeof_pipeline() { return sizeof(gpu_pipeline); }
size_t gpu_sizeof_tally() { return sizeof(gpu_tally); }
@ -117,26 +122,6 @@ typedef struct {
gpu_victim data[256];
} gpu_morgue;
typedef struct {
uint32_t count;
uint32_t views;
uint32_t samples;
bool resolve;
struct {
VkFormat format;
VkImageLayout layout;
VkImageLayout resolveLayout;
gpu_load_op load;
gpu_save_op save;
} color[4];
struct {
VkFormat format;
VkImageLayout layout;
gpu_load_op load;
gpu_save_op save;
} depth;
} gpu_pass_info;
typedef struct {
void* object;
uint64_t hash;
@ -175,7 +160,6 @@ static struct {
gpu_texture surfaceTextures[8];
VkPipelineCache pipelineCache;
VkDebugUtilsMessengerEXT messenger;
gpu_cache_entry renderpasses[16][4];
gpu_cache_entry framebuffers[16][4];
gpu_allocator allocators[GPU_MEMORY_COUNT];
uint8_t allocatorLookup[GPU_MEMORY_COUNT];
@ -207,7 +191,6 @@ 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 VkRenderPass getCachedRenderPass(gpu_pass_info* pass, bool compatible);
static VkFramebuffer getCachedFramebuffer(VkRenderPass pass, VkImageView images[9], uint32_t imageCount, uint32_t size[2]);
static VkImageLayout getNaturalLayout(uint32_t usage, VkImageAspectFlags aspect);
static VkFormat convertFormat(gpu_texture_format format, int colorspace);
@ -1019,6 +1002,108 @@ void gpu_bundle_write(gpu_bundle** bundles, gpu_bundle_info* infos, uint32_t cou
}
}
// Pass
bool gpu_pass_init(gpu_pass* pass, gpu_pass_info* info) {
static const VkAttachmentLoadOp loadOps[] = {
[GPU_LOAD_OP_CLEAR] = VK_ATTACHMENT_LOAD_OP_CLEAR,
[GPU_LOAD_OP_DISCARD] = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
[GPU_LOAD_OP_KEEP] = VK_ATTACHMENT_LOAD_OP_LOAD
};
static const VkAttachmentStoreOp storeOps[] = {
[GPU_SAVE_OP_KEEP] = VK_ATTACHMENT_STORE_OP_STORE,
[GPU_SAVE_OP_DISCARD] = VK_ATTACHMENT_STORE_OP_DONT_CARE
};
VkAttachmentDescription attachments[9];
VkAttachmentReference references[9];
for (uint32_t i = 0; i < info->count; i++) {
references[i].attachment = i;
references[i].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkImageLayout naturalLayout = getNaturalLayout(info->color[i].usage, VK_IMAGE_ASPECT_COLOR_BIT);
bool surface = info->color[i].format == GPU_FORMAT_SURFACE;
bool discard = surface || info->color[i].load != GPU_LOAD_OP_KEEP;
attachments[i] = (VkAttachmentDescription) {
.format = convertFormat(info->color[i].format, info->color[i].srgb),
.samples = info->samples,
.loadOp = loadOps[info->color[i].load],
.storeOp = info->resolve ? VK_ATTACHMENT_STORE_OP_DONT_CARE : storeOps[info->color[i].save],
.initialLayout = discard ? VK_IMAGE_LAYOUT_UNDEFINED : naturalLayout,
.finalLayout = surface && !info->resolve ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : naturalLayout
};
}
if (info->resolve) {
for (uint32_t i = 0; i < info->count; i++) {
uint32_t index = info->count + i;
references[index].attachment = index;
references[index].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkImageLayout naturalLayout = getNaturalLayout(info->color[i].resolveUsage, VK_IMAGE_ASPECT_COLOR_BIT);
bool surface = info->color[i].format == GPU_FORMAT_SURFACE;
attachments[index] = (VkAttachmentDescription) {
.format = attachments[i].format,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.storeOp = storeOps[info->color[i].save],
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = surface ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : naturalLayout
};
}
}
if (info->depth.format) {
uint32_t index = info->count << info->resolve;
references[index].attachment = index;
references[index].layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
VkImageLayout naturalLayout = getNaturalLayout(info->depth.usage, VK_IMAGE_ASPECT_DEPTH_BIT);
attachments[index] = (VkAttachmentDescription) {
.format = convertFormat(info->depth.format, LINEAR),
.samples = info->samples,
.loadOp = loadOps[info->depth.load],
.storeOp = storeOps[info->depth.save],
.stencilLoadOp = loadOps[info->depth.load],
.stencilStoreOp = storeOps[info->depth.save],
.initialLayout = info->depth.load == GPU_LOAD_OP_KEEP ? naturalLayout : VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = naturalLayout
};
}
VkSubpassDescription subpass = {
.colorAttachmentCount = info->count,
.pColorAttachments = &references[0],
.pResolveAttachments = info->resolve ? &references[info->count] : NULL,
.pDepthStencilAttachment = info->depth.format ? &references[info->count << info->resolve] : NULL
};
VkRenderPassMultiviewCreateInfo multiview = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO,
.subpassCount = 1,
.pViewMasks = (uint32_t[1]) { (1 << info->views) - 1 }
};
VkRenderPassCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.pNext = info->views > 0 ? &multiview : NULL,
.attachmentCount = (info->count << info->resolve) + !!info->depth.format,
.pAttachments = attachments,
.subpassCount = 1,
.pSubpasses = &subpass
};
VK(vkCreateRenderPass(state.device, &createInfo, NULL, &pass->handle), "Could not create render pass") {
return false;
}
nickname(pass->handle, VK_OBJECT_TYPE_RENDER_PASS, info->label);
return true;
}
void gpu_pass_destroy(gpu_pass* pass) {
condemn(pass->handle, VK_OBJECT_TYPE_RENDER_PASS);
}
// Pipeline
bool gpu_pipeline_init_graphics(gpu_pipeline* pipeline, gpu_pipeline_info* info) {
@ -1266,28 +1351,6 @@ bool gpu_pipeline_init_graphics(gpu_pipeline* pipeline, gpu_pipeline_info* info)
}
};
bool resolve = info->multisample.count > 1;
bool depth = info->depth.format;
gpu_pass_info pass = {
.count = (info->colorCount << resolve) + depth,
.views = info->viewCount,
.samples = info->multisample.count,
.resolve = resolve,
.depth.format = convertFormat(info->depth.format, LINEAR),
.depth.layout = info->depth.format ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED,
.depth.load = GPU_LOAD_OP_CLEAR,
.depth.save = GPU_SAVE_OP_DISCARD
};
for (uint32_t i = 0; i < info->colorCount; i++) {
pass.color[i].format = convertFormat(info->color[i].format, info->color[i].srgb);
pass.color[i].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
pass.color[i].resolveLayout = resolve ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
pass.color[i].load = GPU_LOAD_OP_CLEAR;
pass.color[i].save = GPU_SAVE_OP_SAVE;
}
VkGraphicsPipelineCreateInfo pipelineInfo = (VkGraphicsPipelineCreateInfo) {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = 2,
@ -1301,7 +1364,7 @@ bool gpu_pipeline_init_graphics(gpu_pipeline* pipeline, gpu_pipeline_info* info)
.pColorBlendState = &colorBlend,
.pDynamicState = &dynamicState,
.layout = info->shader->pipelineLayout,
.renderPass = getCachedRenderPass(&pass, true)
.renderPass = info->pass->handle
};
VK(vkCreateGraphicsPipelines(state.device, state.pipelineCache, 1, &pipelineInfo, NULL, &pipeline->handle), "Could not create pipeline") {
@ -1401,11 +1464,10 @@ void gpu_tally_destroy(gpu_tally* tally) {
// Stream
gpu_stream* gpu_stream_begin(const char* label) {
gpu_stream* gpu_stream_begin() {
gpu_tick* tick = &state.ticks[state.tick[CPU] & TICK_MASK];
CHECK(state.streamCount < COUNTOF(tick->streams), "Too many passes") return NULL;
gpu_stream* stream = &tick->streams[state.streamCount];
nickname(stream->commands, VK_OBJECT_TYPE_COMMAND_BUFFER, label);
VkCommandBufferBeginInfo beginfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
@ -1421,56 +1483,41 @@ void gpu_stream_end(gpu_stream* stream) {
VK(vkEndCommandBuffer(stream->commands), "Failed to end stream") return;
}
void gpu_render_begin(gpu_stream* stream, gpu_canvas* canvas) {
gpu_texture* texture = canvas->color[0].texture ? canvas->color[0].texture : canvas->depth.texture;
gpu_pass_info pass = {
.views = texture->layers,
.samples = texture->samples,
.resolve = !!canvas->color[0].resolve
};
void gpu_render_begin(gpu_stream* stream, gpu_render_target* target) {
VkImageView images[9];
VkClearValue clears[9];
uint32_t count = 0;
for (uint32_t i = 0; i < COUNTOF(canvas->color) && canvas->color[i].texture; i++) {
images[i] = canvas->color[i].texture->view;
memcpy(clears[i].color.float32, canvas->color[i].clear, 4 * sizeof(float));
pass.color[i].format = convertFormat(canvas->color[i].texture->format, canvas->color[i].texture->srgb);
pass.color[i].layout = canvas->color[i].texture->layout;
pass.color[i].load = canvas->color[i].load;
pass.color[i].save = canvas->color[i].save;
pass.count++;
for (uint32_t i = 0; i < COUNTOF(target->color) && target->color[i].texture; i++) {
images[i] = target->color[i].texture->view;
memcpy(clears[i].color.float32, target->color[i].clear, 4 * sizeof(float));
count++;
}
if (pass.resolve) {
for (uint32_t i = 0; i < pass.count; i++) {
images[pass.count + i] = canvas->color[i].resolve->view;
pass.color[i].resolveLayout = canvas->color[i].resolve->layout;
bool resolve = target->color[0].texture && target->color[0].resolve;
if (resolve) {
for (uint32_t i = 0; i < count; i++) {
images[count + i] = target->color[i].resolve->view;
}
pass.count <<= 1;
count <<= 1;
}
if (canvas->depth.texture) {
uint32_t index = pass.count++;
images[index] = canvas->depth.texture->view;
clears[index].depthStencil.depth = canvas->depth.clear.depth;
clears[index].depthStencil.stencil = canvas->depth.clear.stencil;
pass.depth.format = convertFormat(canvas->depth.texture->format, LINEAR);
pass.depth.layout = canvas->depth.texture->layout;
pass.depth.load = canvas->depth.load;
pass.depth.save = canvas->depth.save;
if (target->depth.texture) {
uint32_t index = count++;
images[index] = target->depth.texture->view;
clears[index].depthStencil.depth = target->depth.clear.depth;
clears[index].depthStencil.stencil = target->depth.clear.stencil;
}
VkRenderPass renderPass = getCachedRenderPass(&pass, false);
VkFramebuffer framebuffer = getCachedFramebuffer(renderPass, images, pass.count, canvas->size);
VkFramebuffer framebuffer = getCachedFramebuffer(target->pass->handle, images, count, target->size);
VkRenderPassBeginInfo beginfo = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = renderPass,
.renderPass = target->pass->handle,
.framebuffer = framebuffer,
.renderArea = { { 0, 0 }, { canvas->size[0], canvas->size[1] } },
.clearValueCount = pass.count,
.renderArea = { { 0, 0 }, { target->size[0], target->size[1] } },
.clearValueCount = count,
.pClearValues = clears
};
@ -2218,12 +2265,6 @@ void gpu_destroy(void) {
if (framebuffer) vkDestroyFramebuffer(state.device, framebuffer, NULL);
}
}
for (uint32_t i = 0; i < COUNTOF(state.renderpasses); i++) {
for (uint32_t j = 0; j < COUNTOF(state.renderpasses[0]); j++) {
VkRenderPass pass = state.renderpasses[i][j].object;
if (pass) vkDestroyRenderPass(state.device, pass, NULL);
}
}
for (uint32_t i = 0; i < COUNTOF(state.memory); i++) {
if (state.memory[i].handle) vkFreeMemory(state.device, state.memory[i].handle, NULL);
}
@ -2447,170 +2488,6 @@ static void expunge() {
}
}
// Ugliness until we can use dynamic rendering
static VkRenderPass getCachedRenderPass(gpu_pass_info* pass, bool compatible) {
bool depth = pass->depth.layout != VK_IMAGE_LAYOUT_UNDEFINED;
uint32_t count = (pass->count - depth) >> pass->resolve;
uint32_t lower[] = {
count > 0 ? pass->color[0].format : 0xff,
count > 1 ? pass->color[1].format : 0xff,
count > 2 ? pass->color[2].format : 0xff,
count > 3 ? pass->color[3].format : 0xff,
depth ? pass->depth.format : 0xff,
pass->samples,
pass->resolve,
pass->views
};
uint32_t upper[] = {
count > 0 ? pass->color[0].load : 0xff,
count > 1 ? pass->color[1].load : 0xff,
count > 2 ? pass->color[2].load : 0xff,
count > 3 ? pass->color[3].load : 0xff,
count > 0 ? pass->color[0].save : 0xff,
count > 1 ? pass->color[1].save : 0xff,
count > 2 ? pass->color[2].save : 0xff,
count > 3 ? pass->color[3].save : 0xff,
depth ? pass->depth.load : 0xff,
depth ? pass->depth.save : 0xff,
count > 0 ? pass->color[0].layout : 0x00,
count > 1 ? pass->color[1].layout : 0x00,
count > 2 ? pass->color[2].layout : 0x00,
count > 3 ? pass->color[3].layout : 0x00,
pass->resolve && count > 0 ? pass->color[0].resolveLayout : 0x00,
pass->resolve && count > 1 ? pass->color[1].resolveLayout : 0x00,
pass->resolve && count > 2 ? pass->color[2].resolveLayout : 0x00,
pass->resolve && count > 3 ? pass->color[3].resolveLayout : 0x00,
depth ? pass->depth.layout : 0x00,
0
};
// The lower half of the hash contains format, sample, multiview info, which is all that's needed
// to select a "compatible" render pass (which is all that's needed for creating pipelines).
// The upper half of the hash contains load/store info and usage flags (for layout transitions),
// which is necessary to select an exact match when e.g. actually beginning a render pass
uint64_t hash = ((uint64_t) hash32(HASH_SEED, upper, sizeof(upper)) << 32) | hash32(HASH_SEED, lower, sizeof(lower));
uint64_t mask = compatible ? ~0u : ~0ull;
// Search for a pass, they are always stored in MRU order, which requires moving it to the first
// column if you end up using it, and shifting down the rest (dunno if that's actually worth it)
uint32_t rows = COUNTOF(state.renderpasses);
uint32_t cols = COUNTOF(state.renderpasses[0]);
gpu_cache_entry* row = state.renderpasses[hash & (rows - 1)];
for (uint32_t i = 0; i < cols && row[i].object; i++) {
if ((row[i].hash & mask) == hash) {
gpu_cache_entry entry = row[i];
if (i > 0) {
for (uint32_t j = i; j >= 1; j--) {
row[j] = row[j - 1];
}
row[0] = entry;
}
return entry.object;
}
}
// If no render pass was found, make a new one, potentially condemning and evicting an old one
static const VkAttachmentLoadOp loadOps[] = {
[GPU_LOAD_OP_LOAD] = VK_ATTACHMENT_LOAD_OP_LOAD,
[GPU_LOAD_OP_CLEAR] = VK_ATTACHMENT_LOAD_OP_CLEAR,
[GPU_LOAD_OP_DISCARD] = VK_ATTACHMENT_LOAD_OP_DONT_CARE
};
static const VkAttachmentStoreOp storeOps[] = {
[GPU_SAVE_OP_SAVE] = VK_ATTACHMENT_STORE_OP_STORE,
[GPU_SAVE_OP_DISCARD] = VK_ATTACHMENT_STORE_OP_DONT_CARE
};
VkAttachmentDescription attachments[9];
VkAttachmentReference references[9];
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 discard = surface || pass->color[i].load != GPU_LOAD_OP_LOAD;
attachments[i] = (VkAttachmentDescription) {
.format = pass->color[i].format,
.samples = pass->samples,
.loadOp = loadOps[pass->color[i].load],
.storeOp = pass->resolve ? VK_ATTACHMENT_STORE_OP_DONT_CARE : storeOps[pass->color[i].save],
.initialLayout = discard ? VK_IMAGE_LAYOUT_UNDEFINED : pass->color[i].layout,
.finalLayout = surface && !pass->resolve ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : pass->color[i].layout
};
}
if (pass->resolve) {
for (uint32_t i = 0; i < count; i++) {
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;
attachments[index] = (VkAttachmentDescription) {
.format = pass->color[i].format,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.storeOp = storeOps[pass->color[i].save],
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = surface ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : pass->color[i].resolveLayout
};
}
}
if (depth) {
uint32_t index = pass->count - 1;
references[index].attachment = index;
references[index].layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachments[index] = (VkAttachmentDescription) {
.format = pass->depth.format,
.samples = pass->samples,
.loadOp = loadOps[pass->depth.load],
.storeOp = storeOps[pass->depth.save],
.stencilLoadOp = loadOps[pass->depth.load],
.stencilStoreOp = storeOps[pass->depth.save],
.initialLayout = pass->depth.load == GPU_LOAD_OP_LOAD ? pass->depth.layout : VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = pass->depth.layout
};
}
VkSubpassDescription subpass = {
.colorAttachmentCount = count,
.pColorAttachments = &references[0],
.pResolveAttachments = pass->resolve ? &references[count] : NULL,
.pDepthStencilAttachment = depth ? &references[pass->count - 1] : NULL
};
VkRenderPassMultiviewCreateInfo multiview = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO,
.subpassCount = 1,
.pViewMasks = (uint32_t[1]) { (1 << pass->views) - 1 }
};
VkRenderPassCreateInfo info = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.pNext = pass->views > 0 ? &multiview : NULL,
.attachmentCount = pass->count,
.pAttachments = attachments,
.subpassCount = 1,
.pSubpasses = &subpass
};
VkRenderPass handle;
VK(vkCreateRenderPass(state.device, &info, NULL, &handle), "Could not create render pass") {
return VK_NULL_HANDLE;
}
condemn(row[cols - 1].object, VK_OBJECT_TYPE_RENDER_PASS);
memmove(row + 1, row, (cols - 1) * sizeof(row[0]));
row[0].object = handle;
row[0].hash = hash;
return handle;
}
VkFramebuffer getCachedFramebuffer(VkRenderPass pass, VkImageView images[9], uint32_t imageCount, uint32_t size[2]) {
uint32_t hash = HASH_SEED;
hash = hash32(hash, images, imageCount * sizeof(images[0]));

View File

@ -242,18 +242,18 @@ typedef struct {
} Camera;
typedef struct {
Font* font;
Shader* shader;
Sampler* sampler;
Material* material;
uint64_t formatHash;
gpu_pipeline_info info;
bool dirty;
MeshMode mode;
float color[4];
float viewport[4];
float depthRange[2];
uint32_t scissor[4];
float color[4];
MeshMode mode;
bool dirty;
uint64_t formatHash;
gpu_pipeline_info info;
Material* material;
Sampler* sampler;
Shader* shader;
Font* font;
} Pipeline;
enum {
@ -284,7 +284,13 @@ typedef struct {
struct Pass {
uint32_t ref;
uint32_t tick;
PassInfo info;
gpu_pass* gpu;
Texture* color[4];
Texture* depth;
bool resolve;
gpu_render_target target;
gpu_stream* stream;
float* transform;
float transforms[16][16];
@ -322,12 +328,6 @@ typedef struct {
uint32_t tail;
} MaterialBlock;
typedef struct {
gpu_texture* texture;
uint32_t hash;
uint32_t tick;
} TempAttachment;
typedef struct {
void* next;
gpu_bundle_pool* gpu;
@ -362,8 +362,8 @@ static struct {
gpu_features features;
gpu_limits limits;
GraphicsStats stats;
float background[4];
Texture* window;
Pass* windowPass;
Font* defaultFont;
Buffer* defaultBuffer;
Texture* defaultTexture;
@ -377,8 +377,6 @@ static struct {
Material* defaultMaterial;
uint32_t materialBlock;
arr_t(MaterialBlock) materialBlocks;
arr_t(TempAttachment) attachments;
arr_t(Pass*) passes;
map_t pipelineLookup;
arr_t(gpu_pipeline*) pipelines;
arr_t(Layout) layouts;
@ -390,16 +388,14 @@ static struct {
// Helpers
static void* tempAlloc(size_t size);
static void* tempGrow(void* p, size_t size);
static uint32_t tempPush(void);
static void tempPop(uint32_t stack);
static int u64cmp(const void* a, const void* b);
static void beginFrame(void);
static void cleanupPasses(void);
static void processReadbacks(void);
static uint32_t getLayout(gpu_slot* slots, uint32_t count);
static gpu_bundle* getBundle(uint32_t layout);
static gpu_texture* getAttachment(uint32_t size[2], uint32_t layers, TextureFormat format, bool srgb, uint32_t samples);
static uint32_t convertTextureUsage(uint32_t usage);
static size_t measureTexture(TextureFormat format, uint16_t w, uint16_t h, uint16_t d);
static void checkTextureBounds(const TextureInfo* info, uint32_t offset[4], uint32_t extent[3]);
static void mipmapTexture(gpu_stream* stream, Texture* texture, uint32_t base, uint32_t count);
@ -412,11 +408,11 @@ static void onMessage(void* context, const char* message, bool severe);
// Entry
bool lovrGraphicsInit(bool debug, bool vsync) {
bool lovrGraphicsInit(GraphicsConfig* config) {
if (state.initialized) return false;
gpu_config config = {
.debug = debug,
gpu_config gpu = {
.debug = config->debug,
.callback = onMessage,
.engineName = "LOVR",
.engineVersion = { LOVR_VERSION_MAJOR, LOVR_VERSION_MINOR, LOVR_VERSION_PATCH },
@ -427,22 +423,22 @@ bool lovrGraphicsInit(bool debug, bool vsync) {
#ifdef LOVR_VK
if (os_window_is_open()) {
config.vk.getInstanceExtensions = os_vk_get_instance_extensions;
config.vk.createSurface = os_vk_create_surface;
config.vk.surface = true;
config.vk.vsync = vsync;
gpu.vk.getInstanceExtensions = os_vk_get_instance_extensions;
gpu.vk.createSurface = os_vk_create_surface;
gpu.vk.surface = true;
gpu.vk.vsync = config->vsync;
}
#endif
#if defined LOVR_VK && !defined LOVR_DISABLE_HEADSET
if (lovrHeadsetInterface) {
config.vk.getPhysicalDevice = lovrHeadsetInterface->getVulkanPhysicalDevice;
config.vk.createInstance = lovrHeadsetInterface->createVulkanInstance;
config.vk.createDevice = lovrHeadsetInterface->createVulkanDevice;
gpu.vk.getPhysicalDevice = lovrHeadsetInterface->getVulkanPhysicalDevice;
gpu.vk.createInstance = lovrHeadsetInterface->createVulkanInstance;
gpu.vk.createDevice = lovrHeadsetInterface->createVulkanDevice;
}
#endif
if (!gpu_init(&config)) {
if (!gpu_init(&gpu)) {
lovrThrow("Failed to initialize GPU");
}
@ -457,8 +453,6 @@ bool lovrGraphicsInit(bool debug, bool vsync) {
arr_init(&state.pipelines, realloc);
arr_init(&state.layouts, realloc);
arr_init(&state.materialBlocks, realloc);
arr_init(&state.attachments, realloc);
arr_init(&state.passes, realloc);
gpu_slot builtinSlots[] = {
{ 0, GPU_SLOT_UNIFORM_BUFFER, GPU_STAGE_ALL }, // Cameras
@ -589,6 +583,41 @@ bool lovrGraphicsInit(bool debug, bool vsync) {
.texture = state.defaultTexture
});
if (gpu.vk.surface) {
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,
.usage = TEXTURE_RENDER,
.srgb = true
};
TextureFormat depthFormat = config->stencil ? FORMAT_D32FS8 : FORMAT_D32F;
if (config->stencil && !lovrGraphicsIsFormatSupported(depthFormat, TEXTURE_FEATURE_RENDER)) {
depthFormat = FORMAT_D24S8; // Guaranteed to be supported if the other one isn't
}
state.windowPass = lovrPassCreate(&(PassInfo) {
.type = PASS_RENDER,
.canvas.count = 1,
.canvas.textures[0] = state.window,
.canvas.depth.format = depthFormat,
.canvas.samples = config->antialias ? 4 : 1
});
}
float16Init();
glslang_initialize_process();
state.initialized = true;
@ -597,17 +626,11 @@ bool lovrGraphicsInit(bool debug, bool vsync) {
void lovrGraphicsDestroy() {
if (!state.initialized) return;
cleanupPasses();
arr_free(&state.passes);
for (Readback* readback = state.oldestReadback; readback; readback = readback->next) {
lovrRelease(readback, lovrReadbackDestroy);
}
lovrRelease(state.window, lovrTextureDestroy);
for (uint32_t i = 0; i < state.attachments.length; i++) {
gpu_texture_destroy(state.attachments.data[i].texture);
free(state.attachments.data[i].texture);
}
arr_free(&state.attachments);
lovrRelease(state.windowPass, lovrPassDestroy);
lovrRelease(state.defaultFont, lovrFontDestroy);
lovrRelease(state.defaultBuffer, lovrBufferDestroy);
lovrRelease(state.defaultTexture, lovrTextureDestroy);
@ -728,19 +751,6 @@ bool lovrGraphicsIsFormatSupported(uint32_t format, uint32_t features) {
return true;
}
void lovrGraphicsGetBackground(float background[4]) {
background[0] = lovrMathLinearToGamma(state.background[0]);
background[1] = lovrMathLinearToGamma(state.background[1]);
background[2] = lovrMathLinearToGamma(state.background[2]);
background[3] = state.background[3];
}
void lovrGraphicsSetBackground(float background[4]) {
state.background[0] = lovrMathGammaToLinear(background[0]);
state.background[1] = lovrMathGammaToLinear(background[1]);
state.background[2] = lovrMathGammaToLinear(background[2]);
state.background[3] = background[3];
}
void lovrGraphicsSubmit(Pass** passes, uint32_t count) {
if (!state.active) {
@ -790,9 +800,10 @@ void lovrGraphicsSubmit(Pass** passes, uint32_t count) {
state.hasReskin = false;
}
// End passes
// Finish passes
for (uint32_t i = 0; i < count; i++) {
Pass* pass = passes[i];
lovrAssert(passes[i]->tick == state.tick, "Trying to submit a Pass that wasn't reset this frame");
streams[i + 1] = pass->stream;
@ -943,9 +954,6 @@ void lovrGraphicsSubmit(Pass** passes, uint32_t count) {
gpu_submit(streams, total, present);
cleanupPasses();
arr_clear(&state.passes);
state.stats.pipelineSwitches = 0;
state.stats.bundleSwitches = 0;
@ -1047,27 +1055,6 @@ void lovrBufferClear(Buffer* buffer, uint32_t offset, uint32_t size) {
// Texture
Texture* lovrGraphicsGetWindowTexture() {
if (!state.window) {
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,
.usage = TEXTURE_RENDER,
.srgb = true
};
}
if (!state.window->gpu) {
beginFrame();
state.window->gpu = gpu_surface_acquire();
@ -1164,12 +1151,7 @@ Texture* lovrTextureCreate(const TextureInfo* info) {
.size = { info->width, info->height, info->layers },
.mipmaps = texture->info.mipmaps,
.samples = MAX(info->samples, 1),
.usage =
((info->usage & TEXTURE_SAMPLE) ? GPU_TEXTURE_SAMPLE : 0) |
((info->usage & TEXTURE_RENDER) ? GPU_TEXTURE_RENDER : 0) |
((info->usage & TEXTURE_STORAGE) ? GPU_TEXTURE_STORAGE : 0) |
((info->usage & TEXTURE_TRANSFER) ? GPU_TEXTURE_COPY_SRC | GPU_TEXTURE_COPY_DST : 0) |
((info->usage == TEXTURE_RENDER) ? GPU_TEXTURE_TRANSIENT : 0),
.usage = convertTextureUsage(info->usage),
.srgb = info->srgb,
.handle = info->handle,
.label = info->label,
@ -3052,166 +3034,333 @@ static void lovrTallyResolve(Tally* tally, uint32_t index, uint32_t count, gpu_b
// Pass
Pass* lovrGraphicsGetPass(PassInfo* info) {
beginFrame();
Pass* pass = tempAlloc(sizeof(Pass));
Pass* lovrGraphicsGetWindowPass() {
return state.windowPass;
}
Pass* lovrPassCreate(PassInfo* info) {
Pass* pass = calloc(1, sizeof(Pass) + gpu_sizeof_pass());
pass->ref = 1;
pass->gpu = (gpu_pass*) (pass + 1);
pass->info = *info;
pass->stream = gpu_stream_begin(info->label);
arr_init(&pass->readbacks, tempGrow);
arr_init(&pass->access, tempGrow);
arr_push(&state.passes, pass);
lovrRetain(pass);
if (info->type == PASS_TRANSFER) {
return pass;
}
if (info->type == PASS_COMPUTE) {
memset(pass->constants, 0, sizeof(pass->constants));
pass->constantsDirty = true;
pass->bindingMask = 0;
pass->bindingsDirty = true;
pass->pipelineIndex = 0;
pass->pipeline = &pass->pipelines[0];
pass->pipeline->shader = NULL;
pass->pipeline->dirty = true;
gpu_compute_begin(pass->stream);
arr_init(&pass->access, realloc);
arr_init(&pass->access, realloc);
if (info->type != PASS_RENDER) {
lovrPassReset(pass);
return pass;
}
// Validation
Canvas* canvas = &info->canvas;
const TextureInfo* main = canvas->textures[0] ? &canvas->textures[0]->info : &canvas->depth.texture->info;
lovrCheck(canvas->textures[0] || canvas->depth.texture, "Render pass must have at least one color or depth texture");
lovrCheck(main->width <= state.limits.renderSize[0], "Render pass width (%d) exceeds the renderSize limit of this GPU (%d)", main->width, state.limits.renderSize[0]);
lovrCheck(main->height <= state.limits.renderSize[1], "Render pass height (%d) exceeds the renderSize limit of this GPU (%d)", main->height, state.limits.renderSize[1]);
lovrCheck(main->layers <= state.limits.renderSize[2], "Render pass view count (%d) exceeds the renderSize limit of this GPU (%d)", main->layers, state.limits.renderSize[2]);
DepthInfo* depth = &canvas->depth;
const TextureInfo* t = canvas->count > 0 ? &canvas->textures[0]->info : &depth->texture->info;
lovrCheck(canvas->count > 0 || depth->texture, "Render pass must have at least one color or depth texture");
lovrCheck(t->layers <= state.limits.renderSize[2], "Pass view count (%d) exceeds the renderSize limit of this GPU (%d)", t->layers, state.limits.renderSize[2]);
lovrCheck(canvas->samples == 1 || canvas->samples == 4, "Render pass sample count must be 1 or 4...for now");
pass->resolve = canvas->samples > 1 && t->samples == 1;
uint32_t colorTextureCount = 0;
for (uint32_t i = 0; i < COUNTOF(canvas->textures) && canvas->textures[i]; i++, colorTextureCount++) {
pass->cameraCount = t->layers;
pass->cameras = malloc(pass->cameraCount * sizeof(Camera));
lovrAssert(pass->cameras, "Out of memory");
for (uint32_t i = 0; i < canvas->count; i++) {
const TextureInfo* texture = &canvas->textures[i]->info;
bool renderable = texture->format == GPU_FORMAT_SURFACE || (state.features.formats[texture->format] & GPU_FEATURE_RENDER);
lovrCheck(renderable, "This GPU does not support rendering to the texture format used by Canvas texture #%d", i + 1);
lovrCheck(texture->usage & TEXTURE_RENDER, "Texture must be created with the 'render' flag to render to it");
lovrCheck(texture->width == main->width, "Render pass texture sizes must match");
lovrCheck(texture->height == main->height, "Render pass texture sizes must match");
lovrCheck(texture->layers == main->layers, "Render pass texture sizes must match");
lovrCheck(texture->samples == main->samples, "Render pass texture sample counts must match");
lovrCheck(renderable, "This GPU does not support rendering to the texture format used by color target #%d", i + 1);
if (canvas->samples > 1 && t->samples == 1) {
lovrCheck(canvas->loads[i] != LOAD_KEEP, "When multisampling is active, render pass textures must be cleared");
}
}
if (canvas->depth.texture || canvas->depth.format) {
TextureFormat format = canvas->depth.texture ? canvas->depth.texture->info.format : canvas->depth.format;
if (depth->texture || depth->format) {
TextureFormat format = depth->texture ? depth->texture->info.format : depth->format;
bool renderable = state.features.formats[format] & GPU_FEATURE_RENDER;
lovrCheck(format == FORMAT_D16 || format == FORMAT_D32F || format == FORMAT_D24S8 || format == FORMAT_D32FS8, "Depth buffer must use a depth format");
lovrCheck(renderable, "This GPU does not support depth buffers with this texture format");
if (canvas->depth.texture) {
const TextureInfo* texture = &canvas->depth.texture->info;
lovrCheck(texture->usage & TEXTURE_RENDER, "Texture must be created with the 'render' flag to render to it");
lovrCheck(texture->width == main->width, "Render pass texture sizes must match");
lovrCheck(texture->height == main->height, "Render pass texture sizes must match");
lovrCheck(texture->layers == main->layers, "Render pass texture sizes must match");
lovrCheck(texture->samples == main->samples, "Depth buffer sample count must match the main render pass sample count...for now");
}
// Render Pass
gpu_pass_info passInfo = {
.count = canvas->count,
.views = t->layers,
.samples = canvas->samples,
.resolve = pass->resolve
};
for (uint32_t i = 0; i < canvas->count; i++) {
TextureInfo* texture = &canvas->textures[i]->info;
passInfo.color[i] = (gpu_pass_color_info) {
.format = (gpu_texture_format) texture->format,
.srgb = texture->srgb,
.usage = convertTextureUsage(texture->usage),
.load = (gpu_load_op) canvas->loads[i],
.save = (gpu_save_op) GPU_SAVE_OP_KEEP
};
if (pass->resolve) {
passInfo.color[i].resolveUsage = passInfo.color[i].usage;
passInfo.color[i].usage = GPU_TEXTURE_RENDER | GPU_TEXTURE_TRANSIENT;
}
}
// Render target
if (depth->texture || depth->format) {
passInfo.depth = (gpu_pass_depth_info) {
.format = (gpu_texture_format) (depth->texture ? depth->texture->info.format : depth->format),
.usage = depth->texture ? convertTextureUsage(depth->texture->info.usage) : GPU_TEXTURE_TRANSIENT,
.load = (gpu_load_op) depth->load,
.save = depth->texture ? GPU_SAVE_OP_KEEP : GPU_SAVE_OP_DISCARD
};
}
gpu_canvas target = {
.size = { main->width, main->height }
gpu_pass_init(pass->gpu, &passInfo);
pass->target.pass = pass->gpu;
lovrPassSetTarget(pass, canvas->textures, canvas->depth.texture);
lovrPassSetClear(pass, canvas->clears, canvas->depth.clear, 0);
lovrPassReset(pass);
return pass;
}
static void lovrPassRelease(Pass* pass) {
for (size_t i = 0; i < pass->access.length; i++) {
Access* access = &pass->access.data[i];
lovrRelease(access->buffer, lovrBufferDestroy);
lovrRelease(access->texture, lovrTextureDestroy);
}
if (pass->info.type == PASS_RENDER) {
for (size_t i = 0; i <= pass->pipelineIndex; i++) {
lovrRelease(pass->pipelines[i].font, lovrFontDestroy);
lovrRelease(pass->pipelines[i].sampler, lovrSamplerDestroy);
lovrRelease(pass->pipelines[i].shader, lovrShaderDestroy);
lovrRelease(pass->pipelines[i].material, lovrMaterialDestroy);
pass->pipelines[i].font = NULL;
pass->pipelines[i].sampler = NULL;
pass->pipelines[i].shader = NULL;
pass->pipelines[i].material = NULL;
}
}
}
void lovrPassDestroy(void* ref) {
Pass* pass = ref;
lovrPassRelease(pass);
gpu_pass_destroy(pass->gpu);
arr_free(&pass->access);
for (uint32_t i = 0; i < pass->info.canvas.count; i++) {
lovrRelease(pass->color[i], lovrTextureDestroy);
if (pass->resolve && pass->target.color[i].texture) {
gpu_texture_destroy(pass->target.color[i].texture);
free(pass->target.color[i].texture);
}
}
lovrRelease(pass->depth, lovrTextureDestroy);
if (pass->info.canvas.depth.format && pass->target.depth.texture) {
gpu_texture_destroy(pass->target.depth.texture);
free(pass->target.depth.texture);
}
free(pass->cameras);
free(pass);
}
const PassInfo* lovrPassGetInfo(Pass* pass) {
return &pass->info;
}
uint32_t lovrPassGetWidth(Pass* pass) {
if (pass->info.type != PASS_RENDER) {
return 0;
} else {
return (pass->color[0] ? pass->color[0] : pass->depth)->info.width;
}
}
uint32_t lovrPassGetHeight(Pass* pass) {
if (pass->info.type != PASS_RENDER) {
return 0;
} else {
return (pass->color[0] ? pass->color[0] : pass->depth)->info.height;
}
}
uint32_t lovrPassGetViewCount(Pass* pass) {
return pass->cameraCount;
}
uint32_t lovrPassGetSampleCount(Pass* pass) {
return pass->info.canvas.samples;
}
void lovrPassGetTarget(Pass* pass, Texture* color[4], Texture** depth) {
memcpy(color, pass->color, pass->info.canvas.count * sizeof(Texture));
*depth = pass->depth;
}
void lovrPassSetTarget(Pass* pass, Texture* color[4], Texture* depth) {
const TextureInfo* t = color[0] ? &color[0]->info : &depth->info;
bool resized = pass->target.size[0] != t->width || pass->target.size[1] != t->height;
if (resized) {
lovrCheck(t->width <= state.limits.renderSize[0], "Texture width (%d) exceeds the renderSize limit of this GPU (%d)", t->width, state.limits.renderSize[0]);
lovrCheck(t->height <= state.limits.renderSize[1], "Texture height (%d) exceeds the renderSize limit of this GPU (%d)", t->height, state.limits.renderSize[1]);
pass->target.size[0] = t->width;
pass->target.size[1] = t->height;
}
Canvas* canvas = &pass->info.canvas;
gpu_texture_info tempAttachmentInfo = {
.type = GPU_TEXTURE_ARRAY,
.size[0] = t->width,
.size[1] = t->height,
.size[2] = t->layers,
.mipmaps = 1,
.samples = canvas->samples,
.usage = GPU_TEXTURE_RENDER | GPU_TEXTURE_TRANSIENT
};
for (uint32_t i = 0; i < colorTextureCount; i++) {
if (main->samples == 1 && canvas->samples > 1) {
lovrCheck(canvas->loads[i] != LOAD_KEEP, "When internal multisampling is used, render pass textures must be cleared");
TextureFormat format = canvas->textures[i]->info.format;
bool srgb = canvas->textures[i]->info.srgb;
target.color[i].texture = getAttachment(target.size, main->layers, format, srgb, canvas->samples);
target.color[i].resolve = canvas->textures[i]->renderView;
} else {
target.color[i].texture = canvas->textures[i]->renderView;
for (uint32_t i = 0; i < canvas->count; i++) {
lovrRetain(color[i]);
lovrRelease(pass->color[i], lovrTextureDestroy);
lovrCheck(color[i], "Color target #%d is missing", i + 1);
const TextureInfo* info = &color[i]->info;
lovrCheck(info->usage & TEXTURE_RENDER, "Texture must be created with the 'render' flag to render to it");
lovrCheck(info->width == t->width, "Texture sizes must match");
lovrCheck(info->height == t->height, "Texture sizes must match");
lovrCheck(info->layers == t->layers, "Texture sizes must match");
if (pass->color[0]) {
lovrCheck(info->samples == pass->color[0]->info.samples, "Color target sample count is different than the sample count of the texture used to create the Pass");
}
target.color[i].load = (gpu_load_op) canvas->loads[i];
target.color[i].save = GPU_SAVE_OP_SAVE;
target.color[i].clear[0] = lovrMathGammaToLinear(canvas->clears[i][0]);
target.color[i].clear[1] = lovrMathGammaToLinear(canvas->clears[i][1]);
target.color[i].clear[2] = lovrMathGammaToLinear(canvas->clears[i][2]);
target.color[i].clear[3] = canvas->clears[i][3];
if (pass->resolve) {
if (resized) {
if (pass->target.color[i].texture) {
gpu_texture_destroy(pass->target.color[i].texture);
} else {
pass->target.color[i].texture = malloc(gpu_sizeof_texture());
lovrAssert(pass->target.color[i].texture, "Out of memory");
}
if (info->canvas.mipmap && canvas->textures[i]->info.mipmaps > 1) {
trackTexture(pass, canvas->textures[i], GPU_PHASE_TRANSFER, GPU_CACHE_TRANSFER_WRITE);
tempAttachmentInfo.format = info->format;
tempAttachmentInfo.srgb = info->srgb;
gpu_texture_init(pass->target.color[i].texture, &tempAttachmentInfo);
}
pass->target.color[i].resolve = color[i]->renderView;
} else {
gpu_cache cache = GPU_CACHE_COLOR_WRITE | (canvas->loads[i] == LOAD_KEEP ? GPU_CACHE_COLOR_READ : 0);
trackTexture(pass, canvas->textures[i], GPU_PHASE_COLOR, cache);
pass->target.color[i].texture = color[i]->renderView;
}
pass->color[i] = color[i];
}
if (canvas->depth.texture) {
target.depth.texture = canvas->depth.texture->renderView;
if (info->canvas.mipmap && canvas->depth.texture->info.mipmaps > 1) {
trackTexture(pass, canvas->depth.texture, GPU_PHASE_TRANSFER, GPU_CACHE_TRANSFER_WRITE);
lovrRetain(depth);
lovrRelease(pass->depth, lovrTextureDestroy);
lovrCheck(depth, "Depth target is missing");
const TextureInfo* info = &depth->info;
lovrCheck(info->usage & TEXTURE_RENDER, "Texture must be created with the 'render' flag to render to it");
lovrCheck(info->width == t->width, "Texture sizes must match");
lovrCheck(info->height == t->height, "Texture sizes must match");
lovrCheck(info->layers == t->layers, "Texture sizes must match");
lovrCheck(info->samples == canvas->samples, "Sorry, resolving depth textures is not supported yet");
pass->target.depth.texture = depth->renderView;
pass->depth = depth;
} else if (canvas->depth.format && resized) {
if (pass->target.depth.texture) {
gpu_texture_destroy(pass->target.depth.texture);
} else {
gpu_phase phase = canvas->depth.load == LOAD_KEEP ? GPU_PHASE_DEPTH_EARLY : GPU_PHASE_DEPTH_LATE;
gpu_cache cache = GPU_CACHE_DEPTH_WRITE | (canvas->depth.load == LOAD_KEEP ? GPU_CACHE_DEPTH_READ : 0);
trackTexture(pass, canvas->depth.texture, phase, cache);
pass->target.depth.texture = malloc(gpu_sizeof_texture());
lovrAssert(pass->target.depth.texture, "Out of memory");
}
} else if (canvas->depth.format) {
target.depth.texture = getAttachment(target.size, main->layers, canvas->depth.format, false, canvas->samples);
tempAttachmentInfo.format = canvas->depth.format;
tempAttachmentInfo.srgb = false;
gpu_texture_init(pass->target.depth.texture, &tempAttachmentInfo);
}
}
if (target.depth.texture) {
target.depth.load = target.depth.stencilLoad = (gpu_load_op) canvas->depth.load;
target.depth.save = canvas->depth.texture ? GPU_SAVE_OP_SAVE : GPU_SAVE_OP_DISCARD;
target.depth.clear.depth = canvas->depth.clear;
void lovrPassGetClear(Pass* pass, float color[4][4], float* depth, uint8_t* stencil) {
for (uint32_t i = 0; i < pass->info.canvas.count; i++) {
color[i][0] = lovrMathLinearToGamma(pass->target.color[i].clear[0]);
color[i][1] = lovrMathLinearToGamma(pass->target.color[i].clear[1]);
color[i][2] = lovrMathLinearToGamma(pass->target.color[i].clear[2]);
color[i][3] = pass->target.color[i].clear[3];
}
*depth = pass->target.depth.clear.depth;
*stencil = pass->target.depth.clear.stencil;
}
// Begin render pass
void lovrPassSetClear(Pass* pass, float color[4][4], float depth, uint8_t stencil) {
for (uint32_t i = 0; i < pass->info.canvas.count; i++) {
pass->target.color[i].clear[0] = lovrMathGammaToLinear(color[i][0]);
pass->target.color[i].clear[1] = lovrMathGammaToLinear(color[i][1]);
pass->target.color[i].clear[2] = lovrMathGammaToLinear(color[i][2]);
pass->target.color[i].clear[3] = color[i][3];
}
pass->target.depth.clear.depth = depth;
pass->target.depth.clear.stencil = stencil;
}
gpu_render_begin(pass->stream, &target);
void lovrPassReset(Pass* pass) {
lovrPassRelease(pass);
// The default Buffer (filled with zeros/ones) is always at slot #1, used for default vertex data
gpu_buffer* buffers[] = { state.defaultBuffer->gpu, state.defaultBuffer->gpu };
gpu_bind_vertex_buffers(pass->stream, buffers, NULL, 0, 2);
beginFrame();
pass->stream = gpu_stream_begin();
pass->tick = state.tick;
arr_clear(&pass->access);
// Reset state
pass->transform = pass->transforms[0];
pass->transformIndex = 0;
pass->transform = pass->transforms[pass->transformIndex];
mat4_identity(pass->transform);
pass->pipelineIndex = 0;
pass->pipeline = &pass->pipelines[0];
pass->pipeline = &pass->pipelines[pass->pipelineIndex];
pass->pipeline->dirty = true;
float color[4] = { 1.f, 1.f, 1.f, 1.f };
float viewport[4] = { 0.f, 0.f, (float) pass->target.size[0], (float) pass->target.size[1] };
float depthRange[2] = { 0.f, 1.f };
uint32_t scissor[4] = { 0, 0, pass->target.size[0], pass->target.size[1] };
pass->pipeline->mode = MESH_TRIANGLES;
memcpy(pass->pipeline->color, color, sizeof(color));
memcpy(pass->pipeline->viewport, viewport, sizeof(viewport));
memcpy(pass->pipeline->depthRange, depthRange, sizeof(depthRange));
memcpy(pass->pipeline->scissor, scissor, sizeof(scissor));
pass->pipeline->formatHash = 0;
pass->pipeline->info = (gpu_pipeline_info) {
.colorCount = colorTextureCount,
.depth.format = canvas->depth.texture ? canvas->depth.texture->info.format : canvas->depth.format,
.multisample.count = canvas->samples,
.viewCount = main->layers,
.pass = pass->gpu,
.colorCount = pass->info.canvas.count,
.multisample.count = pass->info.canvas.samples,
.viewCount = pass->cameraCount,
.depth.test = GPU_COMPARE_GEQUAL,
.depth.write = true
};
for (uint32_t i = 0; i < colorTextureCount; i++) {
pass->pipeline->info.color[i].format = canvas->textures[i]->info.format;
pass->pipeline->info.color[i].srgb = canvas->textures[i]->info.srgb;
for (uint32_t i = 0; i < pass->info.canvas.count; i++) {
pass->pipeline->info.color[i].mask = 0xf;
}
float defaultColor[4] = { 1.f, 1.f, 1.f, 1.f };
memcpy(pass->pipeline->color, defaultColor, sizeof(defaultColor));
pass->pipeline->formatHash = 0;
pass->pipeline->font = NULL;
pass->pipeline->material = NULL;
pass->pipeline->sampler = NULL;
pass->pipeline->shader = NULL;
pass->pipeline->mode = MESH_TRIANGLES;
pass->pipeline->dirty = true;
pass->pipeline->material = state.defaultMaterial;
lovrRetain(pass->pipeline->material);
pass->pipeline->font = NULL;
pass->materialDirty = true;
pass->samplerDirty = true;
memset(pass->constants, 0, sizeof(pass->constants));
pass->constantsDirty = true;
@ -3219,46 +3368,68 @@ Pass* lovrGraphicsGetPass(PassInfo* info) {
pass->bindingMask = 0;
pass->bindingsDirty = true;
pass->cameraCount = main->layers;
pass->cameras = tempAlloc(pass->cameraCount * sizeof(Camera));
float aspect = (float) pass->target.size[0] / pass->target.size[1];
for (uint32_t i = 0; i < pass->cameraCount; i++) {
mat4_identity(pass->cameras[i].view);
mat4_perspective(pass->cameras[i].projection, 1.f, (float) main->width / main->height, .01f, 0.f);
mat4_perspective(pass->cameras[i].projection, 1.f, aspect, .01f, 0.f);
}
pass->cameraDirty = true;
pass->drawCount = 0;
gpu_buffer_binding cameras = { tempAlloc(gpu_sizeof_buffer()), 0, pass->cameraCount * sizeof(Camera) };
gpu_buffer_binding draws = { tempAlloc(gpu_sizeof_buffer()), 0, 256 * sizeof(DrawData) };
pass->drawCount = 0;
pass->builtins[0] = (gpu_binding) { 0, GPU_SLOT_UNIFORM_BUFFER, .buffer = cameras };
pass->builtins[1] = (gpu_binding) { 1, GPU_SLOT_UNIFORM_BUFFER, .buffer = draws };
pass->builtins[2] = (gpu_binding) { 2, GPU_SLOT_SAMPLER, .sampler = NULL };
pass->pipeline->sampler = state.defaultSamplers[FILTER_LINEAR];
pass->samplerDirty = true;
lovrRetain(pass->pipeline->sampler);
pass->vertexBuffer = NULL;
pass->indexBuffer = NULL;
memset(pass->shapeCache, 0, sizeof(pass->shapeCache));
float viewport[6] = { 0.f, 0.f, (float) main->width, (float) main->height, 0.f, 1.f };
lovrPassSetViewport(pass, viewport, viewport + 4);
if (pass->info.type == PASS_RENDER) {
Canvas* canvas = &pass->info.canvas;
uint32_t scissor[4] = { 0, 0, main->width, main->height };
lovrPassSetScissor(pass, scissor);
for (uint32_t i = 0; i < canvas->count; i++) {
if (pass->color[i] == state.window) {
if (pass->resolve) { // Make sure window swapchain is updated
pass->target.color[i].resolve = lovrGraphicsGetWindowTexture()->gpu;
} else {
pass->target.color[i].texture = lovrGraphicsGetWindowTexture()->gpu;
}
}
return pass;
}
if (canvas->mipmap) {
trackTexture(pass, pass->color[i], GPU_PHASE_TRANSFER, GPU_CACHE_TRANSFER_WRITE);
} else {
gpu_cache cache = GPU_CACHE_COLOR_WRITE | (canvas->loads[i] == LOAD_KEEP ? GPU_CACHE_COLOR_READ : 0);
trackTexture(pass, pass->color[i], GPU_PHASE_COLOR, cache);
}
}
void lovrPassDestroy(void* ref) {
//
}
if (pass->depth) {
if (canvas->mipmap && pass->depth->info.mipmaps > 1) {
trackTexture(pass, pass->depth, GPU_PHASE_TRANSFER, GPU_CACHE_TRANSFER_WRITE);
} else {
gpu_phase phase = canvas->depth.load == LOAD_KEEP ? GPU_PHASE_DEPTH_EARLY : GPU_PHASE_DEPTH_LATE;
gpu_cache cache = GPU_CACHE_DEPTH_WRITE | (canvas->depth.load == LOAD_KEEP ? GPU_CACHE_DEPTH_READ : 0);
trackTexture(pass, pass->depth, phase, cache);
}
}
const PassInfo* lovrPassGetInfo(Pass* pass) {
return &pass->info;
gpu_render_begin(pass->stream, &pass->target);
lovrPassSetViewport(pass, pass->pipeline->viewport, pass->pipeline->depthRange);
lovrPassSetScissor(pass, pass->pipeline->scissor);
// The default vertex buffer is always in the first slot, used for default attribute values
gpu_buffer* buffers[] = { state.defaultBuffer->gpu, state.defaultBuffer->gpu };
gpu_bind_vertex_buffers(pass->stream, buffers, NULL, 0, 2);
} else if (pass->info.type == PASS_COMPUTE) {
gpu_compute_begin(pass->stream);
}
}
void lovrPassGetViewMatrix(Pass* pass, uint32_t index, float* viewMatrix) {
@ -3622,7 +3793,10 @@ void lovrPassSetViewport(Pass* pass, float viewport[4], float depthRange[2]) {
}
void lovrPassSetWinding(Pass* pass, Winding winding) {
if (pass->cameraCount > 0 && pass->cameras[0].projection[5] > 0.f) winding = !winding; // Handedness requires winding flip
if (pass->cameraCount > 0 && pass->cameras[0].projection[5] > 0.f) { // Handedness change needs winding flip
winding = !winding;
}
pass->pipeline->dirty |= pass->pipeline->info.rasterizer.winding != (gpu_winding) winding;
pass->pipeline->info.rasterizer.winding = (gpu_winding) winding;
}
@ -5156,13 +5330,6 @@ static void* tempAlloc(size_t size) {
return state.allocator.memory + cursor;
}
static void* tempGrow(void* p, size_t size) {
if (size == 0) return NULL;
void* new = tempAlloc(size);
if (!p) return new;
return memcpy(new, p, size >> 1);
}
static uint32_t tempPush(void) {
return state.allocator.cursor;
}
@ -5183,37 +5350,11 @@ static void beginFrame(void) {
state.active = true;
state.tick = gpu_begin();
state.stream = gpu_stream_begin("Internal uploads");
state.stream = gpu_stream_begin();
state.allocator.cursor = 0;
processReadbacks();
}
// Clean up ALL passes created during the frame, even unsubmitted ones
static void cleanupPasses(void) {
for (size_t i = 0; i < state.passes.length; i++) {
Pass* pass = state.passes.data[i];
for (size_t j = 0; j < pass->access.length; j++) {
Access* access = &pass->access.data[j];
lovrRelease(access->buffer, lovrBufferDestroy);
lovrRelease(access->texture, lovrTextureDestroy);
}
if (pass->info.type == PASS_RENDER) {
for (size_t j = 0; j <= pass->pipelineIndex; j++) {
lovrRelease(pass->pipelines[j].font, lovrFontDestroy);
lovrRelease(pass->pipelines[j].sampler, lovrSamplerDestroy);
lovrRelease(pass->pipelines[j].shader, lovrShaderDestroy);
lovrRelease(pass->pipelines[j].material, lovrMaterialDestroy);
pass->pipelines[j].font = NULL;
pass->pipelines[j].sampler = NULL;
pass->pipelines[j].shader = NULL;
pass->pipelines[j].material = NULL;
}
}
}
}
static void processReadbacks(void) {
while (state.oldestReadback && gpu_is_complete(state.oldestReadback->tick)) {
Readback* readback = state.oldestReadback;
@ -5312,53 +5453,13 @@ static gpu_bundle* getBundle(uint32_t layoutIndex) {
return pool->bundles;
}
// Note that we are technically not doing synchronization correctly for temporary attachments. It
// is very unlikely to be a problem in practice, though it should still be fixed for correctness.
static gpu_texture* getAttachment(uint32_t size[2], uint32_t layers, TextureFormat format, bool srgb, uint32_t samples) {
uint16_t key[] = { size[0], size[1], layers, format, srgb, samples };
uint32_t hash = (uint32_t) hash64(key, sizeof(key));
// Find a matching attachment that hasn't been used this frame
for (uint32_t i = 0; i < state.attachments.length; i++) {
if (state.attachments.data[i].hash == hash && state.attachments.data[i].tick != state.tick) {
return state.attachments.data[i].texture;
}
}
// Find something to evict
TempAttachment* attachment = NULL;
for (uint32_t i = 0; i < state.attachments.length; i++) {
if (state.tick - state.attachments.data[i].tick > 16) {
attachment = &state.attachments.data[i];
break;
}
}
if (attachment) {
gpu_texture_destroy(attachment->texture);
} else {
arr_expand(&state.attachments, 1);
attachment = &state.attachments.data[state.attachments.length++];
attachment->texture = calloc(1, gpu_sizeof_texture());
lovrAssert(attachment->texture, "Out of memory");
}
gpu_texture_info info = {
.type = GPU_TEXTURE_ARRAY,
.format = (gpu_texture_format) format,
.size[0] = size[0],
.size[1] = size[1],
.size[2] = layers,
.mipmaps = 1,
.samples = samples,
.usage = GPU_TEXTURE_RENDER | GPU_TEXTURE_TRANSIENT,
.srgb = srgb
};
lovrAssert(gpu_texture_init(attachment->texture, &info), "Failed to create scratch texture");
attachment->hash = hash;
attachment->tick = state.tick;
return attachment->texture;
uint32_t convertTextureUsage(uint32_t usage) {
return
((usage & TEXTURE_SAMPLE) ? GPU_TEXTURE_SAMPLE : 0) |
((usage & TEXTURE_RENDER) ? GPU_TEXTURE_RENDER : 0) |
((usage & TEXTURE_STORAGE) ? GPU_TEXTURE_STORAGE : 0) |
((usage & TEXTURE_TRANSFER) ? GPU_TEXTURE_COPY_SRC | GPU_TEXTURE_COPY_DST : 0) |
((usage == TEXTURE_RENDER) ? GPU_TEXTURE_TRANSIENT : 0);
}
// Returns number of bytes of a 3D texture region of a given format

View File

@ -20,6 +20,13 @@ typedef struct Readback Readback;
typedef struct Tally Tally;
typedef struct Pass Pass;
typedef struct {
bool debug;
bool vsync;
bool stencil;
bool antialias;
} GraphicsConfig;
typedef struct {
uint32_t deviceId;
uint32_t vendorId;
@ -90,7 +97,7 @@ enum {
TEXTURE_FEATURE_BLIT_DST = (1 << 7)
};
bool lovrGraphicsInit(bool debug, bool vsync);
bool lovrGraphicsInit(GraphicsConfig* config);
void lovrGraphicsDestroy(void);
void lovrGraphicsGetDevice(GraphicsDevice* device);
@ -99,9 +106,6 @@ void lovrGraphicsGetLimits(GraphicsLimits* limits);
void lovrGraphicsGetStats(GraphicsStats* stats);
bool lovrGraphicsIsFormatSupported(uint32_t format, uint32_t features);
void lovrGraphicsGetBackground(float background[4]);
void lovrGraphicsSetBackground(float background[4]);
void lovrGraphicsSubmit(Pass** passes, uint32_t count);
void lovrGraphicsWait(void);
@ -543,9 +547,9 @@ typedef enum {
} Winding;
typedef enum {
LOAD_KEEP,
LOAD_CLEAR,
LOAD_DISCARD
LOAD_DISCARD,
LOAD_KEEP
} LoadAction;
typedef struct {
@ -556,6 +560,7 @@ typedef struct {
} DepthInfo;
typedef struct {
uint32_t count;
Texture* textures[4];
LoadAction loads[4];
float clears[4][4];
@ -570,13 +575,27 @@ typedef struct {
const char* label;
} PassInfo;
Pass* lovrGraphicsGetPass(PassInfo* info);
Pass* lovrGraphicsGetWindowPass(void);
Pass* lovrPassCreate(PassInfo* info);
void lovrPassDestroy(void* ref);
const PassInfo* lovrPassGetInfo(Pass* pass);
uint32_t lovrPassGetWidth(Pass* pass);
uint32_t lovrPassGetHeight(Pass* pass);
uint32_t lovrPassGetViewCount(Pass* pass);
uint32_t lovrPassGetSampleCount(Pass* pass);
void lovrPassGetTarget(Pass* pass, Texture* color[4], Texture** depth);
void lovrPassSetTarget(Pass* pass, Texture* color[4], Texture* depth);
void lovrPassGetClear(Pass* pass, float color[4][4], float* depth, uint8_t* stencil);
void lovrPassSetClear(Pass* pass, float color[4][4], float depth, uint8_t stencil);
void lovrPassReset(Pass* pass);
void lovrPassGetViewMatrix(Pass* pass, uint32_t index, float viewMatrix[16]);
void lovrPassSetViewMatrix(Pass* pass, uint32_t index, float viewMatrix[16]);
void lovrPassGetProjection(Pass* pass, uint32_t index, float projection[16]);
void lovrPassSetProjection(Pass* pass, uint32_t index, float projection[16]);
void lovrPassPush(Pass* pass, StackType stack);
void lovrPassPop(Pass* pass, StackType stack);
void lovrPassOrigin(Pass* pass);
@ -584,6 +603,7 @@ void lovrPassTranslate(Pass* pass, float* translation);
void lovrPassRotate(Pass* pass, float* rotation);
void lovrPassScale(Pass* pass, float* scale);
void lovrPassTransform(Pass* pass, float* transform);
void lovrPassSetAlphaToCoverage(Pass* pass, bool enabled);
void lovrPassSetBlendMode(Pass* pass, BlendMode mode, BlendAlphaMode alphaMode);
void lovrPassSetColor(Pass* pass, float color[4]);
@ -604,10 +624,12 @@ void lovrPassSetStencilWrite(Pass* pass, StencilAction actions[3], uint8_t value
void lovrPassSetViewport(Pass* pass, float viewport[4], float depthRange[2]);
void lovrPassSetWinding(Pass* pass, Winding winding);
void lovrPassSetWireframe(Pass* pass, bool wireframe);
void lovrPassSendBuffer(Pass* pass, const char* name, size_t length, uint32_t slot, Buffer* buffer, uint32_t offset, uint32_t extent);
void lovrPassSendTexture(Pass* pass, const char* name, size_t length, uint32_t slot, Texture* texture);
void lovrPassSendSampler(Pass* pass, const char* name, size_t length, uint32_t slot, Sampler* sampler);
void lovrPassSendValue(Pass* pass, const char* name, size_t length, void** data, FieldType* type);
void lovrPassPoints(Pass* pass, uint32_t count, float** vertices);
void lovrPassLine(Pass* pass, uint32_t count, float** vertices);
void lovrPassPlane(Pass* pass, float* transform, DrawStyle style, uint32_t cols, uint32_t rows);
@ -625,7 +647,9 @@ void lovrPassMonkey(Pass* pass, float* transform);
void lovrPassDrawModel(Pass* pass, Model* model, float* transform, uint32_t node, bool recurse, uint32_t instances);
void lovrPassMesh(Pass* pass, Buffer* vertices, Buffer* indices, float* transform, uint32_t start, uint32_t count, uint32_t instances);
void lovrPassMultimesh(Pass* pass, Buffer* vertices, Buffer* indices, Buffer* indirect, uint32_t count, uint32_t offset, uint32_t stride);
void lovrPassCompute(Pass* pass, uint32_t x, uint32_t y, uint32_t z, Buffer* indirect, uint32_t offset);
void lovrPassClearBuffer(Pass* pass, Buffer* buffer, uint32_t offset, uint32_t extent);
void lovrPassClearTexture(Pass* pass, Texture* texture, float value[4], uint32_t layer, uint32_t layerCount, uint32_t level, uint32_t levelCount);
void* lovrPassCopyDataToBuffer(Pass* pass, Buffer* buffer, uint32_t offset, uint32_t extent);
@ -638,5 +662,6 @@ void lovrPassMipmap(Pass* pass, Texture* texture, uint32_t base, uint32_t count)
Readback* lovrPassReadBuffer(Pass* pass, Buffer* buffer, uint32_t index, uint32_t count);
Readback* lovrPassReadTexture(Pass* pass, Texture* texture, uint32_t offset[4], uint32_t extent[3]);
Readback* lovrPassReadTally(Pass* pass, Tally* tally, uint32_t index, uint32_t count);
void lovrPassTick(Pass* pass, Tally* tally, uint32_t index);
void lovrPassTock(Pass* pass, Tally* tally, uint32_t index);

View File

@ -4,14 +4,14 @@
HeadsetInterface* lovrHeadsetInterface = NULL;
static bool initialized = false;
bool lovrHeadsetInit(HeadsetDriver* drivers, size_t count, float supersample, float offset, uint32_t msaa, bool overlay) {
bool lovrHeadsetInit(HeadsetConfig* config) {
if (initialized) return false;
initialized = true;
for (size_t i = 0; i < count; i++) {
for (size_t i = 0; i < config->driverCount; i++) {
HeadsetInterface* interface = NULL;
switch (drivers[i]) {
switch (config->drivers[i]) {
#ifdef LOVR_USE_DESKTOP
case DRIVER_DESKTOP: interface = &lovrHeadsetDesktopDriver; break;
#endif
@ -24,7 +24,7 @@ bool lovrHeadsetInit(HeadsetDriver* drivers, size_t count, float supersample, fl
default: continue;
}
if (interface->init(supersample, offset, msaa, overlay)) {
if (interface->init(config)) {
lovrHeadsetInterface = interface;
break;
}

View File

@ -9,6 +9,7 @@
struct Model;
struct ModelData;
struct Texture;
struct Pass;
typedef enum {
DRIVER_DESKTOP,
@ -16,6 +17,16 @@ typedef enum {
DRIVER_WEBXR
} HeadsetDriver;
typedef struct {
HeadsetDriver* drivers;
size_t driverCount;
float supersample;
float offset;
bool stencil;
bool antialias;
bool overlay;
} HeadsetConfig;
typedef enum {
ORIGIN_HEAD,
ORIGIN_FLOOR
@ -110,7 +121,7 @@ typedef struct HeadsetInterface {
void (*getVulkanPhysicalDevice)(void* instance, uintptr_t physicalDevice);
uint32_t (*createVulkanInstance)(void* instanceCreateInfo, void* allocator, uintptr_t instance, void* getInstanceProcAddr);
uint32_t (*createVulkanDevice)(void* instance, void* deviceCreateInfo, void* allocator, uintptr_t device, void* getInstanceProcAddr);
bool (*init)(float supersample, float offset, uint32_t msaa, bool overlay);
bool (*init)(HeadsetConfig* config);
void (*start)(void);
void (*destroy)(void);
bool (*getName)(char* name, size_t length);
@ -138,6 +149,7 @@ typedef struct HeadsetInterface {
struct ModelData* (*newModelData)(Device device, bool animated);
bool (*animate)(Device device, struct Model* model);
struct Texture* (*getTexture)(void);
struct Pass* (*getPass)(void);
void (*submit)(void);
bool (*isFocused)(void);
double (*update)(void);
@ -151,5 +163,5 @@ extern HeadsetInterface lovrHeadsetDesktopDriver;
// Active driver
extern HeadsetInterface* lovrHeadsetInterface;
bool lovrHeadsetInit(HeadsetDriver* drivers, size_t count, float supersample, float offset, uint32_t msaa, bool overlay);
bool lovrHeadsetInit(HeadsetConfig* config);
void lovrHeadsetDestroy(void);

View File

@ -36,8 +36,8 @@ static void onFocus(bool focused) {
lovrEventPush((Event) { .type = EVENT_FOCUS, .data.boolean = { focused } });
}
static bool desktop_init(float supersample, float offset, uint32_t msaa, bool overlay) {
state.offset = offset;
static bool desktop_init(HeadsetConfig* config) {
state.offset = config->offset;
state.clipNear = .01f;
state.clipFar = 0.f;
state.prevDisplayTime = os_get_time();
@ -192,6 +192,10 @@ static Texture* desktop_getTexture(void) {
return NULL;
}
static Pass* desktop_getPass(void) {
return lovrGraphicsGetWindowPass();
}
static void desktop_submit(void) {
//
}
@ -320,6 +324,7 @@ HeadsetInterface lovrHeadsetDesktopDriver = {
.newModelData = desktop_newModelData,
.animate = desktop_animate,
.getTexture = desktop_getTexture,
.getPass = desktop_getPass,
.submit = desktop_submit,
.isFocused = desktop_isFocused,
.update = desktop_update

View File

@ -134,6 +134,7 @@ enum {
};
static struct {
HeadsetConfig config;
XrInstance instance;
XrSystemId system;
XrSession session;
@ -146,17 +147,16 @@ static struct {
XrCompositionLayerProjectionView layerViews[2];
XrFrameState frameState;
Texture* textures[MAX_IMAGES];
Pass* pass;
double lastDisplayTime;
uint32_t imageIndex;
uint32_t imageCount;
uint32_t msaa;
uint32_t textureIndex;
uint32_t textureCount;
uint32_t width;
uint32_t height;
float clipNear;
float clipFar;
float offset;
bool waited;
bool hasImage;
bool began;
XrActionSet actionSet;
XrAction actions[MAX_ACTIONS];
XrPath actionFilters[MAX_DEVICES];
@ -285,9 +285,8 @@ static uint32_t openxr_createVulkanDevice(void* instance, void* deviceCreateInfo
static void openxr_destroy();
static bool openxr_init(float supersample, float offset, uint32_t msaa, bool overlay) {
state.msaa = msaa;
state.offset = offset;
static bool openxr_init(HeadsetConfig* config) {
state.config = *config;
#ifdef __ANDROID__
static PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
@ -333,7 +332,7 @@ static bool openxr_init(float supersample, float offset, uint32_t msaa, bool ove
{ "XR_FB_display_refresh_rate", &state.features.refreshRate, false },
{ "XR_FB_hand_tracking_aim", &state.features.handTrackingAim, false },
{ "XR_FB_hand_tracking_mesh", &state.features.handTrackingMesh, false },
{ "XR_EXTX_overlay", &state.features.overlay, !overlay },
{ "XR_EXTX_overlay", &state.features.overlay, !config->overlay },
{ "XR_HTCX_vive_tracker_interaction", &state.features.viveTrackers, false },
};
@ -427,8 +426,8 @@ static bool openxr_init(float supersample, float offset, uint32_t msaa, bool ove
return false;
}
state.width = MIN(views[0].recommendedImageRectWidth * supersample, views[0].maxImageRectWidth);
state.height = MIN(views[0].recommendedImageRectHeight * supersample, views[0].maxImageRectHeight);
state.width = MIN(views[0].recommendedImageRectWidth * config->supersample, views[0].maxImageRectWidth);
state.height = MIN(views[0].recommendedImageRectHeight * config->supersample, views[0].maxImageRectHeight);
}
{ // Actions
@ -852,7 +851,7 @@ static void openxr_start(void) {
if (XR_FAILED(xrCreateReferenceSpace(state.session, &info, &state.referenceSpace))) {
info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
info.poseInReferenceSpace.position.y = -state.offset;
info.poseInReferenceSpace.position.y = -state.config.offset;
XR(xrCreateReferenceSpace(state.session, &info, &state.referenceSpace));
}
@ -895,7 +894,7 @@ static void openxr_start(void) {
XrSwapchainCreateInfo info = {
.type = XR_TYPE_SWAPCHAIN_CREATE_INFO,
.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT,
.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT,
.format = VK_FORMAT_R8G8B8A8_SRGB,
.width = state.width,
.height = state.height,
@ -906,9 +905,9 @@ static void openxr_start(void) {
};
XR(xrCreateSwapchain(state.session, &info, &state.swapchain));
XR(xrEnumerateSwapchainImages(state.swapchain, MAX_IMAGES, &state.imageCount, (XrSwapchainImageBaseHeader*) images));
XR(xrEnumerateSwapchainImages(state.swapchain, MAX_IMAGES, &state.textureCount, (XrSwapchainImageBaseHeader*) images));
for (uint32_t i = 0; i < state.imageCount; i++) {
for (uint32_t i = 0; i < state.textureCount; i++) {
state.textures[i] = lovrTextureCreate(&(TextureInfo) {
.type = TEXTURE_ARRAY,
.format = FORMAT_RGBA8,
@ -918,11 +917,23 @@ static void openxr_start(void) {
.layers = 2,
.mipmaps = 1,
.samples = 1,
.usage = TEXTURE_RENDER,
.usage = TEXTURE_RENDER | TEXTURE_SAMPLE,
.handle = (uintptr_t) images[i].image
});
}
state.pass = lovrPassCreate(&(PassInfo) {
.type = PASS_RENDER,
.canvas.count = 1,
.canvas.textures[0] = state.textures[0],
.canvas.loads[0] = LOAD_CLEAR,
.canvas.clears[0] = { 0.f, 0.f, 0.f, 0.f },
.canvas.depth.format = state.config.stencil ? FORMAT_D32FS8 : FORMAT_D32F,
.canvas.depth.load = LOAD_CLEAR,
.canvas.depth.clear = 0.f,
.canvas.samples = state.config.antialias ? 4 : 1
});
XrCompositionLayerFlags layerFlags = 0;
if (state.features.overlay) {
@ -952,10 +963,12 @@ static void openxr_start(void) {
}
static void openxr_destroy(void) {
for (uint32_t i = 0; i < state.imageCount; i++) {
for (uint32_t i = 0; i < state.textureCount; i++) {
lovrRelease(state.textures[i], lovrTextureDestroy);
}
lovrRelease(state.pass, lovrPassDestroy);
for (size_t i = 0; i < MAX_ACTIONS; i++) {
if (state.actions[i]) {
xrDestroyAction(state.actions[i]);
@ -1650,57 +1663,74 @@ static bool openxr_animate(Device device, struct Model* model) {
}
static Texture* openxr_getTexture(void) {
if (!SESSION_ACTIVE(state.sessionState)) {
return NULL;
}
return state.began && state.frameState.shouldRender ? state.textures[state.textureIndex] : NULL;
}
if (state.hasImage) {
return state.textures[state.imageIndex];
static Pass* openxr_getPass(void) {
if (state.began) {
return state.frameState.shouldRender ? state.pass : NULL;
}
XrFrameBeginInfo beginfo = { .type = XR_TYPE_FRAME_BEGIN_INFO };
XR(xrBeginFrame(state.session, &beginfo));
state.began = true;
if (!state.frameState.shouldRender) {
return NULL;
}
XrSwapchainImageWaitInfo waitInfo = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, .timeout = XR_INFINITE_DURATION };
XR(xrAcquireSwapchainImage(state.swapchain, NULL, &state.imageIndex));
XR(xrAcquireSwapchainImage(state.swapchain, NULL, &state.textureIndex));
XR(xrWaitSwapchainImage(state.swapchain, &waitInfo));
lovrPassSetTarget(state.pass, &state.textures[state.textureIndex], NULL);
lovrPassReset(state.pass);
uint32_t count;
XrView views[2];
getViews(views, &count);
state.layerViews[0].pose = views[0].pose;
state.layerViews[0].fov = views[0].fov;
state.layerViews[1].pose = views[1].pose;
state.layerViews[1].fov = views[1].fov;
state.hasImage = true;
return state.textures[state.imageIndex];
for (uint32_t i = 0; i < count; i++) {
state.layerViews[i].pose = views[i].pose;
state.layerViews[i].fov = views[i].fov;
float viewMatrix[16];
mat4_fromQuat(viewMatrix, &views[i].pose.orientation.x);
memcpy(viewMatrix + 12, &views[i].pose.position.x, 3 * sizeof(float));
mat4_invert(viewMatrix);
float projection[16];
XrFovf* fov = &views[i].fov;
mat4_fov(projection, -fov->angleLeft, fov->angleRight, fov->angleUp, -fov->angleDown, state.clipNear, state.clipFar);
lovrPassSetViewMatrix(state.pass, i, viewMatrix);
lovrPassSetProjection(state.pass, i, projection);
}
return state.pass;
}
static void openxr_submit(void) {
if (!SESSION_ACTIVE(state.sessionState)) {
if (!state.began || !SESSION_ACTIVE(state.sessionState)) {
state.waited = false;
return;
}
XrFrameEndInfo endInfo = {
XrFrameEndInfo info = {
.type = XR_TYPE_FRAME_END_INFO,
.displayTime = state.frameState.predictedDisplayTime,
.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE,
.layers = (const XrCompositionLayerBaseHeader*[1]) { (XrCompositionLayerBaseHeader*) &state.layers[0] },
.layerCount = state.hasImage ? 1 : 0
.layers = (const XrCompositionLayerBaseHeader*[1]) {
(XrCompositionLayerBaseHeader*) &state.layers[0]
}
};
if (state.hasImage) {
if (state.frameState.shouldRender) {
XR(xrReleaseSwapchainImage(state.swapchain, NULL));
state.hasImage = false;
info.layerCount = 1;
}
XR(xrEndFrame(state.session, &endInfo));
XR(xrEndFrame(state.session, &info));
state.began = false;
state.waited = false;
}
@ -1709,7 +1739,7 @@ static bool openxr_isFocused(void) {
}
static double openxr_update(void) {
if (state.waited && !state.hasImage) return openxr_getDeltaTime();
if (state.waited) return openxr_getDeltaTime();
XrEventDataBuffer e; // Not using designated initializers here to avoid an implicit 4k zero
e.type = XR_TYPE_EVENT_DATA_BUFFER;
@ -1813,6 +1843,7 @@ HeadsetInterface lovrHeadsetOpenXRDriver = {
.newModelData = openxr_newModelData,
.animate = openxr_animate,
.getTexture = openxr_getTexture,
.getPass = openxr_getPass,
.submit = openxr_submit,
.isFocused = openxr_isFocused,
.update = openxr_update