mirror of https://github.com/bjornbytes/lovr.git
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:
parent
289f08b0df
commit
4ee092e81b
33
etc/boot.lua
33
etc/boot.lua
|
@ -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)
|
||||
|
|
|
@ -14,7 +14,7 @@ function lovr.load()
|
|||
return
|
||||
end
|
||||
|
||||
lovr.graphics.setBackground(0x20232c)
|
||||
lovr.graphics.getWindowPass():setClear(0x20232c)
|
||||
|
||||
--[=[
|
||||
logo = lovr.graphics.newShader([[
|
||||
|
|
|
@ -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 }
|
||||
};
|
||||
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue