OculusVR: updated to SDK 1.3
This commit is contained in:
Родитель
ea613785e4
Коммит
bea6e81d12
|
@ -27,7 +27,7 @@ Supported rendering backends:
|
|||
|
||||
Supported HMD:
|
||||
|
||||
* OculusVR (0.4.2+)
|
||||
* OculusVR (1.3.0)
|
||||
|
||||
Supported platforms:
|
||||
|
||||
|
|
|
@ -143,11 +143,7 @@ class ExampleCubes : public entry::AppI
|
|||
{
|
||||
float view[16];
|
||||
bx::mtxQuatTranslationHMD(view, hmd->eye[0].rotation, eye);
|
||||
|
||||
float proj[16];
|
||||
bx::mtxProj(proj, hmd->eye[0].fov, 0.1f, 100.0f);
|
||||
|
||||
bgfx::setViewTransform(0, view, proj);
|
||||
bgfx::setViewTransform(0, view, hmd->eye[0].projection);
|
||||
|
||||
// Set view 0 default viewport.
|
||||
//
|
||||
|
|
|
@ -577,11 +577,7 @@ class ExampleMetaballs : public entry::AppI
|
|||
{
|
||||
float view[16];
|
||||
bx::mtxQuatTranslationHMD(view, hmd->eye[0].rotation, eye);
|
||||
|
||||
float proj[16];
|
||||
bx::mtxProj(proj, hmd->eye[0].fov, 0.1f, 100.0f);
|
||||
|
||||
bgfx::setViewTransform(0, view, proj);
|
||||
bgfx::setViewTransform(0, view, hmd->eye[0].projection);
|
||||
|
||||
// Set view 0 default viewport.
|
||||
//
|
||||
|
|
|
@ -91,11 +91,7 @@ class ExampleMesh : public entry::AppI
|
|||
{
|
||||
float view[16];
|
||||
bx::mtxQuatTranslationHMD(view, hmd->eye[0].rotation, eye);
|
||||
|
||||
float proj[16];
|
||||
bx::mtxProj(proj, hmd->eye[0].fov, 0.1f, 100.0f);
|
||||
|
||||
bgfx::setViewTransform(0, view, proj);
|
||||
bgfx::setViewTransform(0, view, hmd->eye[0].projection);
|
||||
|
||||
// Set view 0 default viewport.
|
||||
//
|
||||
|
|
|
@ -160,11 +160,7 @@ class ExampleInstancing : public entry::AppI
|
|||
{
|
||||
float view[16];
|
||||
bx::mtxQuatTranslationHMD(view, hmd->eye[0].rotation, eye);
|
||||
|
||||
float proj[16];
|
||||
bx::mtxProj(proj, hmd->eye[0].fov, 0.1f, 100.0f);
|
||||
|
||||
bgfx::setViewTransform(0, view, proj);
|
||||
bgfx::setViewTransform(0, view, hmd->eye[0].projection);
|
||||
|
||||
// Set view 0 default viewport.
|
||||
//
|
||||
|
|
|
@ -225,11 +225,7 @@ class ExampleBump : public entry::AppI
|
|||
{
|
||||
float view[16];
|
||||
bx::mtxQuatTranslationHMD(view, hmd->eye[0].rotation, eye);
|
||||
|
||||
float proj[16];
|
||||
bx::mtxProj(proj, hmd->eye[0].fov, 0.1f, 100.0f);
|
||||
|
||||
bgfx::setViewTransform(0, view, proj);
|
||||
bgfx::setViewTransform(0, view, hmd->eye[0].projection);
|
||||
|
||||
// Set view 0 default viewport.
|
||||
//
|
||||
|
|
|
@ -174,11 +174,7 @@ class ExampleLod : public entry::AppI
|
|||
{
|
||||
float view[16];
|
||||
bx::mtxQuatTranslationHMD(view, hmd->eye[0].rotation, eye);
|
||||
|
||||
float proj[16];
|
||||
bx::mtxProj(proj, hmd->eye[0].fov, 0.1f, 100.0f);
|
||||
|
||||
bgfx::setViewTransform(0, view, proj);
|
||||
bgfx::setViewTransform(0, view, hmd->eye[0].projection);
|
||||
|
||||
// Set view 0 default viewport.
|
||||
//
|
||||
|
|
|
@ -305,11 +305,7 @@ int _main_(int _argc, char** _argv)
|
|||
|
||||
float tmp[16];
|
||||
bx::mtxMul(tmp, view, viewHead);
|
||||
|
||||
float proj[16];
|
||||
bx::mtxProj(proj, hmd->eye[0].fov, 0.1f, 10000.0f);
|
||||
|
||||
bgfx::setViewTransform(0, tmp, proj);
|
||||
bgfx::setViewTransform(0, tmp, hmd->eye[0].projection);
|
||||
|
||||
// Set view 0 default viewport.
|
||||
//
|
||||
|
|
|
@ -181,13 +181,10 @@ class ExampleOcclusion : public entry::AppI
|
|||
float tmp[16];
|
||||
bx::mtxMul(tmp, view, viewHead);
|
||||
|
||||
float proj[16];
|
||||
bx::mtxProj(proj, hmd->eye[0].fov, 0.1f, 10000.0f);
|
||||
|
||||
bgfx::setViewTransform(0, tmp, proj);
|
||||
bgfx::setViewTransform(0, tmp, hmd->eye[0].projection);
|
||||
bgfx::setViewRect(0, 0, 0, hmd->width, hmd->height);
|
||||
|
||||
bgfx::setViewTransform(1, tmp, proj);
|
||||
bgfx::setViewTransform(1, tmp, hmd->eye[1].projection);
|
||||
bgfx::setViewRect(1, 0, 0, hmd->width, hmd->height);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -615,6 +615,7 @@ namespace bgfx
|
|||
float translation[3]; //!< Eye translation.
|
||||
float fov[4]; //!< Field of view (up, down, left, right).
|
||||
float viewOffset[3]; //!< Eye view matrix translation adjustment.
|
||||
float projection[16]; //!< Eye projection matrix
|
||||
float pixelsPerTanAngle[2]; //!<
|
||||
};
|
||||
|
||||
|
@ -1914,7 +1915,7 @@ namespace bgfx
|
|||
///
|
||||
void setViewRect(uint8_t _id, uint16_t _x, uint16_t _y, uint16_t _width, uint16_t _height);
|
||||
|
||||
/// @attention C99 equivalent is `bgfx_set_view_rect_auto`.
|
||||
/// @attention C99 equivalent is `bgfx_set_view_rect_auto`.
|
||||
///
|
||||
void setViewRect(uint8_t _id, uint16_t _x, uint16_t _y, BackbufferRatio::Enum _ratio);
|
||||
|
||||
|
|
|
@ -178,42 +178,20 @@ function exampleProject(_name)
|
|||
"ws2_32",
|
||||
}
|
||||
|
||||
-- Check for LibOVR 5.0+
|
||||
if os.isdir(path.join(os.getenv("OVR_DIR"), "LibOVR/Lib/Windows/Win32/Debug/VS2012")) then
|
||||
configuration { "x32", "Debug" }
|
||||
libdirs { path.join("$(OVR_DIR)/LibOVR/Lib/Windows/Win32/Debug", _ACTION) }
|
||||
|
||||
configuration { "x32", "Debug" }
|
||||
libdirs { path.join("$(OVR_DIR)/LibOVR/Lib/Windows/Win32/Debug", _ACTION) }
|
||||
configuration { "x32", "Release" }
|
||||
libdirs { path.join("$(OVR_DIR)/LibOVR/Lib/Windows/Win32/Release", _ACTION) }
|
||||
|
||||
configuration { "x32", "Release" }
|
||||
libdirs { path.join("$(OVR_DIR)/LibOVR/Lib/Windows/Win32/Release", _ACTION) }
|
||||
configuration { "x64", "Debug" }
|
||||
libdirs { path.join("$(OVR_DIR)/LibOVR/Lib/Windows/x64/Debug", _ACTION) }
|
||||
|
||||
configuration { "x64", "Debug" }
|
||||
libdirs { path.join("$(OVR_DIR)/LibOVR/Lib/Windows/x64/Debug", _ACTION) }
|
||||
configuration { "x64", "Release" }
|
||||
libdirs { path.join("$(OVR_DIR)/LibOVR/Lib/Windows/x64/Release", _ACTION) }
|
||||
|
||||
configuration { "x64", "Release" }
|
||||
libdirs { path.join("$(OVR_DIR)/LibOVR/Lib/Windows/x64/Release", _ACTION) }
|
||||
|
||||
configuration { "x32 or x64" }
|
||||
links { "libovr" }
|
||||
else
|
||||
configuration { "x32" }
|
||||
libdirs { path.join("$(OVR_DIR)/LibOVR/Lib/Win32", _ACTION) }
|
||||
|
||||
configuration { "x64" }
|
||||
libdirs { path.join("$(OVR_DIR)/LibOVR/Lib/x64", _ACTION) }
|
||||
|
||||
configuration { "x32", "Debug" }
|
||||
links { "libovrd" }
|
||||
|
||||
configuration { "x32", "Release" }
|
||||
links { "libovr" }
|
||||
|
||||
configuration { "x64", "Debug" }
|
||||
links { "libovr64d" }
|
||||
|
||||
configuration { "x64", "Release" }
|
||||
links { "libovr64" }
|
||||
end
|
||||
configuration { "x32 or x64" }
|
||||
links { "libovr" }
|
||||
|
||||
configuration {}
|
||||
end
|
||||
|
|
314
src/ovr.cpp
314
src/ovr.cpp
|
@ -9,17 +9,15 @@
|
|||
|
||||
namespace bgfx
|
||||
{
|
||||
#if OVR_VERSION <= OVR_VERSION_050
|
||||
# define OVR_EYE_BUFFER 100
|
||||
#else
|
||||
# define OVR_EYE_BUFFER 8
|
||||
#endif // OVR_VERSION...
|
||||
|
||||
OVR::OVR()
|
||||
: m_hmd(NULL)
|
||||
, m_isenabled(false)
|
||||
, m_debug(false)
|
||||
, m_mirror(NULL)
|
||||
, m_hmdFrameReady(-1)
|
||||
, m_frameIndex(0)
|
||||
, m_sensorSampleTime(0)
|
||||
{
|
||||
memset(m_eyeBuffers, 0, sizeof(m_eyeBuffers));
|
||||
}
|
||||
|
||||
OVR::~OVR()
|
||||
|
@ -29,225 +27,162 @@ namespace bgfx
|
|||
|
||||
void OVR::init()
|
||||
{
|
||||
bool initialized = !!ovr_Initialize();
|
||||
BX_WARN(initialized, "Unable to create OVR device.");
|
||||
if (!initialized)
|
||||
ovrResult initialized = ovr_Initialize(NULL);
|
||||
ovrGraphicsLuid luid;
|
||||
|
||||
BX_WARN(initialized == ovrSuccess, "Unable to create OVR device.");
|
||||
|
||||
if (initialized != ovrSuccess)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_hmd = ovrHmd_Create(0);
|
||||
if (NULL == m_hmd)
|
||||
initialized = ovr_Create(&m_hmd, &luid);
|
||||
if (initialized != ovrSuccess)
|
||||
{
|
||||
m_hmd = ovrHmd_CreateDebug(ovrHmd_DK2);
|
||||
BX_WARN(NULL != m_hmd, "Unable to create OVR device.");
|
||||
if (NULL == m_hmd)
|
||||
{
|
||||
return;
|
||||
}
|
||||
BX_WARN(initialized == ovrSuccess, "Unable to create OVR device.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_hmdDesc = ovr_GetHmdDesc(m_hmd);
|
||||
|
||||
BX_TRACE("HMD: %s, %s, firmware: %d.%d"
|
||||
, m_hmd->ProductName
|
||||
, m_hmd->Manufacturer
|
||||
, m_hmd->FirmwareMajor
|
||||
, m_hmd->FirmwareMinor
|
||||
, m_hmdDesc.ProductName
|
||||
, m_hmdDesc.Manufacturer
|
||||
, m_hmdDesc.FirmwareMajor
|
||||
, m_hmdDesc.FirmwareMinor
|
||||
);
|
||||
|
||||
ovrSizei sizeL = ovrHmd_GetFovTextureSize(m_hmd, ovrEye_Left, m_hmd->DefaultEyeFov[0], 1.0f);
|
||||
ovrSizei sizeR = ovrHmd_GetFovTextureSize(m_hmd, ovrEye_Right, m_hmd->DefaultEyeFov[1], 1.0f);
|
||||
m_rtSize.w = sizeL.w + sizeR.w + OVR_EYE_BUFFER;
|
||||
m_rtSize.h = bx::uint32_max(sizeL.h, sizeR.h);
|
||||
m_warning = true;
|
||||
ovrSizei sizeL = ovr_GetFovTextureSize(m_hmd, ovrEye_Left, m_hmdDesc.DefaultEyeFov[0], 1.0f);
|
||||
ovrSizei sizeR = ovr_GetFovTextureSize(m_hmd, ovrEye_Right, m_hmdDesc.DefaultEyeFov[1], 1.0f);
|
||||
m_hmdSize.w = sizeL.w + sizeR.w;
|
||||
m_hmdSize.h = bx::uint32_max(sizeL.h, sizeR.h);
|
||||
}
|
||||
|
||||
void OVR::shutdown()
|
||||
{
|
||||
BX_CHECK(!m_isenabled, "HMD not disabled.");
|
||||
ovrHmd_Destroy(m_hmd);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (m_eyeBuffers[i])
|
||||
{
|
||||
m_eyeBuffers[i]->destroy(m_hmd);
|
||||
BX_DELETE(g_allocator, m_eyeBuffers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_mirror)
|
||||
{
|
||||
m_mirror->destroy(m_hmd);
|
||||
BX_DELETE(g_allocator, m_mirror);
|
||||
}
|
||||
|
||||
ovr_Destroy(m_hmd);
|
||||
m_hmd = NULL;
|
||||
ovr_Shutdown();
|
||||
}
|
||||
|
||||
void OVR::getViewport(uint8_t _eye, Rect* _viewport)
|
||||
{
|
||||
_viewport->m_x = _eye * (m_rtSize.w + OVR_EYE_BUFFER + 1)/2;
|
||||
_viewport->m_x = 0;
|
||||
_viewport->m_y = 0;
|
||||
_viewport->m_width = (m_rtSize.w - OVR_EYE_BUFFER)/2;
|
||||
_viewport->m_height = m_rtSize.h;
|
||||
_viewport->m_width = m_eyeBuffers[_eye]->m_eyeTextureSize.w;
|
||||
_viewport->m_height = m_eyeBuffers[_eye]->m_eyeTextureSize.h;
|
||||
}
|
||||
|
||||
bool OVR::postReset(void* _nwh, ovrRenderAPIConfig* _config, bool _debug)
|
||||
void OVR::renderEyeStart(uint8_t _eye)
|
||||
{
|
||||
if (_debug)
|
||||
{
|
||||
switch (_config->Header.API)
|
||||
{
|
||||
#if BGFX_CONFIG_RENDERER_DIRECT3D11
|
||||
case ovrRenderAPI_D3D11:
|
||||
{
|
||||
ovrD3D11ConfigData* data = (ovrD3D11ConfigData*)_config;
|
||||
# if OVR_VERSION > OVR_VERSION_043
|
||||
m_rtSize = data->Header.BackBufferSize;
|
||||
# else
|
||||
m_rtSize = data->Header.RTSize;
|
||||
# endif // OVR_VERSION > OVR_VERSION_043
|
||||
}
|
||||
break;
|
||||
#endif // BGFX_CONFIG_RENDERER_DIRECT3D11
|
||||
|
||||
#if BGFX_CONFIG_RENDERER_OPENGL
|
||||
case ovrRenderAPI_OpenGL:
|
||||
{
|
||||
ovrGLConfigData* data = (ovrGLConfigData*)_config;
|
||||
# if OVR_VERSION > OVR_VERSION_043
|
||||
m_rtSize = data->Header.BackBufferSize;
|
||||
# else
|
||||
m_rtSize = data->Header.RTSize;
|
||||
# endif // OVR_VERSION > OVR_VERSION_043
|
||||
}
|
||||
break;
|
||||
#endif // BGFX_CONFIG_RENDERER_OPENGL
|
||||
|
||||
case ovrRenderAPI_None:
|
||||
default:
|
||||
BX_CHECK(false, "You should not be here!");
|
||||
break;
|
||||
}
|
||||
|
||||
m_debug = true;
|
||||
return false;
|
||||
}
|
||||
m_eyeBuffers[_eye]->onRender(m_hmd);
|
||||
}
|
||||
|
||||
bool OVR::postReset()
|
||||
{
|
||||
if (NULL == m_hmd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int eyeIdx = 0; eyeIdx < ovrEye_Count; eyeIdx++)
|
||||
{
|
||||
m_erd[eyeIdx] = ovr_GetRenderDesc(m_hmd, (ovrEyeType)eyeIdx, m_hmdDesc.DefaultEyeFov[eyeIdx]);
|
||||
}
|
||||
|
||||
m_isenabled = true;
|
||||
|
||||
ovrBool result;
|
||||
result = ovrHmd_AttachToWindow(m_hmd, _nwh, NULL, NULL);
|
||||
if (!result) { goto ovrError; }
|
||||
|
||||
ovrFovPort eyeFov[2] = { m_hmd->DefaultEyeFov[0], m_hmd->DefaultEyeFov[1] };
|
||||
result = ovrHmd_ConfigureRendering(m_hmd
|
||||
, _config
|
||||
, 0
|
||||
#if OVR_VERSION < OVR_VERSION_050
|
||||
| ovrDistortionCap_Chromatic // permanently enabled >= v5.0
|
||||
#endif
|
||||
| ovrDistortionCap_Vignette
|
||||
| ovrDistortionCap_TimeWarp
|
||||
| ovrDistortionCap_Overdrive
|
||||
| ovrDistortionCap_NoRestore
|
||||
| ovrDistortionCap_HqDistortion
|
||||
, eyeFov
|
||||
, m_erd
|
||||
);
|
||||
if (!result) { goto ovrError; }
|
||||
|
||||
ovrHmd_SetEnabledCaps(m_hmd
|
||||
, 0
|
||||
| ovrHmdCap_LowPersistence
|
||||
| ovrHmdCap_DynamicPrediction
|
||||
);
|
||||
|
||||
result = ovrHmd_ConfigureTracking(m_hmd
|
||||
, 0
|
||||
| ovrTrackingCap_Orientation
|
||||
| ovrTrackingCap_MagYawCorrection
|
||||
| ovrTrackingCap_Position
|
||||
, 0
|
||||
);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
ovrError:
|
||||
BX_TRACE("Failed to initialize OVR.");
|
||||
m_isenabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_warning = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OVR::postReset(const ovrTexture& _texture)
|
||||
{
|
||||
if (NULL != m_hmd)
|
||||
{
|
||||
m_texture[0] = _texture;
|
||||
m_texture[1] = _texture;
|
||||
|
||||
ovrRecti rect;
|
||||
rect.Pos.x = 0;
|
||||
rect.Pos.y = 0;
|
||||
rect.Size.w = (m_rtSize.w - OVR_EYE_BUFFER)/2;
|
||||
rect.Size.h = m_rtSize.h;
|
||||
|
||||
m_texture[0].Header.RenderViewport = rect;
|
||||
|
||||
rect.Pos.x += rect.Size.w + OVR_EYE_BUFFER;
|
||||
m_texture[1].Header.RenderViewport = rect;
|
||||
|
||||
m_timing = ovrHmd_BeginFrame(m_hmd, 0);
|
||||
#if OVR_VERSION > OVR_VERSION_042
|
||||
m_pose[0] = ovrHmd_GetHmdPosePerEye(m_hmd, ovrEye_Left);
|
||||
m_pose[1] = ovrHmd_GetHmdPosePerEye(m_hmd, ovrEye_Right);
|
||||
#else
|
||||
m_pose[0] = ovrHmd_GetEyePose(m_hmd, ovrEye_Left);
|
||||
m_pose[1] = ovrHmd_GetEyePose(m_hmd, ovrEye_Right);
|
||||
#endif // OVR_VERSION > OVR_VERSION_042
|
||||
}
|
||||
}
|
||||
|
||||
void OVR::preReset()
|
||||
{
|
||||
if (m_isenabled)
|
||||
{
|
||||
ovrHmd_EndFrame(m_hmd, m_pose, m_texture);
|
||||
ovrHmd_AttachToWindow(m_hmd, NULL, NULL, NULL);
|
||||
ovrHmd_ConfigureRendering(m_hmd, NULL, 0, NULL, NULL);
|
||||
// on window resize this will recreate the mirror texture in ovrPostReset
|
||||
m_mirror->destroy(m_hmd);
|
||||
BX_DELETE(g_allocator, m_mirror);
|
||||
m_mirror = NULL;
|
||||
m_isenabled = false;
|
||||
}
|
||||
|
||||
m_debug = false;
|
||||
}
|
||||
|
||||
bool OVR::swap(HMD& _hmd)
|
||||
void OVR::commitEye(uint8_t _eye)
|
||||
{
|
||||
if (m_isenabled)
|
||||
{
|
||||
m_hmdFrameReady = ovr_CommitTextureSwapChain(m_hmd, m_eyeBuffers[_eye]->m_swapTextureChain);
|
||||
}
|
||||
}
|
||||
|
||||
bool OVR::swap(HMD& _hmd, bool originBottomLeft)
|
||||
{
|
||||
_hmd.flags = BGFX_HMD_NONE;
|
||||
|
||||
if (NULL != m_hmd)
|
||||
{
|
||||
_hmd.flags |= BGFX_HMD_DEVICE_RESOLUTION;
|
||||
_hmd.deviceWidth = m_hmd->Resolution.w;
|
||||
_hmd.deviceHeight = m_hmd->Resolution.h;
|
||||
_hmd.deviceWidth = m_hmdDesc.Resolution.w;
|
||||
_hmd.deviceHeight = m_hmdDesc.Resolution.h;
|
||||
}
|
||||
|
||||
if (!m_isenabled)
|
||||
if (!m_isenabled || !OVR_SUCCESS(m_hmdFrameReady))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_hmd.flags |= BGFX_HMD_RENDERING;
|
||||
ovrHmd_EndFrame(m_hmd, m_pose, m_texture);
|
||||
|
||||
if (m_warning)
|
||||
// finish frame for current eye
|
||||
ovrViewScaleDesc viewScaleDesc;
|
||||
viewScaleDesc.HmdSpaceToWorldScaleInMeters = 1.0f;
|
||||
viewScaleDesc.HmdToEyeOffset[0] = m_hmdToEyeOffset[0];
|
||||
viewScaleDesc.HmdToEyeOffset[1] = m_hmdToEyeOffset[1];
|
||||
|
||||
// create the main eye layer
|
||||
ovrLayerEyeFov eyeLayer;
|
||||
eyeLayer.Header.Type = ovrLayerType_EyeFov;
|
||||
eyeLayer.Header.Flags = originBottomLeft ? ovrLayerFlag_TextureOriginAtBottomLeft : 0;
|
||||
|
||||
for (int eye = 0; eye < ovrEye_Count; eye++)
|
||||
{
|
||||
m_warning = !ovrHmd_DismissHSWDisplay(m_hmd);
|
||||
eyeLayer.ColorTexture[eye] = m_eyeBuffers[eye]->m_swapTextureChain;
|
||||
eyeLayer.Viewport[eye] = ::OVR::Recti(m_eyeBuffers[eye]->m_eyeTextureSize);
|
||||
eyeLayer.Fov[eye] = m_hmdDesc.DefaultEyeFov[eye];
|
||||
eyeLayer.RenderPose[eye] = m_pose[eye];
|
||||
eyeLayer.SensorSampleTime = m_sensorSampleTime;
|
||||
}
|
||||
|
||||
m_timing = ovrHmd_BeginFrame(m_hmd, 0);
|
||||
// append all the layers to global list
|
||||
ovrLayerHeader* layerList = &eyeLayer.Header;
|
||||
|
||||
#if OVR_VERSION > OVR_VERSION_042
|
||||
m_pose[0] = ovrHmd_GetHmdPosePerEye(m_hmd, ovrEye_Left);
|
||||
m_pose[1] = ovrHmd_GetHmdPosePerEye(m_hmd, ovrEye_Right);
|
||||
#else
|
||||
m_pose[0] = ovrHmd_GetEyePose(m_hmd, ovrEye_Left);
|
||||
m_pose[1] = ovrHmd_GetEyePose(m_hmd, ovrEye_Right);
|
||||
#endif // OVR_VERSION > OVR_VERSION_042
|
||||
ovr_SubmitFrame(m_hmd, m_frameIndex, NULL, &layerList, 1);
|
||||
|
||||
// perform mirror texture blit right after the entire frame is submitted to HMD
|
||||
m_mirror->blit(m_hmd);
|
||||
|
||||
m_hmdToEyeOffset[0] = m_erd[0].HmdToEyeOffset;
|
||||
m_hmdToEyeOffset[1] = m_erd[1].HmdToEyeOffset;
|
||||
|
||||
ovr_GetEyePoses(m_hmd, m_frameIndex, ovrTrue, m_hmdToEyeOffset, m_pose, &m_sensorSampleTime);
|
||||
|
||||
getEyePose(_hmd);
|
||||
|
||||
|
@ -258,7 +193,7 @@ ovrError:
|
|||
{
|
||||
if (NULL != m_hmd)
|
||||
{
|
||||
ovrHmd_RecenterPose(m_hmd);
|
||||
ovr_RecenterTrackingOrigin(m_hmd);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,44 +218,27 @@ ovrError:
|
|||
eye.fov[1] = erd.Fov.DownTan;
|
||||
eye.fov[2] = erd.Fov.LeftTan;
|
||||
eye.fov[3] = erd.Fov.RightTan;
|
||||
#if OVR_VERSION > OVR_VERSION_042
|
||||
eye.viewOffset[0] = erd.HmdToEyeViewOffset.x;
|
||||
eye.viewOffset[1] = erd.HmdToEyeViewOffset.y;
|
||||
eye.viewOffset[2] = erd.HmdToEyeViewOffset.z;
|
||||
#else
|
||||
eye.viewOffset[0] = erd.ViewAdjust.x;
|
||||
eye.viewOffset[1] = erd.ViewAdjust.y;
|
||||
eye.viewOffset[2] = erd.ViewAdjust.z;
|
||||
#endif // OVR_VERSION > OVR_VERSION_042
|
||||
|
||||
ovrMatrix4f eyeProj = ovrMatrix4f_Projection(m_erd[ii].Fov, 0.01f, 1000.0f, ovrProjection_LeftHanded);
|
||||
for (int jj = 0; jj < 4; ++jj)
|
||||
{
|
||||
for (int kk = 0; kk < 4; ++kk)
|
||||
{
|
||||
eye.projection[4 * jj + kk] = eyeProj.M[kk][jj];
|
||||
}
|
||||
}
|
||||
|
||||
eye.viewOffset[0] = erd.HmdToEyeOffset.x;
|
||||
eye.viewOffset[1] = erd.HmdToEyeOffset.y;
|
||||
eye.viewOffset[2] = erd.HmdToEyeOffset.z;
|
||||
|
||||
eye.pixelsPerTanAngle[0] = erd.PixelsPerTanAngleAtCenter.x;
|
||||
eye.pixelsPerTanAngle[1] = erd.PixelsPerTanAngleAtCenter.y;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int ii = 0; ii < 2; ++ii)
|
||||
{
|
||||
_hmd.eye[ii].rotation[0] = 0.0f;
|
||||
_hmd.eye[ii].rotation[1] = 0.0f;
|
||||
_hmd.eye[ii].rotation[2] = 0.0f;
|
||||
_hmd.eye[ii].rotation[3] = 1.0f;
|
||||
_hmd.eye[ii].translation[0] = 0.0f;
|
||||
_hmd.eye[ii].translation[1] = 0.0f;
|
||||
_hmd.eye[ii].translation[2] = 0.0f;
|
||||
_hmd.eye[ii].fov[0] = 1.32928634f;
|
||||
_hmd.eye[ii].fov[1] = 1.32928634f;
|
||||
_hmd.eye[ii].fov[2] = 0 == ii ? 1.05865765f : 1.09236801f;
|
||||
_hmd.eye[ii].fov[3] = 0 == ii ? 1.09236801f : 1.05865765f;
|
||||
_hmd.eye[ii].viewOffset[0] = 0 == ii ? 0.0355070010f : -0.0375000015f;
|
||||
_hmd.eye[ii].viewOffset[1] = 0.0f;
|
||||
_hmd.eye[ii].viewOffset[2] = 0 == ii ? 0.00150949787f : -0.00150949787f;
|
||||
_hmd.eye[ii].pixelsPerTanAngle[0] = 1;
|
||||
_hmd.eye[ii].pixelsPerTanAngle[1] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
_hmd.width = uint16_t(m_rtSize.w);
|
||||
_hmd.height = uint16_t(m_rtSize.h);
|
||||
_hmd.width = uint16_t(m_hmdSize.w);
|
||||
_hmd.height = uint16_t(m_hmdSize.h);
|
||||
}
|
||||
|
||||
} // namespace bgfx
|
||||
|
|
100
src/ovr.h
100
src/ovr.h
|
@ -14,36 +14,44 @@
|
|||
|
||||
# define OVR_VERSION_(_a, _b, _c) (_a * 10000 + _b * 100 + _c)
|
||||
# define OVR_VERSION OVR_VERSION_(OVR_PRODUCT_VERSION, OVR_MAJOR_VERSION, OVR_MINOR_VERSION)
|
||||
# define OVR_VERSION_042 OVR_VERSION_(0, 4, 2)
|
||||
# define OVR_VERSION_043 OVR_VERSION_(0, 4, 3)
|
||||
# define OVR_VERSION_044 OVR_VERSION_(0, 4, 4)
|
||||
# define OVR_VERSION_050 OVR_VERSION_(0, 5, 0)
|
||||
|
||||
# if OVR_VERSION < OVR_VERSION_050
|
||||
# include <OVR.h>
|
||||
# else
|
||||
# include <OVR_CAPI.h>
|
||||
# endif // OVR_VERSION < OVR_VERSION_050
|
||||
# include <OVR_CAPI.h>
|
||||
|
||||
# if BGFX_CONFIG_RENDERER_DIRECT3D11
|
||||
# if OVR_VERSION < OVR_VERSION_050
|
||||
# define OVR_D3D_VERSION 11
|
||||
# include <OVR_D3D.h>
|
||||
# else
|
||||
# include <OVR_CAPI_D3D.h>
|
||||
# endif
|
||||
# include <OVR_CAPI_D3D.h>
|
||||
# endif // BGFX_CONFIG_RENDERER_DIRECT3D11
|
||||
|
||||
# if BGFX_CONFIG_RENDERER_OPENGL
|
||||
# if OVR_VERSION < OVR_VERSION_050
|
||||
# include <OVR_GL.h>
|
||||
# else
|
||||
# include <OVR_CAPI_GL.h>
|
||||
# endif
|
||||
# include <OVR_CAPI_GL.h>
|
||||
# endif // BGFX_CONFIG_RENDERER_OPENGL
|
||||
|
||||
#include "Extras/OVR_Math.h"
|
||||
|
||||
namespace bgfx
|
||||
{
|
||||
// single eye buffer
|
||||
struct OVRBufferI
|
||||
{
|
||||
virtual ~OVRBufferI() {};
|
||||
virtual void onRender(const ovrSession &session) = 0;
|
||||
virtual void destroy(const ovrSession &session) = 0;
|
||||
|
||||
ovrSizei m_eyeTextureSize;
|
||||
ovrTextureSwapChain m_swapTextureChain;
|
||||
};
|
||||
|
||||
// mirrored window output
|
||||
struct OVRMirrorI
|
||||
{
|
||||
virtual ~OVRMirrorI() {};
|
||||
virtual void init(const ovrSession &session, int windowWidth, int windowHeight) = 0;
|
||||
virtual void destroy(const ovrSession &session) = 0;
|
||||
virtual void blit(const ovrSession &session) = 0;
|
||||
|
||||
ovrMirrorTexture m_mirrorTexture;
|
||||
ovrMirrorTextureDesc m_mirrorDesc;
|
||||
};
|
||||
|
||||
struct OVR
|
||||
{
|
||||
OVR();
|
||||
|
@ -59,37 +67,31 @@ namespace bgfx
|
|||
return m_isenabled;
|
||||
}
|
||||
|
||||
bool isDebug() const
|
||||
{
|
||||
return m_debug;
|
||||
}
|
||||
|
||||
void init();
|
||||
void shutdown();
|
||||
|
||||
void getViewport(uint8_t _eye, Rect* _viewport);
|
||||
bool postReset(void* _nwh, ovrRenderAPIConfig* _config, bool _debug = false);
|
||||
void postReset(const ovrTexture& _texture);
|
||||
void renderEyeStart(uint8_t _eye);
|
||||
bool postReset();
|
||||
void preReset();
|
||||
bool swap(HMD& _hmd);
|
||||
void commitEye(uint8_t _eye);
|
||||
bool swap(HMD& _hmd, bool originBottomLeft);
|
||||
void recenter();
|
||||
void getEyePose(HMD& _hmd);
|
||||
void getSize(uint32_t& _width, uint32_t& _height) const
|
||||
{
|
||||
_width = m_rtSize.w;
|
||||
_height = m_rtSize.h;
|
||||
}
|
||||
|
||||
ovrHmd m_hmd;
|
||||
ovrFrameTiming m_timing;
|
||||
ovrSession m_hmd;
|
||||
ovrHmdDesc m_hmdDesc;
|
||||
ovrEyeRenderDesc m_erd[2];
|
||||
ovrRecti m_rect[2];
|
||||
ovrPosef m_pose[2];
|
||||
ovrTexture m_texture[2];
|
||||
ovrSizei m_rtSize;
|
||||
bool m_warning;
|
||||
ovrRecti m_rect[2];
|
||||
ovrPosef m_pose[2];
|
||||
ovrVector3f m_hmdToEyeOffset[2];
|
||||
ovrSizei m_hmdSize;
|
||||
ovrResult m_hmdFrameReady;
|
||||
OVRBufferI *m_eyeBuffers[2];
|
||||
OVRMirrorI *m_mirror;
|
||||
long long m_frameIndex;
|
||||
double m_sensorSampleTime;
|
||||
bool m_isenabled;
|
||||
bool m_debug;
|
||||
};
|
||||
|
||||
} // namespace bgfx
|
||||
|
@ -139,7 +141,15 @@ namespace bgfx
|
|||
_viewport->m_height = 0;
|
||||
}
|
||||
|
||||
bool swap(HMD& _hmd)
|
||||
void commitEye(uint8_t /*_eye*/)
|
||||
{
|
||||
}
|
||||
|
||||
void renderEyeStart(uint8_t /*_eye*/)
|
||||
{
|
||||
}
|
||||
|
||||
bool swap(HMD& _hmd, bool /*originBottomLeft*/)
|
||||
{
|
||||
_hmd.flags = BGFX_HMD_NONE;
|
||||
getEyePose(_hmd);
|
||||
|
@ -155,12 +165,6 @@ namespace bgfx
|
|||
_hmd.width = 0;
|
||||
_hmd.height = 0;
|
||||
}
|
||||
|
||||
void getSize(uint32_t& _width, uint32_t& _height) const
|
||||
{
|
||||
_width = 0;
|
||||
_height = 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace bgfx
|
||||
|
|
|
@ -601,6 +601,170 @@ namespace bgfx { namespace d3d11
|
|||
static PFN_GET_DEBUG_INTERFACE1 DXGIGetDebugInterface1;
|
||||
#endif // USE_D3D11_DYNAMIC_LIB
|
||||
|
||||
|
||||
#if BGFX_CONFIG_USE_OVR
|
||||
|
||||
#include <tinystl/vector.h>
|
||||
|
||||
// Oculus Rift eye buffer
|
||||
struct OVRBufferDX11 : public OVRBufferI
|
||||
{
|
||||
OVRBufferDX11(const ovrSession& session, int eyeIdx, ID3D11Device* d3dDevice, ID3D11DeviceContext* d3dCtx)
|
||||
{
|
||||
m_d3dDevice = d3dDevice;
|
||||
m_d3dContext = d3dCtx;
|
||||
ovrHmdDesc hmdDesc = ovr_GetHmdDesc(session);
|
||||
m_eyeTextureSize = ovr_GetFovTextureSize(session, (ovrEyeType)eyeIdx, hmdDesc.DefaultEyeFov[eyeIdx], 1.0f);
|
||||
|
||||
ovrTextureSwapChainDesc desc = {};
|
||||
desc.Type = ovrTexture_2D;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
|
||||
desc.Width = m_eyeTextureSize.w;
|
||||
desc.Height = m_eyeTextureSize.h;
|
||||
desc.MipLevels = 1;
|
||||
desc.SampleCount = 1;
|
||||
desc.MiscFlags = ovrTextureMisc_DX_Typeless;
|
||||
desc.BindFlags = ovrTextureBind_DX_RenderTarget;
|
||||
desc.StaticImage = ovrFalse;
|
||||
|
||||
ovrResult result = ovr_CreateTextureSwapChainDX(session, d3dDevice, &desc, &m_swapTextureChain);
|
||||
|
||||
if (!OVR_SUCCESS(result))
|
||||
{
|
||||
BX_CHECK(false, "Could not create D3D11 OVR swap texture");
|
||||
}
|
||||
|
||||
int textureCount = 0;
|
||||
ovr_GetTextureSwapChainLength(session, m_swapTextureChain, &textureCount);
|
||||
|
||||
for (int i = 0; i < textureCount; ++i)
|
||||
{
|
||||
ID3D11Texture2D* tex = NULL;
|
||||
ovr_GetTextureSwapChainBufferDX(session, m_swapTextureChain, i, IID_PPV_ARGS(&tex));
|
||||
D3D11_RENDER_TARGET_VIEW_DESC rtvd = {};
|
||||
rtvd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
rtvd.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
|
||||
|
||||
ID3D11RenderTargetView* rtv;
|
||||
DX_CHECK(d3dDevice->CreateRenderTargetView(tex, &rtvd, &rtv));
|
||||
m_eyeRtv.push_back(rtv);
|
||||
tex->Release();
|
||||
}
|
||||
|
||||
// setup depth buffer
|
||||
D3D11_TEXTURE2D_DESC dbDesc;
|
||||
dbDesc.Width = m_eyeTextureSize.w;
|
||||
dbDesc.Height = m_eyeTextureSize.h;
|
||||
dbDesc.MipLevels = 1;
|
||||
dbDesc.ArraySize = 1;
|
||||
dbDesc.Format = DXGI_FORMAT_D32_FLOAT;
|
||||
dbDesc.SampleDesc.Count = 1;
|
||||
dbDesc.SampleDesc.Quality = 0;
|
||||
dbDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||
dbDesc.CPUAccessFlags = 0;
|
||||
dbDesc.MiscFlags = 0;
|
||||
dbDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
|
||||
ID3D11Texture2D* tex;
|
||||
DX_CHECK(d3dDevice->CreateTexture2D(&dbDesc, NULL, &tex));
|
||||
DX_CHECK(d3dDevice->CreateDepthStencilView(tex, NULL, &m_depthBuffer));
|
||||
tex->Release();
|
||||
}
|
||||
|
||||
void onRender(const ovrSession& session)
|
||||
{
|
||||
// Clear and set up rendertarget
|
||||
int texIndex = 0;
|
||||
ovr_GetTextureSwapChainCurrentIndex(session, m_swapTextureChain, &texIndex);
|
||||
|
||||
float black[] = { 0.f, 0.f, 0.f, 0.f }; // Important that alpha=0, if want pixels to be transparent, for manual layers
|
||||
m_d3dContext->OMSetRenderTargets(1, &m_eyeRtv[texIndex], m_depthBuffer);
|
||||
m_d3dContext->ClearRenderTargetView(m_eyeRtv[texIndex], black);
|
||||
m_d3dContext->ClearDepthStencilView(m_depthBuffer, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1, 0);
|
||||
|
||||
D3D11_VIEWPORT D3Dvp;
|
||||
D3Dvp.TopLeftX = 0;
|
||||
D3Dvp.TopLeftY = 0;
|
||||
D3Dvp.Width = (FLOAT)m_eyeTextureSize.w;
|
||||
D3Dvp.Height = (FLOAT)m_eyeTextureSize.h;
|
||||
D3Dvp.MinDepth = 0;
|
||||
D3Dvp.MaxDepth = 1;
|
||||
m_d3dContext->RSSetViewports(1, &D3Dvp);
|
||||
}
|
||||
|
||||
void destroy(const ovrSession& session)
|
||||
{
|
||||
for (size_t i = 0; i < m_eyeRtv.size(); ++i)
|
||||
{
|
||||
m_eyeRtv[i]->Release();
|
||||
}
|
||||
|
||||
ovr_DestroyTextureSwapChain(session, m_swapTextureChain);
|
||||
m_depthBuffer->Release();
|
||||
}
|
||||
|
||||
ID3D11Device* m_d3dDevice;
|
||||
ID3D11DeviceContext* m_d3dContext;
|
||||
stl::vector<ID3D11RenderTargetView *> m_eyeRtv;
|
||||
ID3D11DepthStencilView* m_depthBuffer;
|
||||
};
|
||||
|
||||
// Oculus Rift mirror
|
||||
struct OVRMirrorDX11 : public OVRMirrorI
|
||||
{
|
||||
OVRMirrorDX11(ID3D11Device* d3dDevice,
|
||||
ID3D11DeviceContext* d3dCtx,
|
||||
IDXGISwapChain* d3dSc) : m_d3dDevice(d3dDevice)
|
||||
, m_d3dContext(d3dCtx)
|
||||
, m_d3dSwapChain(d3dSc)
|
||||
{
|
||||
}
|
||||
|
||||
void init(const ovrSession& session, int windowWidth, int windowHeight)
|
||||
{
|
||||
m_mirrorDesc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
|
||||
m_mirrorDesc.Width = windowWidth;
|
||||
m_mirrorDesc.Height = windowHeight;
|
||||
ovrResult result = ovr_CreateMirrorTextureDX(session, m_d3dDevice, &m_mirrorDesc, &m_mirrorTexture);
|
||||
|
||||
if (!OVR_SUCCESS(result))
|
||||
{
|
||||
BX_CHECK(false, "Could not create D3D11 OVR mirror texture");
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(const ovrSession& session)
|
||||
{
|
||||
if (!m_mirrorTexture)
|
||||
return;
|
||||
|
||||
ovr_DestroyMirrorTexture(session, m_mirrorTexture);
|
||||
m_mirrorTexture = NULL;
|
||||
}
|
||||
|
||||
void blit(const ovrSession& session)
|
||||
{
|
||||
if (!m_mirrorTexture)
|
||||
return;
|
||||
|
||||
ID3D11Texture2D* tex = NULL;
|
||||
ovr_GetMirrorTextureBufferDX(session, m_mirrorTexture, IID_PPV_ARGS(&tex));
|
||||
ID3D11Texture2D* backBuffer;
|
||||
DX_CHECK(m_d3dSwapChain->GetBuffer(0, IID_ID3D11Texture2D, (void**)&backBuffer));
|
||||
|
||||
m_d3dContext->CopyResource(backBuffer, tex);
|
||||
DX_CHECK(m_d3dSwapChain->Present(0, 0));
|
||||
|
||||
tex->Release();
|
||||
backBuffer->Release();
|
||||
}
|
||||
|
||||
ID3D11Device* m_d3dDevice;
|
||||
ID3D11DeviceContext* m_d3dContext;
|
||||
IDXGISwapChain* m_d3dSwapChain;
|
||||
};
|
||||
#endif // BGFX_CONFIG_USE_OVR
|
||||
|
||||
struct RendererContextD3D11 : public RendererContextI
|
||||
{
|
||||
RendererContextD3D11()
|
||||
|
@ -635,8 +799,6 @@ namespace bgfx { namespace d3d11
|
|||
, m_fsChanges(0)
|
||||
, m_rtMsaa(false)
|
||||
, m_timerQuerySupport(false)
|
||||
, m_ovrRtv(NULL)
|
||||
, m_ovrDsv(NULL)
|
||||
{
|
||||
m_fbh.idx = invalidHandle;
|
||||
memset(&m_adapterDesc, 0, sizeof(m_adapterDesc) );
|
||||
|
@ -2001,10 +2163,6 @@ BX_PRAGMA_DIAGNOSTIC_POP();
|
|||
|
||||
uint32_t width = getBufferWidth();
|
||||
uint32_t height = getBufferHeight();
|
||||
if (m_ovr.isEnabled() )
|
||||
{
|
||||
m_ovr.getSize(width, height);
|
||||
}
|
||||
|
||||
FrameBufferHandle fbh = BGFX_INVALID_HANDLE;
|
||||
setFrameBuffer(fbh, false);
|
||||
|
@ -2192,7 +2350,7 @@ BX_PRAGMA_DIAGNOSTIC_POP();
|
|||
|
||||
if (SUCCEEDED(hr) )
|
||||
{
|
||||
if (!m_ovr.swap(_hmd) )
|
||||
if (!m_ovr.swap(_hmd, false) )
|
||||
{
|
||||
hr = m_swapChain->Present(syncInterval, 0);
|
||||
}
|
||||
|
@ -3083,77 +3241,22 @@ BX_PRAGMA_DIAGNOSTIC_POP();
|
|||
void ovrPostReset()
|
||||
{
|
||||
#if BGFX_CONFIG_USE_OVR
|
||||
if (m_flags & (BGFX_RESET_HMD|BGFX_RESET_HMD_DEBUG) )
|
||||
if (m_resolution.m_flags & (BGFX_RESET_HMD|BGFX_RESET_HMD_DEBUG) )
|
||||
{
|
||||
ovrD3D11Config config;
|
||||
config.D3D11.Header.API = ovrRenderAPI_D3D11;
|
||||
# if OVR_VERSION > OVR_VERSION_043
|
||||
config.D3D11.Header.BackBufferSize.w = m_scd.BufferDesc.Width;
|
||||
config.D3D11.Header.BackBufferSize.h = m_scd.BufferDesc.Height;
|
||||
config.D3D11.pBackBufferUAV = NULL;
|
||||
# else
|
||||
config.D3D11.Header.RTSize.w = m_scd.BufferDesc.Width;
|
||||
config.D3D11.Header.RTSize.h = m_scd.BufferDesc.Height;
|
||||
# endif // OVR_VERSION > OVR_VERSION_042
|
||||
config.D3D11.Header.Multisample = 0;
|
||||
config.D3D11.pDevice = m_device;
|
||||
config.D3D11.pDeviceContext = m_deviceCtx;
|
||||
config.D3D11.pBackBufferRT = m_backBufferColor;
|
||||
config.D3D11.pSwapChain = m_swapChain;
|
||||
if (m_ovr.postReset(g_platformData.nwh, &config.Config, !!(m_flags & BGFX_RESET_HMD_DEBUG) ) )
|
||||
if (m_ovr.postReset())
|
||||
{
|
||||
uint32_t size = sizeof(uint32_t) + sizeof(TextureCreate);
|
||||
const Memory* mem = alloc(size);
|
||||
for (int eyeIdx = 0; eyeIdx < ovrEye_Count; eyeIdx++)
|
||||
{
|
||||
// eye buffers need to be initialized only once during application lifetime
|
||||
if (!m_ovr.m_eyeBuffers[eyeIdx])
|
||||
{
|
||||
m_ovr.m_eyeBuffers[eyeIdx] = BX_NEW(g_allocator, OVRBufferDX11(m_ovr.m_hmd, eyeIdx, m_device, m_deviceCtx));
|
||||
}
|
||||
}
|
||||
|
||||
bx::StaticMemoryBlockWriter writer(mem->data, mem->size);
|
||||
uint32_t magic = BGFX_CHUNK_MAGIC_TEX;
|
||||
bx::write(&writer, magic);
|
||||
|
||||
TextureCreate tc;
|
||||
tc.m_flags = BGFX_TEXTURE_RT|( ((m_flags & BGFX_RESET_MSAA_MASK) >> BGFX_RESET_MSAA_SHIFT) << BGFX_TEXTURE_RT_MSAA_SHIFT);
|
||||
tc.m_width = m_ovr.m_rtSize.w;
|
||||
tc.m_height = m_ovr.m_rtSize.h;
|
||||
tc.m_sides = 0;
|
||||
tc.m_depth = 0;
|
||||
tc.m_numMips = 1;
|
||||
tc.m_format = uint8_t(bgfx::TextureFormat::BGRA8);
|
||||
tc.m_cubeMap = false;
|
||||
tc.m_mem = NULL;
|
||||
bx::write(&writer, tc);
|
||||
m_ovrRT.create(mem, tc.m_flags, 0);
|
||||
|
||||
release(mem);
|
||||
|
||||
DX_CHECK(m_device->CreateRenderTargetView(m_ovrRT.m_ptr, NULL, &m_ovrRtv) );
|
||||
|
||||
D3D11_TEXTURE2D_DESC dsd;
|
||||
dsd.Width = m_ovr.m_rtSize.w;
|
||||
dsd.Height = m_ovr.m_rtSize.h;
|
||||
dsd.MipLevels = 1;
|
||||
dsd.ArraySize = 1;
|
||||
dsd.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
|
||||
dsd.SampleDesc = m_scd.SampleDesc;
|
||||
dsd.Usage = D3D11_USAGE_DEFAULT;
|
||||
dsd.BindFlags = D3D11_BIND_DEPTH_STENCIL;
|
||||
dsd.CPUAccessFlags = 0;
|
||||
dsd.MiscFlags = 0;
|
||||
|
||||
ID3D11Texture2D* depthStencil;
|
||||
DX_CHECK(m_device->CreateTexture2D(&dsd, NULL, &depthStencil) );
|
||||
DX_CHECK(m_device->CreateDepthStencilView(depthStencil, NULL, &m_ovrDsv) );
|
||||
DX_RELEASE(depthStencil, 0);
|
||||
|
||||
ovrD3D11Texture texture;
|
||||
texture.D3D11.Header.API = ovrRenderAPI_D3D11;
|
||||
texture.D3D11.Header.TextureSize = m_ovr.m_rtSize;
|
||||
texture.D3D11.pTexture = m_ovrRT.m_texture2d;
|
||||
texture.D3D11.pSRView = m_ovrRT.m_srv;
|
||||
m_ovr.postReset(texture.Texture);
|
||||
|
||||
bx::xchg(m_ovrRtv, m_backBufferColor);
|
||||
|
||||
BX_CHECK(NULL == m_backBufferDepthStencil, "");
|
||||
bx::xchg(m_ovrDsv, m_backBufferDepthStencil);
|
||||
// recreate mirror texture
|
||||
m_ovr.m_mirror = BX_NEW(g_allocator, OVRMirrorDX11(m_device, m_deviceCtx, m_swapChain));
|
||||
m_ovr.m_mirror->init(m_ovr.m_hmd, m_resolution.m_width, m_resolution.m_height);
|
||||
}
|
||||
}
|
||||
#endif // BGFX_CONFIG_USE_OVR
|
||||
|
@ -3163,16 +3266,6 @@ BX_PRAGMA_DIAGNOSTIC_POP();
|
|||
{
|
||||
#if BGFX_CONFIG_USE_OVR
|
||||
m_ovr.preReset();
|
||||
if (NULL != m_ovrRtv)
|
||||
{
|
||||
bx::xchg(m_ovrRtv, m_backBufferColor);
|
||||
bx::xchg(m_ovrDsv, m_backBufferDepthStencil);
|
||||
BX_CHECK(NULL == m_backBufferDepthStencil, "");
|
||||
|
||||
DX_RELEASE(m_ovrRtv, 0);
|
||||
DX_RELEASE(m_ovrDsv, 0);
|
||||
m_ovrRT.destroy();
|
||||
}
|
||||
#endif // BGFX_CONFIG_USE_OVR
|
||||
}
|
||||
|
||||
|
@ -3564,9 +3657,6 @@ BX_PRAGMA_DIAGNOSTIC_POP();
|
|||
bool m_timerQuerySupport;
|
||||
|
||||
OVR m_ovr;
|
||||
TextureD3D11 m_ovrRT;
|
||||
ID3D11RenderTargetView* m_ovrRtv;
|
||||
ID3D11DepthStencilView* m_ovrDsv;
|
||||
};
|
||||
|
||||
static RendererContextD3D11* s_renderD3D11;
|
||||
|
@ -4864,7 +4954,7 @@ BX_PRAGMA_DIAGNOSTIC_POP();
|
|||
|
||||
_render->m_hmdInitialized = m_ovr.isInitialized();
|
||||
|
||||
const bool hmdEnabled = m_ovr.isEnabled() || m_ovr.isDebug();
|
||||
const bool hmdEnabled = m_ovr.isEnabled();
|
||||
ViewState viewState(_render, hmdEnabled);
|
||||
|
||||
bool wireframe = !!(_render->m_debug&BGFX_DEBUG_WIREFRAME);
|
||||
|
@ -4987,6 +5077,9 @@ BX_PRAGMA_DIAGNOSTIC_POP();
|
|||
if (m_ovr.isEnabled() )
|
||||
{
|
||||
m_ovr.getViewport(eye, &viewState.m_rect);
|
||||
// commit previous eye to HMD and start rendering new frame
|
||||
m_ovr.commitEye(eye);
|
||||
m_ovr.renderEyeStart(eye);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -1274,6 +1274,144 @@ namespace bgfx { namespace gl
|
|||
BX_UNUSED(supported);
|
||||
}
|
||||
|
||||
#if BGFX_CONFIG_USE_OVR
|
||||
|
||||
// Oculus Rift eye buffer
|
||||
struct OVRBufferGL : public OVRBufferI
|
||||
{
|
||||
OVRBufferGL(const ovrSession& session, int eyeIdx)
|
||||
{
|
||||
ovrHmdDesc hmdDesc = ovr_GetHmdDesc(session);
|
||||
m_eyeTextureSize = ovr_GetFovTextureSize(session, (ovrEyeType)eyeIdx, hmdDesc.DefaultEyeFov[eyeIdx], 1.0f);
|
||||
|
||||
ovrTextureSwapChainDesc desc = {};
|
||||
desc.Type = ovrTexture_2D;
|
||||
desc.ArraySize = 1;
|
||||
desc.Width = m_eyeTextureSize.w;
|
||||
desc.Height = m_eyeTextureSize.h;
|
||||
desc.MipLevels = 1;
|
||||
desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
|
||||
desc.SampleCount = 1;
|
||||
desc.StaticImage = ovrFalse;
|
||||
|
||||
ovr_CreateTextureSwapChainGL(session, &desc, &m_swapTextureChain);
|
||||
|
||||
int textureCount = 0;
|
||||
ovr_GetTextureSwapChainLength(session, m_swapTextureChain, &textureCount);
|
||||
|
||||
for (int j = 0; j < textureCount; ++j)
|
||||
{
|
||||
GLuint chainTexId;
|
||||
ovr_GetTextureSwapChainBufferGL(session, m_swapTextureChain, j, &chainTexId);
|
||||
GL_CHECK(glBindTexture(GL_TEXTURE_2D, chainTexId));
|
||||
|
||||
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
}
|
||||
|
||||
GL_CHECK(glGenFramebuffers(1, &m_eyeFbo));
|
||||
|
||||
// create depth buffer
|
||||
GL_CHECK(glGenTextures(1, &m_depthBuffer));
|
||||
GL_CHECK(glBindTexture(GL_TEXTURE_2D, m_depthBuffer));
|
||||
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
GL_CHECK(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
|
||||
GL_CHECK(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, m_eyeTextureSize.w, m_eyeTextureSize.h, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL));
|
||||
}
|
||||
|
||||
void onRender(const ovrSession& session)
|
||||
{
|
||||
// set the current eye texture in swap chain
|
||||
int curIndex;
|
||||
ovr_GetTextureSwapChainCurrentIndex(session, m_swapTextureChain, &curIndex);
|
||||
ovr_GetTextureSwapChainBufferGL(session, m_swapTextureChain, curIndex, &m_eyeTexId);
|
||||
|
||||
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, m_eyeFbo));
|
||||
GL_CHECK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_eyeTexId, 0));
|
||||
GL_CHECK(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depthBuffer, 0));
|
||||
|
||||
GL_CHECK(glViewport(0, 0, m_eyeTextureSize.w, m_eyeTextureSize.h));
|
||||
GL_CHECK(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
||||
}
|
||||
|
||||
void destroy(const ovrSession& session)
|
||||
{
|
||||
GL_CHECK(glDeleteFramebuffers(1, &m_eyeFbo));
|
||||
GL_CHECK(glDeleteTextures(1, &m_depthBuffer));
|
||||
|
||||
ovr_DestroyTextureSwapChain(session, m_swapTextureChain);
|
||||
}
|
||||
|
||||
GLuint m_eyeFbo;
|
||||
GLuint m_eyeTexId;
|
||||
GLuint m_depthBuffer;
|
||||
};
|
||||
|
||||
// Oculus Rift mirror
|
||||
struct OVRMirrorGL : public OVRMirrorI
|
||||
{
|
||||
void init(const ovrSession& session, int windowWidth, int windowHeight)
|
||||
{
|
||||
memset(&m_mirrorDesc, 0, sizeof(m_mirrorDesc));
|
||||
m_mirrorDesc.Width = windowWidth;
|
||||
m_mirrorDesc.Height = windowHeight;
|
||||
m_mirrorDesc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB;
|
||||
|
||||
ovr_CreateMirrorTextureGL(session, &m_mirrorDesc, &m_mirrorTexture);
|
||||
|
||||
// Fallback to doing nothing if mirror was not created. This is to prevent errors with fast window resizes
|
||||
if (!m_mirrorTexture)
|
||||
return;
|
||||
|
||||
// Configure the mirror read buffer
|
||||
GLuint texId;
|
||||
ovr_GetMirrorTextureBufferGL(session, m_mirrorTexture, &texId);
|
||||
GL_CHECK(glGenFramebuffers(1, &m_mirrorFBO));
|
||||
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, m_mirrorFBO));
|
||||
GL_CHECK(glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texId, 0));
|
||||
GL_CHECK(glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0));
|
||||
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, 0));
|
||||
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
GL_CHECK(glDeleteFramebuffers(1, &m_mirrorFBO));
|
||||
BX_CHECK(false, "Could not initialize VR buffers!");
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(const ovrSession& session)
|
||||
{
|
||||
if (!m_mirrorTexture)
|
||||
return;
|
||||
|
||||
GL_CHECK(glDeleteFramebuffers(1, &m_mirrorFBO));
|
||||
ovr_DestroyMirrorTexture(session, m_mirrorTexture);
|
||||
m_mirrorTexture = NULL;
|
||||
}
|
||||
|
||||
void blit(const ovrSession& /*session*/)
|
||||
{
|
||||
if (!m_mirrorTexture)
|
||||
return;
|
||||
|
||||
// Blit mirror texture to back buffer
|
||||
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, m_mirrorFBO));
|
||||
GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
|
||||
GLint w = m_mirrorDesc.Width;
|
||||
GLint h = m_mirrorDesc.Height;
|
||||
GL_CHECK(glBlitFramebuffer(0, h, w, 0, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST));
|
||||
GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, 0));
|
||||
}
|
||||
|
||||
GLuint m_mirrorFBO;
|
||||
};
|
||||
#endif // BGFX_CONFIG_USE_OVR
|
||||
|
||||
struct RendererContextGL : public RendererContextI
|
||||
{
|
||||
RendererContextGL()
|
||||
|
@ -1304,7 +1442,6 @@ namespace bgfx { namespace gl
|
|||
, m_hash( (BX_PLATFORM_WINDOWS<<1) | BX_ARCH_64BIT)
|
||||
, m_backBufferFbo(0)
|
||||
, m_msaaBackBufferFbo(0)
|
||||
, m_ovrFbo(0)
|
||||
{
|
||||
memset(m_msaaBackBufferRbos, 0, sizeof(m_msaaBackBufferRbos) );
|
||||
}
|
||||
|
@ -2107,10 +2244,9 @@ namespace bgfx { namespace gl
|
|||
m_glctx.swap(m_frameBuffers[m_windows[ii].idx].m_swapChain);
|
||||
}
|
||||
|
||||
if (!m_ovr.swap(_hmd) )
|
||||
{
|
||||
m_glctx.swap();
|
||||
}
|
||||
m_ovr.swap(_hmd, true);
|
||||
// need to swap GL render context even if OVR is enabled to get the mirror texture in the output
|
||||
m_glctx.swap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2397,10 +2533,6 @@ namespace bgfx { namespace gl
|
|||
|
||||
uint32_t width = m_resolution.m_width;
|
||||
uint32_t height = m_resolution.m_height;
|
||||
if (m_ovr.isEnabled() )
|
||||
{
|
||||
m_ovr.getSize(width, height);
|
||||
}
|
||||
|
||||
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, m_backBufferFbo) );
|
||||
GL_CHECK(glViewport(0, 0, width, height) );
|
||||
|
@ -2857,63 +2989,22 @@ namespace bgfx { namespace gl
|
|||
void ovrPostReset()
|
||||
{
|
||||
#if BGFX_CONFIG_USE_OVR
|
||||
if (m_resolution.m_flags & (BGFX_RESET_HMD|BGFX_RESET_HMD_DEBUG) )
|
||||
if (m_resolution.m_flags & (BGFX_RESET_HMD | BGFX_RESET_HMD_DEBUG))
|
||||
{
|
||||
ovrGLConfig config;
|
||||
config.OGL.Header.API = ovrRenderAPI_OpenGL;
|
||||
# if OVR_VERSION > OVR_VERSION_043
|
||||
config.OGL.Header.BackBufferSize.w = m_resolution.m_width;
|
||||
config.OGL.Header.BackBufferSize.h = m_resolution.m_height;
|
||||
# else
|
||||
config.OGL.Header.RTSize.w = m_resolution.m_width;
|
||||
config.OGL.Header.RTSize.h = m_resolution.m_height;
|
||||
# endif // OVR_VERSION > OVR_VERSION_043
|
||||
config.OGL.Header.Multisample = 0;
|
||||
config.OGL.Window = (HWND)g_platformData.nwh;
|
||||
config.OGL.DC = GetDC(config.OGL.Window);
|
||||
if (m_ovr.postReset(g_platformData.nwh, &config.Config, !!(m_resolution.m_flags & BGFX_RESET_HMD_DEBUG) ) )
|
||||
if (m_ovr.postReset())
|
||||
{
|
||||
uint32_t size = sizeof(uint32_t) + sizeof(TextureCreate);
|
||||
const Memory* mem = alloc(size);
|
||||
for (int eyeIdx = 0; eyeIdx < ovrEye_Count; eyeIdx++)
|
||||
{
|
||||
// eye buffers need to be initialized only once during application lifetime
|
||||
if (!m_ovr.m_eyeBuffers[eyeIdx])
|
||||
{
|
||||
m_ovr.m_eyeBuffers[eyeIdx] = BX_NEW(g_allocator, OVRBufferGL(m_ovr.m_hmd, eyeIdx));
|
||||
}
|
||||
}
|
||||
|
||||
bx::StaticMemoryBlockWriter writer(mem->data, mem->size);
|
||||
uint32_t magic = BGFX_CHUNK_MAGIC_TEX;
|
||||
bx::write(&writer, magic);
|
||||
|
||||
TextureCreate tc;
|
||||
tc.m_flags = BGFX_TEXTURE_RT|( ((m_resolution.m_flags & BGFX_RESET_MSAA_MASK) >> BGFX_RESET_MSAA_SHIFT) << BGFX_TEXTURE_RT_MSAA_SHIFT);;
|
||||
tc.m_width = m_ovr.m_rtSize.w;
|
||||
tc.m_height = m_ovr.m_rtSize.h;
|
||||
tc.m_sides = 0;
|
||||
tc.m_depth = 0;
|
||||
tc.m_numMips = 1;
|
||||
tc.m_format = uint8_t(bgfx::TextureFormat::BGRA8);
|
||||
tc.m_cubeMap = false;
|
||||
tc.m_mem = NULL;
|
||||
bx::write(&writer, tc);
|
||||
|
||||
m_ovrRT.create(mem, tc.m_flags, 0);
|
||||
release(mem);
|
||||
|
||||
m_ovrFbo = m_msaaBackBufferFbo;
|
||||
|
||||
GL_CHECK(glGenFramebuffers(1, &m_msaaBackBufferFbo) );
|
||||
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, m_msaaBackBufferFbo) );
|
||||
|
||||
GL_CHECK(glFramebufferTexture2D(GL_FRAMEBUFFER
|
||||
, GL_COLOR_ATTACHMENT0
|
||||
, GL_TEXTURE_2D
|
||||
, m_ovrRT.m_id
|
||||
, 0
|
||||
) );
|
||||
|
||||
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, m_ovrFbo) );
|
||||
|
||||
ovrGLTexture texture;
|
||||
texture.OGL.Header.API = ovrRenderAPI_OpenGL;
|
||||
texture.OGL.Header.TextureSize = m_ovr.m_rtSize;
|
||||
texture.OGL.TexId = m_ovrRT.m_id;
|
||||
m_ovr.postReset(texture.Texture);
|
||||
// recreate mirror texture
|
||||
m_ovr.m_mirror = BX_NEW(g_allocator, OVRMirrorGL);
|
||||
m_ovr.m_mirror->init(m_ovr.m_hmd, m_resolution.m_width, m_resolution.m_height);
|
||||
}
|
||||
}
|
||||
#endif // BGFX_CONFIG_USE_OVR
|
||||
|
@ -2923,14 +3014,6 @@ namespace bgfx { namespace gl
|
|||
{
|
||||
#if BGFX_CONFIG_USE_OVR
|
||||
m_ovr.preReset();
|
||||
if (m_ovr.isEnabled() )
|
||||
{
|
||||
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0) );
|
||||
GL_CHECK(glDeleteFramebuffers(1, &m_msaaBackBufferFbo) );
|
||||
m_msaaBackBufferFbo = m_ovrFbo;
|
||||
m_ovrFbo = 0;
|
||||
m_ovrRT.destroy();
|
||||
}
|
||||
#endif // BGFX_CONFIG_USE_OVR
|
||||
}
|
||||
|
||||
|
@ -3354,8 +3437,6 @@ namespace bgfx { namespace gl
|
|||
const char* m_glslVersion;
|
||||
|
||||
OVR m_ovr;
|
||||
TextureGL m_ovrRT;
|
||||
GLint m_ovrFbo;
|
||||
};
|
||||
|
||||
RendererContextGL* s_renderGL;
|
||||
|
@ -3647,7 +3728,7 @@ namespace bgfx { namespace gl
|
|||
}
|
||||
|
||||
m_numPredefined = 0;
|
||||
m_numSamplers = 0;
|
||||
m_numSamplers = 0;
|
||||
|
||||
BX_TRACE("Uniforms (%d):", activeUniforms);
|
||||
for (int32_t ii = 0; ii < activeUniforms; ++ii)
|
||||
|
@ -5439,7 +5520,7 @@ namespace bgfx { namespace gl
|
|||
|
||||
_render->m_hmdInitialized = m_ovr.isInitialized();
|
||||
|
||||
const bool hmdEnabled = m_ovr.isEnabled() || m_ovr.isDebug();
|
||||
const bool hmdEnabled = m_ovr.isEnabled();
|
||||
ViewState viewState(_render, hmdEnabled);
|
||||
|
||||
uint16_t programIdx = invalidHandle;
|
||||
|
@ -5491,12 +5572,13 @@ namespace bgfx { namespace gl
|
|||
m_occlusionQuery.resolve(_render);
|
||||
}
|
||||
|
||||
uint8_t eye = 0;
|
||||
|
||||
if (0 == (_render->m_debug&BGFX_DEBUG_IFH) )
|
||||
{
|
||||
GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, m_msaaBackBufferFbo) );
|
||||
|
||||
bool viewRestart = false;
|
||||
uint8_t eye = 0;
|
||||
uint8_t restartState = 0;
|
||||
viewState.m_rect = _render->m_rect[0];
|
||||
|
||||
|
@ -5578,6 +5660,9 @@ namespace bgfx { namespace gl
|
|||
if (m_ovr.isEnabled() )
|
||||
{
|
||||
m_ovr.getViewport(eye, &viewState.m_rect);
|
||||
// commit previous eye to HMD and start rendering new frame
|
||||
m_ovr.commitEye(eye);
|
||||
m_ovr.renderEyeStart(eye);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -5679,7 +5764,7 @@ namespace bgfx { namespace gl
|
|||
const RenderCompute& compute = renderItem.compute;
|
||||
|
||||
ProgramGL& program = m_program[key.m_program];
|
||||
GL_CHECK(glUseProgram(program.m_id) );
|
||||
GL_CHECK(glUseProgram(program.m_id) );
|
||||
|
||||
GLbitfield barrier = 0;
|
||||
for (uint32_t ii = 0; ii < BGFX_MAX_COMPUTE_BINDINGS; ++ii)
|
||||
|
|
Загрузка…
Ссылка в новой задаче