#ifdef KORE_OCULUS #include #include "Direct3D11.h" #include #include #include "OVR_CAPI_D3D.h" #include "d3d11.h" #include #if _MSC_VER > 1600 #include "DirectXMath.h" using namespace DirectX; #else #include "xnamath.h" #endif //_MSC_VER > 1600 #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "d3dcompiler.lib") using namespace Kore; namespace { kinc_vr_sensor_state_t sensorStates[2]; } //------------------------------------------------------------ struct DepthBuffer { ID3D11DepthStencilView *TexDsv; DepthBuffer(ID3D11Device *Device, int sizeW, int sizeH, int sampleCount = 1) { DXGI_FORMAT format = DXGI_FORMAT_D32_FLOAT; D3D11_TEXTURE2D_DESC dsDesc; dsDesc.Width = sizeW; dsDesc.Height = sizeH; dsDesc.MipLevels = 1; dsDesc.ArraySize = 1; dsDesc.Format = format; dsDesc.SampleDesc.Count = sampleCount; dsDesc.SampleDesc.Quality = 0; dsDesc.Usage = D3D11_USAGE_DEFAULT; dsDesc.CPUAccessFlags = 0; dsDesc.MiscFlags = 0; dsDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; ID3D11Texture2D *Tex; Device->CreateTexture2D(&dsDesc, nullptr, &Tex); Device->CreateDepthStencilView(Tex, nullptr, &TexDsv); Tex->Release(); } ~DepthBuffer() { TexDsv->Release(); TexDsv = nullptr; } }; //----------------------------------------------------------- struct Camera { XMVECTOR Pos; XMVECTOR Rot; Camera(){}; Camera(XMVECTOR *pos, XMVECTOR *rot) : Pos(*pos), Rot(*rot){}; Camera(const XMVECTOR &pos, const XMVECTOR &rot) : Pos(pos), Rot(rot){}; XMMATRIX GetViewMatrix() { XMVECTOR forward = XMVector3Rotate(XMVectorSet(0, 0, -1, 0), Rot); return (XMMatrixLookAtRH(Pos, XMVectorAdd(Pos, forward), XMVector3Rotate(XMVectorSet(0, 1, 0, 0), Rot))); } static void *operator new(std::size_t size) { UNREFERENCED_PARAMETER(size); return _aligned_malloc(sizeof(Camera), __alignof(Camera)); } static void operator delete(void *p) { _aligned_free(p); } }; //--------------------------------------------------------------------- struct DirectX11 { HWND Window; bool Running; int WinSizeW; int WinSizeH; HINSTANCE hInstance; DirectX11() : Window(nullptr), Running(false), WinSizeW(0), WinSizeH(0), hInstance(nullptr) {} ~DirectX11() { ReleaseDevice(); CloseWindow(); } bool InitWindow(HINSTANCE hinst, const char *title, const char *windowClassName) { hInstance = hinst; Running = true; // Adjust the window size and show at InitDevice time wchar_t wchTitle[256]; MultiByteToWideChar(CP_ACP, 0, title, -1, wchTitle, 256); wchar_t wchClassName[256]; MultiByteToWideChar(CP_ACP, 0, windowClassName, -1, wchClassName, 256); Window = CreateWindowW(wchClassName, wchTitle, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, 0, 0, hinst, 0); if (!Window) return false; SetWindowLongPtr(Window, 0, LONG_PTR(this)); return true; } void CloseWindow() { if (Window) { Window = nullptr; } } bool InitDevice(int vpW, int vpH, const LUID *pLuid, bool windowed = true, int scale = 1) { WinSizeW = vpW; WinSizeH = vpH; if (scale == 0) scale = 1; RECT size = {0, 0, vpW / scale, vpH / scale}; AdjustWindowRect(&size, WS_OVERLAPPEDWINDOW, false); const UINT flags = SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW; if (!SetWindowPos(Window, nullptr, 0, 0, size.right - size.left, size.bottom - size.top, flags)) return false; return true; } void SetAndClearRenderTarget(ID3D11RenderTargetView *rendertarget, DepthBuffer *depthbuffer, float R = 0, float G = 0, float B = 0, float A = 0) { float black[] = {R, G, B, A}; // Important that alpha=0, if want pixels to be transparent, for manual layers dx_ctx.context->OMSetRenderTargets(1, &rendertarget, (depthbuffer ? depthbuffer->TexDsv : nullptr)); dx_ctx.context->ClearRenderTargetView(rendertarget, black); if (depthbuffer) dx_ctx.context->ClearDepthStencilView(depthbuffer->TexDsv, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1, 0); } void SetViewport(float vpX, float vpY, float vpW, float vpH) { D3D11_VIEWPORT D3Dvp; D3Dvp.Width = vpW; D3Dvp.Height = vpH; D3Dvp.MinDepth = 0; D3Dvp.MaxDepth = 1; D3Dvp.TopLeftX = vpX; D3Dvp.TopLeftY = vpY; dx_ctx.context->RSSetViewports(1, &D3Dvp); } void ReleaseDevice() {} }; static DirectX11 Platform; //--------------------------------------------------------------------- // ovrSwapTextureSet wrapper class that also maintains the render target views needed for D3D11 rendering. struct OculusTexture { ovrSession Session; ovrTextureSwapChain TextureChain; std::vector TexRtv; OculusTexture(ovrSession session, int sizeW, int sizeH, int sampleCount = 1) : Session(session), TextureChain(nullptr) { ovrTextureSwapChainDesc desc = {}; desc.Type = ovrTexture_2D; desc.ArraySize = 1; desc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; desc.Width = sizeW; desc.Height = sizeH; desc.MipLevels = 1; desc.SampleCount = sampleCount; desc.MiscFlags = ovrTextureMisc_DX_Typeless | ovrTextureMisc_AutoGenerateMips; desc.BindFlags = ovrTextureBind_DX_RenderTarget; desc.StaticImage = ovrFalse; ovrResult result = ovr_CreateTextureSwapChainDX(session, dx_ctx.device, &desc, &TextureChain); int textureCount = 0; ovr_GetTextureSwapChainLength(Session, TextureChain, &textureCount); if (OVR_SUCCESS(result)) { for (int i = 0; i < textureCount; ++i) { ID3D11Texture2D *tex = nullptr; ovr_GetTextureSwapChainBufferDX(Session, TextureChain, i, IID_PPV_ARGS(&tex)); D3D11_RENDER_TARGET_VIEW_DESC rtvd = {}; rtvd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; rtvd.ViewDimension = (sampleCount > 1) ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D; ID3D11RenderTargetView *rtv; dx_ctx.device->CreateRenderTargetView(tex, &rtvd, &rtv); TexRtv.push_back(rtv); tex->Release(); } } } ~OculusTexture() { for (int i = 0; i < (int)TexRtv.size(); ++i) { TexRtv[i]->Release(); TexRtv[i] = nullptr; } if (TextureChain) { ovr_DestroyTextureSwapChain(Session, TextureChain); TextureChain = nullptr; } } ID3D11RenderTargetView *GetRTV() { int index = 0; ovr_GetTextureSwapChainCurrentIndex(Session, TextureChain, &index); return TexRtv[index]; } void Commit() { ovr_CommitTextureSwapChain(Session, TextureChain); } }; //--------------------------------------------------------------------- namespace { // Initialize these to nullptr here to handle dx_ctx.device lost failures cleanly ovrMirrorTexture mirrorTexture = nullptr; OculusTexture *pEyeRenderTexture[2] = {nullptr, nullptr}; DepthBuffer *pEyeDepthBuffer[2] = {nullptr, nullptr}; ovrSizei windowSize; long long frameIndex = 0; int msaaRate = 4; bool isVisible = true; ovrSession session; ovrHmdDesc hmdDesc; ovrPosef EyeRenderPose[2]; double sensorSampleTime; // Make the eye render buffers (caution if actual size < requested due to HW limits). ovrRecti eyeRenderViewport[2]; void done() { if (mirrorTexture) ovr_DestroyMirrorTexture(session, mirrorTexture); for (int eye = 0; eye < 2; ++eye) { delete pEyeRenderTexture[eye]; delete pEyeDepthBuffer[eye]; } Platform.ReleaseDevice(); ovr_Destroy(session); } void createOculusTexture() { // Create mirror texture ovrMirrorTextureDesc mirrorDesc; memset(&mirrorDesc, 0, sizeof(mirrorDesc)); mirrorDesc.Width = Platform.WinSizeW; mirrorDesc.Height = Platform.WinSizeH; mirrorDesc.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB; mirrorDesc.MirrorOptions = ovrMirrorOption_Default; HRESULT result = ovr_CreateMirrorTextureWithOptionsDX(session, dx_ctx.device, &mirrorDesc, &mirrorTexture); if (!OVR_SUCCESS(result)) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create mirror texture."); done(); } // Make eye render buffers for (int eye = 0; eye < 2; ++eye) { ovrSizei idealSize = ovr_GetFovTextureSize(session, ovrEyeType(eye), hmdDesc.DefaultEyeFov[eye], 1); pEyeRenderTexture[eye] = new OculusTexture(session, idealSize.w, idealSize.h); pEyeDepthBuffer[eye] = new DepthBuffer(dx_ctx.device, idealSize.w, idealSize.h); eyeRenderViewport[eye].Pos.x = 0; eyeRenderViewport[eye].Pos.y = 0; eyeRenderViewport[eye].Size = idealSize; if (!pEyeRenderTexture[eye]->TextureChain) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to create texture."); done(); } } } } void *kinc_vr_interface_init(void *hinst, const char *title, const char *windowClassName) { // Initializes LibOVR, and the Rift ovrInitParams initParams = {ovrInit_RequestVersion | ovrInit_FocusAware, OVR_MINOR_VERSION, NULL, 0, 0}; ovrResult result = ovr_Initialize(&initParams); if (!OVR_SUCCESS(result)) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to initialize libOVR."); return (0); } if (!Platform.InitWindow((HINSTANCE)hinst, title, windowClassName)) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to open window."); return (0); } ovrGraphicsLuid luid; result = ovr_Create(&session, &luid); if (!OVR_SUCCESS(result)) { kinc_log(KINC_LOG_LEVEL_ERROR, "HMD not connected."); return false; // TODO: retry } hmdDesc = ovr_GetHmdDesc(session); // Setup Window and Graphics // Note: the mirror window can be any size, for this sample we use 1/2 the HMD resolution windowSize = {hmdDesc.Resolution.w / 2, hmdDesc.Resolution.h / 2}; if (!Platform.InitDevice(windowSize.w, windowSize.h, reinterpret_cast(&luid))) { kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to init dx_ctx.device."); done(); } // FloorLevel will give tracking poses where the floor height is 0 ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel); // Return window return Platform.Window; } void kinc_vr_interface_begin() { // Call ovr_GetRenderDesc each frame to get the ovrEyeRenderDesc, as the returned values (e.g. HmdToEyeOffset) may change at runtime. ovrEyeRenderDesc eyeRenderDesc[2]; eyeRenderDesc[0] = ovr_GetRenderDesc(session, ovrEye_Left, hmdDesc.DefaultEyeFov[0]); eyeRenderDesc[1] = ovr_GetRenderDesc(session, ovrEye_Right, hmdDesc.DefaultEyeFov[1]); // Get both eye poses simultaneously, with IPD offset already included. ovrPosef HmdToEyePose[2] = {eyeRenderDesc[0].HmdToEyePose, eyeRenderDesc[1].HmdToEyePose}; ovr_GetEyePoses(session, frameIndex, ovrTrue, HmdToEyePose, EyeRenderPose, &sensorSampleTime); } void kinc_vr_interface_begin_render(int eye) { if (pEyeRenderTexture[0] == nullptr || pEyeRenderTexture[1] == nullptr) createOculusTexture(); // Clear and set up rendertarget Platform.SetAndClearRenderTarget(pEyeRenderTexture[eye]->GetRTV(), pEyeDepthBuffer[eye]); Platform.SetViewport((float)eyeRenderViewport[eye].Pos.x, (float)eyeRenderViewport[eye].Pos.y, (float)eyeRenderViewport[eye].Size.w, (float)eyeRenderViewport[eye].Size.h); } void kinc_vr_interface_end_render(int eye) { // Commit rendering to the swap chain pEyeRenderTexture[eye]->Commit(); } namespace { kinc_matrix4x4_t convert(XMMATRIX &m) { XMFLOAT4X4 fView; XMStoreFloat4x4(&fView, m); kinc_matrix4x4_t mat; kinc_matrix4x4_set(&mat, 0, 0, fView._11); kinc_matrix4x4_set(&mat, 0, 1, fView._12); kinc_matrix4x4_set(&mat, 0, 2, fView._13); kinc_matrix4x4_set(&mat, 0, 3, fView._14); kinc_matrix4x4_set(&mat, 1, 0, fView._21); kinc_matrix4x4_set(&mat, 1, 1, fView._22); kinc_matrix4x4_set(&mat, 1, 2, fView._23); kinc_matrix4x4_set(&mat, 1, 3, fView._24); kinc_matrix4x4_set(&mat, 2, 0, fView._31); kinc_matrix4x4_set(&mat, 2, 1, fView._32); kinc_matrix4x4_set(&mat, 2, 2, fView._33); kinc_matrix4x4_set(&mat, 2, 3, fView._34); kinc_matrix4x4_set(&mat, 3, 0, fView._41); kinc_matrix4x4_set(&mat, 3, 1, fView._42); kinc_matrix4x4_set(&mat, 3, 2, fView._43); kinc_matrix4x4_set(&mat, 3, 3, fView._44); return mat; } } kinc_vr_sensor_state_t kinc_vr_interface_get_sensor_state(int eye) { kinc_vr_pose_state_t poseState; ovrQuatf orientation = EyeRenderPose[eye].Orientation; poseState.vrPose.orientation.x = orientation.x; poseState.vrPose.orientation.y = orientation.y; poseState.vrPose.orientation.z = orientation.z; poseState.vrPose.orientation.w = orientation.w; ovrVector3f pos = EyeRenderPose[eye].Position; poseState.vrPose.position.x = pos.x; poseState.vrPose.position.y = pos.y; poseState.vrPose.position.z = pos.z; ovrFovPort fov = hmdDesc.DefaultEyeFov[eye]; poseState.vrPose.left = fov.LeftTan; poseState.vrPose.right = fov.RightTan; poseState.vrPose.bottom = fov.DownTan; poseState.vrPose.top = fov.UpTan; // Get the pose information in XM format XMVECTOR eyeQuat = XMVectorSet(orientation.x, orientation.y, orientation.z, orientation.w); XMVECTOR eyePos = XMVectorSet(pos.x, pos.y, pos.z, 0); // Get view and projection matrices for the Rift camera Camera finalCam(eyePos, eyeQuat); XMMATRIX view = finalCam.GetViewMatrix(); ovrMatrix4f p = ovrMatrix4f_Projection(fov, 0.2f, 1000.0f, ovrProjection_None); XMMATRIX proj = XMMatrixSet(p.M[0][0], p.M[1][0], p.M[2][0], p.M[3][0], p.M[0][1], p.M[1][1], p.M[2][1], p.M[3][1], p.M[0][2], p.M[1][2], p.M[2][2], p.M[3][2], p.M[0][3], p.M[1][3], p.M[2][3], p.M[3][3]); poseState.vrPose.eye = convert(view); kinc_matrix4x4_transpose(&poseState.vrPose.eye); poseState.vrPose.projection = convert(proj); kinc_matrix4x4_transpose(&poseState.vrPose.projection); ovrSessionStatus sessionStatus; ovr_GetSessionStatus(session, &sessionStatus); poseState.isVisible = sessionStatus.IsVisible; poseState.hmdPresenting = sessionStatus.HmdPresent; poseState.hmdMounted = sessionStatus.HmdMounted; poseState.displayLost = sessionStatus.DisplayLost; poseState.shouldQuit = sessionStatus.ShouldQuit; poseState.shouldRecenter = sessionStatus.ShouldRecenter; sensorStates[eye].pose = poseState; return sensorStates[eye]; } kinc_vr_pose_state_t kinc_vr_interface_get_controller(int index) { kinc_vr_pose_state_t todo; return todo; } void kinc_vr_interface_warp_swap() { // Initialize our single full screen Fov layer. ovrLayerEyeFov ld = {}; ld.Header.Type = ovrLayerType_EyeFov; ld.Header.Flags = 0; if (isVisible) { for (int eye = 0; eye < 2; ++eye) { ld.ColorTexture[eye] = pEyeRenderTexture[eye]->TextureChain; ld.Viewport[eye] = eyeRenderViewport[eye]; ld.Fov[eye] = hmdDesc.DefaultEyeFov[eye]; ld.RenderPose[eye] = EyeRenderPose[eye]; ld.SensorSampleTime = sensorSampleTime; } } ovrLayerHeader *layers = &ld.Header; ovrResult result = ovr_SubmitFrame(session, frameIndex, nullptr, &layers, 1); if (!OVR_SUCCESS(result)) { isVisible = false; } else { isVisible = true; } frameIndex++; // Render mirror ID3D11Texture2D *tex = nullptr; ovr_GetMirrorTextureBufferDX(session, mirrorTexture, IID_PPV_ARGS(&tex)); dx_ctx.context->CopyResource(backBuffer, tex); tex->Release(); } void kinc_vr_interface_update_tracking_origin(kinc_tracking_origin_t origin) { switch (origin) { case KINC_TRACKING_ORIGIN_STAND: ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel); break; case KINC_TRACKING_ORIGIN_SIT: ovr_SetTrackingOriginType(session, ovrTrackingOrigin_EyeLevel); break; default: ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel); break; } } void kinc_vr_interface_reset_hmd_pose() { ovr_RecenterTrackingOrigin(session); } void kinc_vr_interface_ovr_shutdown() { ovr_Shutdown(); } #endif