From d52cb04f26bfd51a646505ffa35c286df0c64dfc Mon Sep 17 00:00:00 2001 From: JeroMiya Date: Fri, 8 Jul 2016 18:29:46 -0400 Subject: [PATCH 01/10] Work in progress. At this point it is building, and running initially without crashing, but no display and crash on exit. --- .../Plugins/OSVR/Source/OSVR/OSVR.Build.cs | 15 +- .../Source/OSVR/Private/OSVRCustomPresent.cpp | 1 + .../Source/OSVR/Private/OSVRCustomPresent.h | 57 +++- .../OSVR/Private/OSVRCustomPresentD3D11.h | 73 +---- .../OSVR/Private/OSVRCustomPresentOpenGL.h | 265 +++++++++++++++++- .../OSVR/Source/OSVR/Private/OSVRHMD.cpp | 19 +- .../OSVR/Source/OSVR/Private/OSVRHMD.h | 8 +- .../OSVR/Source/OSVRInput/OSVRInput.Build.cs | 16 +- 8 files changed, 361 insertions(+), 93 deletions(-) diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/OSVR.Build.cs b/OSVRUnreal/Plugins/OSVR/Source/OSVR/OSVR.Build.cs index 398c68f..e685def 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/OSVR.Build.cs +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/OSVR.Build.cs @@ -7,9 +7,15 @@ public OSVR(TargetInfo Target) { PrivateIncludePathModuleNames.Add("TargetPlatform"); + var EngineDir = Path.GetFullPath(BuildConfiguration.RelativeEnginePath); + var openglDrvPrivatePath = Path.Combine(EngineDir, @"Source\Runtime\OpenGLDrv\Private"); + var openglPath = Path.Combine(EngineDir, @"Source\ThirdParty\OpenGL"); + PrivateIncludePaths.AddRange( new string[] { "OSVR/Private", + openglDrvPrivatePath, + openglPath // ... add other private include paths required here ... } ); @@ -29,7 +35,8 @@ public OSVR(TargetInfo Target) "Renderer", "ShaderCore", "HeadMountedDisplay", - "Json" + "Json", + "OpenGLDrv" // ... add private dependencies that you statically link with here ... } ); @@ -38,12 +45,14 @@ public OSVR(TargetInfo Target) PrivateDependencyModuleNames.Add("UnrealEd"); } - if(Target.Platform == UnrealTargetPlatform.Win32 || Target.Platform == UnrealTargetPlatform.Win64) + AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenGL"); + + if (Target.Platform == UnrealTargetPlatform.Win32 || Target.Platform == UnrealTargetPlatform.Win64) { PrivateDependencyModuleNames.AddRange(new string[] { "D3D11RHI" }); // Required for some private headers needed for the rendering support. - var EngineDir = Path.GetFullPath(BuildConfiguration.RelativeEnginePath); + PrivateIncludePaths.AddRange( new string[] { Path.Combine(EngineDir, @"Source\Runtime\Windows\D3D11RHI\Private"), diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.cpp b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.cpp index f1f4287..f29e096 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.cpp +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.cpp @@ -18,3 +18,4 @@ #include "OSVRCustomPresent.h" DEFINE_LOG_CATEGORY(FOSVRCustomPresentLog); + diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h index ef25d6c..389b603 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h @@ -23,7 +23,7 @@ DECLARE_LOG_CATEGORY_EXTERN(FOSVRCustomPresentLog, Log, All); -template +//template class FOSVRCustomPresent : public FRHICustomPresent { public: @@ -92,8 +92,54 @@ class FOSVRCustomPresent : public FRHICustomPresent return bInitialized; } - virtual void GetProjectionMatrix(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) = 0; - virtual bool UpdateViewport(const FViewport& InViewport, class FRHIViewport* InViewportRHI) = 0; + virtual void GetProjectionMatrix(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) + { + OSVR_ReturnCode rc; + rc = osvrRenderManagerGetDefaultRenderParams(&mRenderParams); + check(rc == OSVR_RETURN_SUCCESS); + + mRenderParams.nearClipDistanceMeters = static_cast(nearClip); + mRenderParams.farClipDistanceMeters = static_cast(farClip); + + // this method gets called with alternating eyes starting with the left. We get the render info when + // the left eye (index 0) is requested (releasing the old one, if any), + // and re-use the same collection when the right eye (index 0) is requested + if (eye == 0 || !mCachedRenderInfoCollection) { + if (mCachedRenderInfoCollection) { + rc = osvrRenderManagerReleaseRenderInfoCollection(mCachedRenderInfoCollection); + check(rc == OSVR_RETURN_SUCCESS); + } + rc = osvrRenderManagerGetRenderInfoCollection(mRenderManager, mRenderParams, &mCachedRenderInfoCollection); + check(rc == OSVR_RETURN_SUCCESS); + } + + GetProjectionMatrixImpl(eye, left, right, bottom, top, nearClip, farClip); + } + + virtual bool UpdateViewport(const FViewport& InViewport, class FRHIViewport* InViewportRHI) + { + FScopeLock lock(&mOSVRMutex); + + check(IsInGameThread()); + if (!IsInitialized()) + { + UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("UpdateViewport called but custom present is not initialized - doing nothing")); + return false; + } + else + { + check(InViewportRHI); + auto oldCustomPresent = InViewportRHI->GetCustomPresent(); + if (oldCustomPresent != this) + { + InViewportRHI->SetCustomPresent(this); + } + // UpdateViewport is called before we're initialized, so we have to + // defer updates to the render buffers until we're in the render thread. + //bRenderBuffersNeedToUpdate = true; + return true; + } + } // RenderManager normalizes displays a bit. We create the render target assuming horizontal side-by-side. // RenderManager then rotates that render texture if needed for vertical side-by-side displays. @@ -119,10 +165,11 @@ class FOSVRCustomPresent : public FRHICustomPresent OSVR_RenderInfoCollection mCachedRenderInfoCollection = nullptr; virtual bool CalculateRenderTargetSizeImpl(uint32& InOutSizeX, uint32& InOutSizeY) = 0; - + virtual void GetProjectionMatrixImpl(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) = 0; virtual bool InitializeImpl() = 0; - virtual TGraphicsDevice* GetGraphicsDevice() + template + TGraphicsDevice* GetGraphicsDevice() { auto ret = RHIGetNativeDevice(); return reinterpret_cast(ret); diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h index 4b01682..ba103bf 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h @@ -26,42 +26,14 @@ #include "HideWindowsPlatformTypes.h" #include "Runtime/Windows/D3D11RHI/Private/D3D11RHIPrivate.h" -class FCurrentCustomPresent : public FOSVRCustomPresent +class FDirect3D11CustomPresent : public FOSVRCustomPresent// { public: - FCurrentCustomPresent(OSVR_ClientContext clientContext, float screenScale) : + FDirect3D11CustomPresent(OSVR_ClientContext clientContext, float screenScale) : FOSVRCustomPresent(clientContext, screenScale) { } - virtual bool UpdateViewport(const FViewport& InViewport, class FRHIViewport* InViewportRHI) override - { - FScopeLock lock(&mOSVRMutex); - - check(IsInGameThread()); - if (!IsInitialized()) - { - UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("UpdateViewport called but custom present is not initialized - doing nothing")); - return false; - } - else - { - check(InViewportRHI); - //const FTexture2DRHIRef& rt = InViewport.GetRenderTargetTexture(); - //check(IsValidRef(rt)); - //SetRenderTargetTexture((ID3D11Texture2D*)rt->GetNativeResource()); // @todo: do we need to do this? - auto oldCustomPresent = InViewportRHI->GetCustomPresent(); - if (oldCustomPresent != this) - { - InViewportRHI->SetCustomPresent(this); - } - // UpdateViewport is called before we're initialized, so we have to - // defer updates to the render buffers until we're in the render thread. - //bRenderBuffersNeedToUpdate = true; - return true; - } - } - virtual bool AllocateRenderTargetTexture(uint32 index, uint32 sizeX, uint32 sizeY, uint8 format, uint32 numMips, uint32 flags, uint32 targetableTextureFlags, FTexture2DRHIRef& outTargetableTexture, FTexture2DRHIRef& outShaderResourceTexture, uint32 numSamples = 1) override { // @todo how should we determine SRGB? @@ -74,7 +46,7 @@ class FCurrentCustomPresent : public FOSVRCustomPresent if (IsInitialized()) { auto d3d11RHI = static_cast(GDynamicRHI); - auto graphicsDevice = GetGraphicsDevice(); + auto graphicsDevice = GetGraphicsDevice(); HRESULT hr; D3D11_TEXTURE2D_DESC textureDesc; memset(&textureDesc, 0, sizeof(textureDesc)); @@ -158,28 +130,17 @@ class FCurrentCustomPresent : public FOSVRCustomPresent return false; } +protected: + ID3D11Texture2D* RenderTargetTexture = NULL; + ID3D11RenderTargetView* RenderTargetView = NULL; + + TArray mRenderBuffers; + TArray mRenderInfos; + OSVR_RenderManagerD3D11 mRenderManagerD3D11 = nullptr; - virtual void GetProjectionMatrix(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) override + virtual void GetProjectionMatrixImpl(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) override { OSVR_ReturnCode rc; - rc = osvrRenderManagerGetDefaultRenderParams(&mRenderParams); - check(rc == OSVR_RETURN_SUCCESS); - - mRenderParams.nearClipDistanceMeters = static_cast(nearClip); - mRenderParams.farClipDistanceMeters = static_cast(farClip); - - // this method gets called with alternating eyes starting with the left. We get the render info when - // the left eye (index 0) is requested (releasing the old one, if any), - // and re-use the same collection when the right eye (index 0) is requested - if (eye == 0 || !mCachedRenderInfoCollection) { - if (mCachedRenderInfoCollection) { - rc = osvrRenderManagerReleaseRenderInfoCollection(mCachedRenderInfoCollection); - check(rc == OSVR_RETURN_SUCCESS); - } - rc = osvrRenderManagerGetRenderInfoCollection(mRenderManager, mRenderParams, &mCachedRenderInfoCollection); - check(rc == OSVR_RETURN_SUCCESS); - } - OSVR_RenderInfoD3D11 renderInfo; rc = osvrRenderManagerGetRenderInfoFromCollectionD3D11(mCachedRenderInfoCollection, eye, &renderInfo); check(rc == OSVR_RETURN_SUCCESS); @@ -193,14 +154,6 @@ class FCurrentCustomPresent : public FOSVRCustomPresent bottom = static_cast(renderInfo.projection.bottom); } -protected: - ID3D11Texture2D* RenderTargetTexture = NULL; - ID3D11RenderTargetView* RenderTargetView = NULL; - - TArray mRenderBuffers; - TArray mRenderInfos; - OSVR_RenderManagerD3D11 mRenderManagerD3D11 = nullptr; - virtual bool CalculateRenderTargetSizeImpl(uint32& InOutSizeX, uint32& InOutSizeY) override { if (InitializeImpl()) @@ -333,7 +286,7 @@ class FCurrentCustomPresent : public FOSVRCustomPresent D3D11_TEXTURE2D_DESC renderTextureDesc; RenderTargetTexture->GetDesc(&renderTextureDesc); - auto graphicsDevice = GetGraphicsDevice(); + auto graphicsDevice = GetGraphicsDevice(); //D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc; //memset(&renderTargetViewDesc, 0, sizeof(renderTargetViewDesc)); @@ -409,7 +362,7 @@ class FCurrentCustomPresent : public FOSVRCustomPresent OSVR_GraphicsLibraryD3D11 ret; // Put the device and context into a structure to let RenderManager // know to use this one rather than creating its own. - ret.device = GetGraphicsDevice(); + ret.device = GetGraphicsDevice(); ID3D11DeviceContext *ctx = NULL; ret.device->GetImmediateContext(&ctx); check(ctx); diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h index 963bb8c..2878966 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h @@ -1,5 +1,5 @@ // -// Copyright 2014, 2015 Razer Inc. +// Copyright 2016 Sensics, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,28 +16,37 @@ #pragma once -#if !PLATFORM_WINDOWS #include "IOSVR.h" #include "OSVRCustomPresent.h" #include +#include -// @todo OpenGL implementation -class FCurrentCustomPresent : public FOSVRCustomPresent +#include "OpenGLDrvPrivate.h" +#include "OpenGLResources.h" + +class FOpenGLCustomPresent : public FOSVRCustomPresent { public: - FCurrentCustomPresent(OSVR_ClientContext clientContext, float screenScale) : + FOpenGLCustomPresent(OSVR_ClientContext clientContext, float screenScale) : FOSVRCustomPresent(clientContext, screenScale) { } + virtual ~FOpenGLCustomPresent() + { + if (RenderTargetTexture > 0) + { + glDeleteTextures(1, &RenderTargetTexture); + } + } + protected: - //virtual osvr::renderkit::GraphicsLibrary CreateGraphicsLibrary() override - //{ - // osvr::renderkit::GraphicsLibrary ret; - // // OpenGL path not implemented yet - // return ret; - //} + + TArray mRenderBuffers; + TArray mRenderInfos; + OSVR_RenderManagerOpenGL mRenderManagerOpenGL = nullptr; + GLuint RenderTargetTexture = 0; virtual FString GetGraphicsLibraryName() override { @@ -49,6 +58,238 @@ class FCurrentCustomPresent : public FOSVRCustomPresent return false; } + virtual bool AllocateRenderTargetTexture(uint32 index, uint32 sizeX, uint32 sizeY, uint8 format, uint32 numMips, uint32 flags, uint32 targetableTextureFlags, FTexture2DRHIRef& outTargetableTexture, FTexture2DRHIRef& outShaderResourceTexture, uint32 numSamples = 1) override + { + FScopeLock lock(&mOSVRMutex); + if (IsInitialized()) + { + // create the color buffer + if (RenderTargetTexture > 0) + { + glDeleteTextures(1, &RenderTargetTexture); + } + RenderTargetTexture = 0; + osvrRenderManagerCreateColorBufferOpenGL(sizeX, sizeY, &RenderTargetTexture); + + //SetRenderTargetTexture(D3DTexture); + + auto GLRHI = static_cast(GDynamicRHI); + GLenum target = numSamples > 1 ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; + GLenum attachment = GL_NONE; + bool bAllocatedStorage = false; + numMips = 1; + uint8* textureRange = nullptr; + + auto targetableTexture = new FOpenGLTexture2D( + GLRHI, RenderTargetTexture, target, attachment, sizeX, sizeY, 0, numMips, numSamples, 1, EPixelFormat(format), false, + bAllocatedStorage, flags, textureRange, FClearValueBinding::Black); + + outTargetableTexture = targetableTexture->GetTexture2D(); + outShaderResourceTexture = targetableTexture->GetTexture2D(); + mRenderTexture = targetableTexture; + bRenderBuffersNeedToUpdate = true; + UpdateRenderBuffers(); + return true; + } + return false; + } + + virtual void GetProjectionMatrixImpl(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) override + { + OSVR_ReturnCode rc; + OSVR_RenderInfoOpenGL renderInfo; + rc = osvrRenderManagerGetRenderInfoFromCollectionOpenGL(mCachedRenderInfoCollection, eye, &renderInfo); + check(rc == OSVR_RETURN_SUCCESS); + + // previously we divided these by renderInfo.projection.nearClip but we need + // to pass these unmodified through to the OSVR_Projection_to_D3D call (and OpenGL + // equivalent) + left = static_cast(renderInfo.projection.left); + right = static_cast(renderInfo.projection.right); + top = static_cast(renderInfo.projection.top); + bottom = static_cast(renderInfo.projection.bottom); + } + + virtual bool CalculateRenderTargetSizeImpl(uint32& InOutSizeX, uint32& InOutSizeY) override + { + if (InitializeImpl()) + { + // Should we create a RenderParams? + OSVR_ReturnCode rc; + + rc = osvrRenderManagerGetDefaultRenderParams(&mRenderParams); + check(rc == OSVR_RETURN_SUCCESS); + + OSVR_RenderInfoCollection renderInfoCollection = { 0 }; + rc = osvrRenderManagerGetRenderInfoCollection(mRenderManager, mRenderParams, &renderInfoCollection); + check(rc == OSVR_RETURN_SUCCESS); + + OSVR_RenderInfoCount numRenderInfo; + rc = osvrRenderManagerGetNumRenderInfoInCollection(renderInfoCollection, &numRenderInfo); + check(rc == OSVR_RETURN_SUCCESS); + + mRenderInfos.Empty(); + for (size_t i = 0; i < numRenderInfo; i++) + { + OSVR_RenderInfoOpenGL renderInfo; + rc = osvrRenderManagerGetRenderInfoFromCollectionOpenGL(renderInfoCollection, i, &renderInfo); + check(rc == OSVR_RETURN_SUCCESS); + + mRenderInfos.Add(renderInfo); + } + + rc = osvrRenderManagerReleaseRenderInfoCollection(renderInfoCollection); + check(rc == OSVR_RETURN_SUCCESS); + + // check some assumptions. Should all be the same height. + check(mRenderInfos.Num() == 2); + check(mRenderInfos[0].viewport.height == mRenderInfos[1].viewport.height); + + mRenderInfos[0].viewport.width = int(float(mRenderInfos[0].viewport.width) * mScreenScale); + mRenderInfos[0].viewport.height = int(float(mRenderInfos[0].viewport.height) * mScreenScale); + mRenderInfos[1].viewport.width = mRenderInfos[0].viewport.width; + mRenderInfos[1].viewport.height = mRenderInfos[0].viewport.height; + mRenderInfos[1].viewport.left = mRenderInfos[0].viewport.width; + + InOutSizeX = mRenderInfos[0].viewport.width + mRenderInfos[1].viewport.width; + InOutSizeY = mRenderInfos[0].viewport.height; + check(InOutSizeX != 0 && InOutSizeY != 0); + return true; + } + return false; + } + + virtual bool InitializeImpl() override + { + if (!IsInitialized()) + { + auto graphicsLibrary = CreateGraphicsLibrary(); + auto graphicsLibraryName = GetGraphicsLibraryName(); + OSVR_ReturnCode rc; + + if (!mClientContext) + { + UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("Can't initialize FOSVRCustomPresent without a valid client context")); + return false; + } + + rc = osvrCreateRenderManagerOpenGL( + mClientContext, TCHAR_TO_ANSI(*graphicsLibraryName), + graphicsLibrary, &mRenderManager, &mRenderManagerOpenGL); + + if (rc == OSVR_RETURN_FAILURE || !mRenderManager || !mRenderManagerOpenGL) + { + UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("osvrCreateRenderManagerD3D11 call failed, or returned numm renderManager/renderManagerD3D11 instances")); + return false; + } + + rc = osvrRenderManagerGetDoingOkay(mRenderManager); + if (rc == OSVR_RETURN_FAILURE) + { + UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("osvrRenderManagerGetDoingOkay call failed. Perhaps there was an error during initialization?")); + return false; + } + + OSVR_OpenResultsOpenGL results; + rc = osvrRenderManagerOpenDisplayOpenGL(mRenderManagerOpenGL, &results); + if (rc == OSVR_RETURN_FAILURE || results.status == OSVR_OPEN_STATUS_FAILURE) + { + UE_LOG(FOSVRCustomPresentLog, Warning, + TEXT("osvrRenderManagerOpenDisplayD3D11 call failed, or the result status was OSVR_OPEN_STATUS_FAILURE. Potential causes could be that the display is already open in direct mode with another app, or the display does not support direct mode")); + return false; + } + + // @todo: create the textures? + + bInitialized = true; + } + return true; + } + + virtual void FinishRendering() override + { + check(IsInitialized()); + UpdateRenderBuffers(); + // all of the render manager samples keep the flipY at the default false, + // for both OpenGL and DirectX. Is this even needed anymore? + OSVR_ReturnCode rc; + OSVR_RenderManagerPresentState presentState; + rc = osvrRenderManagerStartPresentRenderBuffers(&presentState); + check(rc == OSVR_RETURN_SUCCESS); + check(mRenderBuffers.Num() == mRenderInfos.Num() && mRenderBuffers.Num() == mViewportDescriptions.Num()); + for (size_t i = 0; i < mRenderBuffers.Num(); i++) + { + rc = osvrRenderManagerPresentRenderBufferOpenGL(presentState, mRenderBuffers[i], mRenderInfos[i], mViewportDescriptions[i]); + check(rc == OSVR_RETURN_SUCCESS); + } + rc = osvrRenderManagerFinishPresentRenderBuffers(mRenderManager, presentState, mRenderParams, ShouldFlipY() ? OSVR_TRUE : OSVR_FALSE); + check(rc == OSVR_RETURN_SUCCESS); + } + + virtual void UpdateRenderBuffers() override + { + OSVR_ReturnCode rc; + + check(IsInitialized()); + if (bRenderBuffersNeedToUpdate) + { + //check(RenderTargetTexture); + mRenderBuffers.Empty(); + + // Adding two RenderBuffers, but they both point to the same D3D11 texture target + for (int i = 0; i < 2; i++) + { + OSVR_RenderBufferOpenGL buffer = { 0 }; + buffer.colorBufferName = RenderTargetTexture; + mRenderBuffers.Add(buffer); + } + + // We need to register these new buffers. + // @todo RegisterRenderBuffers doesn't do anything other than set a flag and crash + // if you pass it a non-empty vector here. Passing it a dummy array for now. + + { + OSVR_RenderManagerRegisterBufferState state; + rc = osvrRenderManagerStartRegisterRenderBuffers(&state); + check(rc == OSVR_RETURN_SUCCESS); + + for (size_t i = 0; i < mRenderBuffers.Num(); i++) + { + rc = osvrRenderManagerRegisterRenderBufferOpenGL(state, mRenderBuffers[i]); + check(rc == OSVR_RETURN_SUCCESS); + } + + rc = osvrRenderManagerFinishRegisterRenderBuffers(mRenderManager, state, false); + check(rc == OSVR_RETURN_SUCCESS); + } + + // Now specify the viewports for each. + mViewportDescriptions.Empty(); + + OSVR_ViewportDescription leftEye, rightEye; + + leftEye.left = 0; + leftEye.lower = 0; + leftEye.width = 0.5; + leftEye.height = 1.0; + mViewportDescriptions.Add(leftEye); + + rightEye.left = 0.5; + rightEye.lower = 0; + rightEye.width = 0.5; + rightEye.height = 1.0; + mViewportDescriptions.Add(rightEye); + + bRenderBuffersNeedToUpdate = false; + } + } + + virtual OSVR_GraphicsLibraryOpenGL CreateGraphicsLibrary() + { + OSVR_GraphicsLibraryOpenGL ret = { 0 }; + // @todo: anything needed here? + return ret; + } }; -#endif + diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRHMD.cpp b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRHMD.cpp index 5c7e586..2115c35 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRHMD.cpp +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRHMD.cpp @@ -21,6 +21,8 @@ #include "SharedPointer.h" #include "SceneViewport.h" #include "OSVREntryPoint.h" +#include "OSVRCustomPresentOpenGL.h" +#include "OSVRCustomPresentD3D11.h" #include "Runtime/Core/Public/Misc/DateTime.h" @@ -32,6 +34,7 @@ #include "AllowWindowsPlatformTypes.h" #include #include +#include #include "HideWindowsPlatformTypes.h" #else #include @@ -69,11 +72,19 @@ void FOSVRHMD::OnEndPlay() void FOSVRHMD::StartCustomPresent() { #if PLATFORM_WINDOWS - if (!mCustomPresent && IsPCPlatform(GMaxRHIShaderPlatform) && !IsOpenGLPlatform(GMaxRHIShaderPlatform)) + if (!mCustomPresent) { - // currently, FCustomPresent creates its own client context, so no need to - // synchronize with the one from FOSVREntryPoint. - mCustomPresent = new FCurrentCustomPresent(nullptr/*osvrClientContext*/, mScreenScale); + if (IsOpenGLPlatform(GMaxRHIShaderPlatform)) + { + mCustomPresent = new FOpenGLCustomPresent(nullptr/*osvrClientContext*/, mScreenScale); + } + else + { + + // currently, FCustomPresent creates its own client context, so no need to + // synchronize with the one from FOSVREntryPoint. + mCustomPresent = new FDirect3D11CustomPresent(nullptr/*osvrClientContext*/, mScreenScale); + } } #endif } diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRHMD.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRHMD.h index 4974936..eec4be1 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRHMD.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRHMD.h @@ -26,11 +26,7 @@ #include -#if PLATFORM_WINDOWS -#include "OSVRCustomPresentD3D11.h" -#else -#include "OSVRCustomPresentOpenGL.h" -#endif +#include "OSVRCustomPresent.h" DECLARE_LOG_CATEGORY_EXTERN(OSVRHMDLog, Log, All); @@ -207,5 +203,5 @@ class FOSVRHMD : public IHeadMountedDisplay, public ISceneViewExtension, public OSVRHMDDescription HMDDescription; OSVR_DisplayConfig DisplayConfig; - TRefCountPtr mCustomPresent; + TRefCountPtr mCustomPresent; }; diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVRInput/OSVRInput.Build.cs b/OSVRUnreal/Plugins/OSVR/Source/OSVRInput/OSVRInput.Build.cs index d3a77b7..5248c72 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVRInput/OSVRInput.Build.cs +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVRInput/OSVRInput.Build.cs @@ -5,13 +5,20 @@ public class OSVRInput : ModuleRules { public OSVRInput(TargetInfo Target) { + var EngineDir = Path.GetFullPath(BuildConfiguration.RelativeEnginePath); + var openglDrvPrivatePath = Path.Combine(EngineDir, @"Source\Runtime\OpenGLDrv\Private"); + var openglPath = Path.Combine(EngineDir, @"Source\ThirdParty\OpenGL"); PrivateIncludePaths.AddRange( new string[] { "OSVR/Private", + openglDrvPrivatePath, + openglPath // ... add other private include paths required here ... } ); + + PrivateDependencyModuleNames.AddRange( new string[] { @@ -28,11 +35,15 @@ public OSVRInput(TargetInfo Target) "Renderer", "ShaderCore", "HeadMountedDisplay", - "Json" + "Json", + "OpenGLDrv" // ... add private dependencies that you statically link with here ... } ); - if(UEBuildConfiguration.bBuildEditor == true) + + AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenGL"); + + if (UEBuildConfiguration.bBuildEditor == true) { PrivateDependencyModuleNames.Add("UnrealEd"); } @@ -43,7 +54,6 @@ public OSVRInput(TargetInfo Target) PrivateDependencyModuleNames.AddRange(new string[] { "D3D11RHI" }); // Required for some private headers needed for the rendering support. - var EngineDir = Path.GetFullPath(BuildConfiguration.RelativeEnginePath); PrivateIncludePaths.AddRange( new string[] { Path.Combine(EngineDir, @"Source\Runtime\Windows\D3D11RHI\Private"), From 178bea27264b16e38162198bb8e2a9d101583d89 Mon Sep 17 00:00:00 2001 From: JeroMiya Date: Tue, 12 Jul 2016 17:07:30 -0400 Subject: [PATCH 02/10] Adjusted header in OSVRCustomPresentOpenGL.h for changes to RenderManager. --- .../Source/OSVR/Private/OSVRCustomPresentOpenGL.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h index 2878966..1bb8cba 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h @@ -16,10 +16,23 @@ #pragma once + #include "IOSVR.h" #include "OSVRCustomPresent.h" - #include +#include + +#if OSVR_ANDROID +// @todo this may not work - if not, what headers is unreal expecting? +#define OSVR_RM_USE_OPENGLES20 1 +#else +// you can't include and in the same source file, +// and unreal is going to include +#define OSVR_RM_SKIP_GL_INCLUDE 1 +#define OSVR_RM_SKIP_GLEXT_INCLUDE 1 +#include +#endif + #include #include "OpenGLDrvPrivate.h" From 08f255aaf2d6f631ea794da4da7af0e681d2da21 Mon Sep 17 00:00:00 2001 From: JeroMiya Date: Mon, 18 Jul 2016 21:00:56 -0400 Subject: [PATCH 03/10] Work in progress split of FOSVRCustomPresent::Initialize to move the OpenDisplay call into a separate call that only runs on the rendering thread. --- .../Source/OSVR/Private/OSVRCustomPresent.h | 18 +++++++++-- .../OSVR/Private/OSVRCustomPresentD3D11.h | 27 ++++++++++------- .../OSVR/Private/OSVRCustomPresentOpenGL.h | 30 +++++++++++-------- .../OSVR/Source/OSVR/Private/OSVRRender.cpp | 13 ++++++-- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h index 389b603..e4b952b 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h @@ -80,7 +80,9 @@ class FOSVRCustomPresent : public FRHICustomPresent return true; } - // implement this in the sub-class + // Initializes RenderManager but does not open the displays + // Can be called from the render thread or game thread. + // @todo: this call should be lazy, then IsInitialized can be removed. virtual bool Initialize() { FScopeLock lock(&mOSVRMutex); @@ -92,6 +94,16 @@ class FOSVRCustomPresent : public FRHICustomPresent return bInitialized; } + virtual bool LazyOpenDisplay() + { + FScopeLock lock(&mOSVRMutex); + if(IsInRenderingThread() && IsInitialized() && !bDisplayOpen) + { + bDisplayOpen = LazyOpenDisplayImpl(); + } + return bDisplayOpen; + } + virtual void GetProjectionMatrix(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) { OSVR_ReturnCode rc; @@ -158,6 +170,7 @@ class FOSVRCustomPresent : public FRHICustomPresent bool bRenderBuffersNeedToUpdate = true; bool bInitialized = false; + bool bDisplayOpen = false; bool bOwnClientContext = true; float mScreenScale = 1.0f; OSVR_ClientContext mClientContext = nullptr; @@ -167,7 +180,8 @@ class FOSVRCustomPresent : public FRHICustomPresent virtual bool CalculateRenderTargetSizeImpl(uint32& InOutSizeX, uint32& InOutSizeY) = 0; virtual void GetProjectionMatrixImpl(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) = 0; virtual bool InitializeImpl() = 0; - + virtual bool LazyOpenDisplayImpl() = 0; + template TGraphicsDevice* GetGraphicsDevice() { diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h index ba103bf..9a2ffef 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h @@ -224,22 +224,29 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// return false; } - OSVR_OpenResultsD3D11 results; - rc = osvrRenderManagerOpenDisplayD3D11(mRenderManagerD3D11, &results); - if (rc == OSVR_RETURN_FAILURE || results.status == OSVR_OPEN_STATUS_FAILURE) - { - UE_LOG(FOSVRCustomPresentLog, Warning, - TEXT("osvrRenderManagerOpenDisplayD3D11 call failed, or the result status was OSVR_OPEN_STATUS_FAILURE. Potential causes could be that the display is already open in direct mode with another app, or the display does not support direct mode")); - return false; - } - - // @todo: create the textures? + // The display is not opened here, but in a separate call (LazyOpenDisplay) + // because we may be in the game thread here bInitialized = true; } return true; } + virtual bool LazyOpenDisplayImpl() override + { + // we can assume we're initialized and running on the rendering thread + // and we haven't already opened the display here (done in parent class) + OSVR_OpenResultsD3D11 results; + rc = osvrRenderManagerOpenDisplayD3D11(mRenderManagerD3D11, &results); + if (rc == OSVR_RETURN_FAILURE || results.status == OSVR_OPEN_STATUS_FAILURE) + { + UE_LOG(FOSVRCustomPresentLog, Warning, + TEXT("osvrRenderManagerOpenDisplayD3D11 call failed, or the result status was OSVR_OPEN_STATUS_FAILURE. Potential causes could be that the display is already open in direct mode with another app, or the display does not support direct mode")); + return false; + } + return true; + } + virtual void FinishRendering() override { check(IsInitialized()); diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h index 1bb8cba..36bab59 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h @@ -202,23 +202,29 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("osvrRenderManagerGetDoingOkay call failed. Perhaps there was an error during initialization?")); return false; } - - OSVR_OpenResultsOpenGL results; - rc = osvrRenderManagerOpenDisplayOpenGL(mRenderManagerOpenGL, &results); - if (rc == OSVR_RETURN_FAILURE || results.status == OSVR_OPEN_STATUS_FAILURE) - { - UE_LOG(FOSVRCustomPresentLog, Warning, - TEXT("osvrRenderManagerOpenDisplayD3D11 call failed, or the result status was OSVR_OPEN_STATUS_FAILURE. Potential causes could be that the display is already open in direct mode with another app, or the display does not support direct mode")); - return false; - } - - // @todo: create the textures? - + + // We don't open the display here, because this might be called from the game thread + // LazyOpenDisplay needs to be called on the rendering thread to open the display bInitialized = true; } return true; } + virtual bool LazyOpenDisplayImpl() override + { + // we can assume we're initialized and running on the rendering thread + // and we haven't already opened the display here (done in parent class) + OSVR_OpenResultsOpenGL results; + rc = osvrRenderManagerOpenDisplayOpenGL(mRenderManagerOpenGL, &results); + if (rc == OSVR_RETURN_FAILURE || results.status == OSVR_OPEN_STATUS_FAILURE) + { + UE_LOG(FOSVRCustomPresentLog, Warning, + TEXT("osvrRenderManagerOpenDisplayD3D11 call failed, or the result status was OSVR_OPEN_STATUS_FAILURE. Potential causes could be that the display is already open in direct mode with another app, or the display does not support direct mode")); + return false; + } + return true; + } + virtual void FinishRendering() override { check(IsInitialized()); diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp index 487ee15..c98c254 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp @@ -93,11 +93,17 @@ void FOSVRHMD::GetTimewarpMatrices_RenderThread(const struct FRenderingComposite void FOSVRHMD::PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& ViewFamily) { check(IsInRenderingThread()); - if (mCustomPresent && !mCustomPresent->IsInitialized()) + if (mCustomPresent) { - mCustomPresent->Initialize(); + // @todo make Initialize lazy and remove this if block + if(!mCustomPresent->IsInitialized()) + { + mCustomPresent->Initialize(); + } + + mCustomPresent->LazyOpenDisplay(); } - + FQuat lastHmdOrientation, hmdOrientation; FVector lastHmdPosition, hmdPosition; UpdateHeadPose(lastHmdOrientation, lastHmdPosition, hmdOrientation, hmdPosition); @@ -192,6 +198,7 @@ void FOSVRHMD::UpdateViewport(bool bUseSeparateRenderTarget, const FViewport& In bool FOSVRHMD::AllocateRenderTargetTexture(uint32 index, uint32 sizeX, uint32 sizeY, uint8 format, uint32 numMips, uint32 flags, uint32 targetableTextureFlags, FTexture2DRHIRef& outTargetableTexture, FTexture2DRHIRef& outShaderResourceTexture, uint32 numSamples) { check(index == 0); + check(IsInRenderingThread()); if (mCustomPresent && mCustomPresent->IsInitialized()) { return mCustomPresent->AllocateRenderTargetTexture(index, sizeX, sizeY, format, numMips, flags, targetableTextureFlags, outTargetableTexture, outShaderResourceTexture, numSamples); From c88835c8d747de2e193709af6a9639281a20ea1a Mon Sep 17 00:00:00 2001 From: JeroMiya Date: Tue, 19 Jul 2016 11:34:46 -0400 Subject: [PATCH 04/10] Fix compiler errors from last night's work in progress. --- .../OSVR/Private/OSVRCustomPresentD3D11.h | 2 +- .../OSVR/Private/OSVRCustomPresentOpenGL.h | 34 +++++++++---------- .../OSVR/Source/OSVR/Private/OSVRRender.cpp | 2 ++ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h index 9a2ffef..0008a3d 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h @@ -237,7 +237,7 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// // we can assume we're initialized and running on the rendering thread // and we haven't already opened the display here (done in parent class) OSVR_OpenResultsD3D11 results; - rc = osvrRenderManagerOpenDisplayD3D11(mRenderManagerD3D11, &results); + OSVR_ReturnCode rc = osvrRenderManagerOpenDisplayD3D11(mRenderManagerD3D11, &results); if (rc == OSVR_RETURN_FAILURE || results.status == OSVR_OPEN_STATUS_FAILURE) { UE_LOG(FOSVRCustomPresentLog, Warning, diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h index 36bab59..080d5de 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h @@ -82,7 +82,7 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent glDeleteTextures(1, &RenderTargetTexture); } RenderTargetTexture = 0; - osvrRenderManagerCreateColorBufferOpenGL(sizeX, sizeY, &RenderTargetTexture); + osvrRenderManagerCreateColorBufferOpenGL(sizeX, sizeY, GL_BGRA, &RenderTargetTexture); //SetRenderTargetTexture(D3DTexture); @@ -215,7 +215,7 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent // we can assume we're initialized and running on the rendering thread // and we haven't already opened the display here (done in parent class) OSVR_OpenResultsOpenGL results; - rc = osvrRenderManagerOpenDisplayOpenGL(mRenderManagerOpenGL, &results); + OSVR_ReturnCode rc = osvrRenderManagerOpenDisplayOpenGL(mRenderManagerOpenGL, &results); if (rc == OSVR_RETURN_FAILURE || results.status == OSVR_OPEN_STATUS_FAILURE) { UE_LOG(FOSVRCustomPresentLog, Warning, @@ -228,21 +228,21 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent virtual void FinishRendering() override { check(IsInitialized()); - UpdateRenderBuffers(); - // all of the render manager samples keep the flipY at the default false, - // for both OpenGL and DirectX. Is this even needed anymore? - OSVR_ReturnCode rc; - OSVR_RenderManagerPresentState presentState; - rc = osvrRenderManagerStartPresentRenderBuffers(&presentState); - check(rc == OSVR_RETURN_SUCCESS); - check(mRenderBuffers.Num() == mRenderInfos.Num() && mRenderBuffers.Num() == mViewportDescriptions.Num()); - for (size_t i = 0; i < mRenderBuffers.Num(); i++) - { - rc = osvrRenderManagerPresentRenderBufferOpenGL(presentState, mRenderBuffers[i], mRenderInfos[i], mViewportDescriptions[i]); - check(rc == OSVR_RETURN_SUCCESS); - } - rc = osvrRenderManagerFinishPresentRenderBuffers(mRenderManager, presentState, mRenderParams, ShouldFlipY() ? OSVR_TRUE : OSVR_FALSE); - check(rc == OSVR_RETURN_SUCCESS); + //UpdateRenderBuffers(); + //// all of the render manager samples keep the flipY at the default false, + //// for both OpenGL and DirectX. Is this even needed anymore? + //OSVR_ReturnCode rc; + //OSVR_RenderManagerPresentState presentState; + //rc = osvrRenderManagerStartPresentRenderBuffers(&presentState); + //check(rc == OSVR_RETURN_SUCCESS); + //check(mRenderBuffers.Num() == mRenderInfos.Num() && mRenderBuffers.Num() == mViewportDescriptions.Num()); + //for (size_t i = 0; i < mRenderBuffers.Num(); i++) + //{ + // rc = osvrRenderManagerPresentRenderBufferOpenGL(presentState, mRenderBuffers[i], mRenderInfos[i], mViewportDescriptions[i]); + // check(rc == OSVR_RETURN_SUCCESS); + //} + //rc = osvrRenderManagerFinishPresentRenderBuffers(mRenderManager, presentState, mRenderParams, ShouldFlipY() ? OSVR_TRUE : OSVR_FALSE); + //check(rc == OSVR_RETURN_SUCCESS); } virtual void UpdateRenderBuffers() override diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp index c98c254..b72504a 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp @@ -201,6 +201,8 @@ bool FOSVRHMD::AllocateRenderTargetTexture(uint32 index, uint32 sizeX, uint32 si check(IsInRenderingThread()); if (mCustomPresent && mCustomPresent->IsInitialized()) { + bool displayOpen = mCustomPresent->LazyOpenDisplay(); + check(displayOpen); return mCustomPresent->AllocateRenderTargetTexture(index, sizeX, sizeY, format, numMips, flags, targetableTextureFlags, outTargetableTexture, outShaderResourceTexture, numSamples); } return false; From a3c7ceb243ca8ceceb1ccf6b0bebf6329ccec0ab Mon Sep 17 00:00:00 2001 From: JeroMiya Date: Thu, 21 Jul 2016 10:16:48 -0400 Subject: [PATCH 05/10] Initial implementation of the toolkit for the OpenGL implementation. --- OSVRUnreal/Config/DefaultEngine.ini | 8 + OSVRUnreal/Content/Maps/basic.umap | Bin 809949 -> 747335 bytes .../Source/OSVR/Private/OSVRCustomPresent.h | 7 + .../OSVR/Private/OSVRCustomPresentD3D11.h | 7 + .../OSVR/Private/OSVRCustomPresentOpenGL.h | 192 +++++++++++++----- .../OSVR/Source/OSVR/Private/OSVRRender.cpp | 12 ++ 6 files changed, 177 insertions(+), 49 deletions(-) diff --git a/OSVRUnreal/Config/DefaultEngine.ini b/OSVRUnreal/Config/DefaultEngine.ini index 019e74b..be84d24 100644 --- a/OSVRUnreal/Config/DefaultEngine.ini +++ b/OSVRUnreal/Config/DefaultEngine.ini @@ -71,3 +71,11 @@ UIScaleRule=ShortestSide CustomScalingRuleClass=None UIScaleCurve=(EditorCurveData=(PreInfinityExtrap=RCCE_Constant,PostInfinityExtrap=RCCE_Constant,Keys=((Time=480.000000,Value=0.444000),(Time=720.000000,Value=0.666000),(Time=1080.000000,Value=1.000000),(Time=8640.000000,Value=8.000000)),DefaultValue=340282346638528859811704183484516925440.000000),ExternalCurve=None) +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +-TargetedRHIs=PCD3D_SM5 +-TargetedRHIs=GLSL_150 ++TargetedRHIs=GLSL_150 +MinimumOSVersion=MSOS_Vista +AudioDevice= + + diff --git a/OSVRUnreal/Content/Maps/basic.umap b/OSVRUnreal/Content/Maps/basic.umap index 294b14043819226e8fd8ed22d882eda954c700b5..c2abe942fdb73fb3eed711f07fd126aa034f1fcb 100644 GIT binary patch delta 25185 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 delta 87977 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 p4&{D5PgiUf4sGk~ TGraphicsDevice* GetGraphicsDevice() diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h index 0008a3d..7f1e926 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h @@ -247,6 +247,13 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// return true; } + virtual bool LazySetSrcTextureImpl(FTexture2DRHIParamRef srcTexture) override + { + // The Direct3D11 implementation can allocate the texture right away, + // so we don't need to defer setting this. + return true; + } + virtual void FinishRendering() override { check(IsInitialized()); diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h index 080d5de..0b3f8d0 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h @@ -38,6 +38,106 @@ #include "OpenGLDrvPrivate.h" #include "OpenGLResources.h" +//========================================================================== +// Toolkit object to handle our window creation needs. We pass it down to +// the RenderManager to override its window and context creation behavior, +// since we are using the Unreal window/context creation. + +class FUnrealOSVRRenderManagerOpenGLToolkit { +private: + OSVR_OpenGLToolkitFunctions toolkit; + + // eliminate force to bool performance warning. + static inline OSVR_CBool ConvertBool(bool bValue) + { + return bValue ? OSVR_TRUE : OSVR_FALSE; + } + + static inline bool ConvertBool(OSVR_CBool bValue) + { + return bValue == OSVR_TRUE ? true : false; + } + + static void createImpl(void* data) { + } + static void destroyImpl(void* data) { + delete ((FUnrealOSVRRenderManagerOpenGLToolkit*)data); + } + static OSVR_CBool addOpenGLContextImpl(void* data, const OSVR_OpenGLContextParams* p) { + return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->addOpenGLContext(p)); + } + static OSVR_CBool removeOpenGLContextsImpl(void* data) { + return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->removeOpenGLContexts()); + } + static OSVR_CBool makeCurrentImpl(void* data, size_t display) { + return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->makeCurrent(display)); + } + static OSVR_CBool swapBuffersImpl(void* data, size_t display) { + return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->swapBuffers(display)); + } + static OSVR_CBool setVerticalSyncImpl(void* data, OSVR_CBool verticalSync) { + return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->setVerticalSync(ConvertBool(verticalSync))); + } + static OSVR_CBool handleEventsImpl(void* data) { + return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->handleEvents()); + } + +public: + FUnrealOSVRRenderManagerOpenGLToolkit() { + memset(&toolkit, 0, sizeof(toolkit)); + toolkit.size = sizeof(toolkit); + toolkit.data = this; + + toolkit.create = createImpl; + toolkit.destroy = destroyImpl; + toolkit.addOpenGLContext = addOpenGLContextImpl; + toolkit.removeOpenGLContexts = removeOpenGLContextsImpl; + toolkit.makeCurrent = makeCurrentImpl; + toolkit.swapBuffers = swapBuffersImpl; + toolkit.setVerticalSync = setVerticalSyncImpl; + toolkit.handleEvents = handleEventsImpl; + } + + ~FUnrealOSVRRenderManagerOpenGLToolkit() { + } + + const OSVR_OpenGLToolkitFunctions* getToolkit() const { return &toolkit; } + + bool addOpenGLContext(const OSVR_OpenGLContextParams* p) { + // @todo change resolution and move the window here? + // We may need to just record the params and respond to them later, + // since this call has to be done on the game thread, but it's called on the + // window thread. + + //FSystemResolution::RequestResolutionChange(p->width, p->height, EWindowMode::Windowed); + return true; + } + + bool removeOpenGLContexts() { + return true; + } + + bool makeCurrent(size_t display) { + // we are always current, since unreal creates our context + // beforehand + return true; + } + + bool swapBuffers(size_t display) { + // unreal does this for us + return true; + } + + bool setVerticalSync(bool verticalSync) { + // @todo ??? + return true; + } + bool handleEvents() { + // @todo ??? + return true; + } +}; + class FOpenGLCustomPresent : public FOSVRCustomPresent { public: @@ -73,37 +173,12 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent virtual bool AllocateRenderTargetTexture(uint32 index, uint32 sizeX, uint32 sizeY, uint8 format, uint32 numMips, uint32 flags, uint32 targetableTextureFlags, FTexture2DRHIRef& outTargetableTexture, FTexture2DRHIRef& outShaderResourceTexture, uint32 numSamples = 1) override { - FScopeLock lock(&mOSVRMutex); - if (IsInitialized()) - { - // create the color buffer - if (RenderTargetTexture > 0) - { - glDeleteTextures(1, &RenderTargetTexture); - } - RenderTargetTexture = 0; - osvrRenderManagerCreateColorBufferOpenGL(sizeX, sizeY, GL_BGRA, &RenderTargetTexture); - - //SetRenderTargetTexture(D3DTexture); - - auto GLRHI = static_cast(GDynamicRHI); - GLenum target = numSamples > 1 ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; - GLenum attachment = GL_NONE; - bool bAllocatedStorage = false; - numMips = 1; - uint8* textureRange = nullptr; - - auto targetableTexture = new FOpenGLTexture2D( - GLRHI, RenderTargetTexture, target, attachment, sizeX, sizeY, 0, numMips, numSamples, 1, EPixelFormat(format), false, - bAllocatedStorage, flags, textureRange, FClearValueBinding::Black); - - outTargetableTexture = targetableTexture->GetTexture2D(); - outShaderResourceTexture = targetableTexture->GetTexture2D(); - mRenderTexture = targetableTexture; - bRenderBuffersNeedToUpdate = true; - UpdateRenderBuffers(); - return true; - } + // For OpenGL, we allow RenderManager to create our render textures for us and then + // we pass that texture id in on the first RenderTexture_RenderThread call. + + // signal UpdateRenderBuffers to register the new render buffer when we + // next get it passed in via LazySetSrcTexture + bRenderBuffersNeedToUpdate = true; return false; } @@ -137,14 +212,14 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent rc = osvrRenderManagerGetRenderInfoCollection(mRenderManager, mRenderParams, &renderInfoCollection); check(rc == OSVR_RETURN_SUCCESS); - OSVR_RenderInfoCount numRenderInfo; + OSVR_RenderInfoCount numRenderInfo = { 0 }; rc = osvrRenderManagerGetNumRenderInfoInCollection(renderInfoCollection, &numRenderInfo); check(rc == OSVR_RETURN_SUCCESS); mRenderInfos.Empty(); for (size_t i = 0; i < numRenderInfo; i++) { - OSVR_RenderInfoOpenGL renderInfo; + OSVR_RenderInfoOpenGL renderInfo = { 0 }; rc = osvrRenderManagerGetRenderInfoFromCollectionOpenGL(renderInfoCollection, i, &renderInfo); check(rc == OSVR_RETURN_SUCCESS); @@ -225,24 +300,40 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent return true; } + virtual bool LazySetSrcTextureImpl(FTexture2DRHIParamRef srcTexture) override + { + auto textureOpenGL = static_cast(&(*srcTexture)); + RenderTargetTexture = textureOpenGL->Resource; + mRenderTexture = srcTexture; // @todo do we need mRenderTexture? + UpdateRenderBuffers(); + return true; + } + virtual void FinishRendering() override { check(IsInitialized()); - //UpdateRenderBuffers(); - //// all of the render manager samples keep the flipY at the default false, - //// for both OpenGL and DirectX. Is this even needed anymore? - //OSVR_ReturnCode rc; - //OSVR_RenderManagerPresentState presentState; - //rc = osvrRenderManagerStartPresentRenderBuffers(&presentState); - //check(rc == OSVR_RETURN_SUCCESS); - //check(mRenderBuffers.Num() == mRenderInfos.Num() && mRenderBuffers.Num() == mViewportDescriptions.Num()); - //for (size_t i = 0; i < mRenderBuffers.Num(); i++) - //{ - // rc = osvrRenderManagerPresentRenderBufferOpenGL(presentState, mRenderBuffers[i], mRenderInfos[i], mViewportDescriptions[i]); - // check(rc == OSVR_RETURN_SUCCESS); - //} - //rc = osvrRenderManagerFinishPresentRenderBuffers(mRenderManager, presentState, mRenderParams, ShouldFlipY() ? OSVR_TRUE : OSVR_FALSE); - //check(rc == OSVR_RETURN_SUCCESS); + UpdateRenderBuffers(); + + // all of the render manager samples keep the flipY at the default false, + // for both OpenGL and DirectX. Is this even needed anymore? + OSVR_ReturnCode rc; + OSVR_RenderManagerPresentState presentState; + rc = osvrRenderManagerStartPresentRenderBuffers(&presentState); + check(rc == OSVR_RETURN_SUCCESS); + check(mRenderBuffers.Num() == mRenderInfos.Num() && mRenderBuffers.Num() == mViewportDescriptions.Num()); + for (size_t i = 0; i < mRenderBuffers.Num(); i++) + { + auto renderBuffer = mRenderBuffers[i]; + auto renderInfo = mRenderInfos[i]; + auto viewportDescription = mViewportDescriptions[i]; + rc = osvrRenderManagerPresentRenderBufferOpenGL(presentState, renderBuffer, renderInfo, viewportDescription); + check(rc == OSVR_RETURN_SUCCESS); + } + + // @todo: figure out why this call is failing (release DLLs make debugging + // difficult here - can't determine which failure branch is being taken). + rc = osvrRenderManagerFinishPresentRenderBuffers(mRenderManager, presentState, mRenderParams, ShouldFlipY() ? OSVR_TRUE : OSVR_FALSE); + check(rc == OSVR_RETURN_SUCCESS); } virtual void UpdateRenderBuffers() override @@ -306,7 +397,10 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent virtual OSVR_GraphicsLibraryOpenGL CreateGraphicsLibrary() { OSVR_GraphicsLibraryOpenGL ret = { 0 }; - // @todo: anything needed here? + // @todo figure out why this toolkit doesn't get passed back when + // we get the render info from the collection API. + auto toolkit = new FUnrealOSVRRenderManagerOpenGLToolkit(); + ret.toolkit = toolkit->getToolkit(); return ret; } }; diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp index b72504a..4f44104 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp @@ -33,6 +33,18 @@ void FOSVRHMD::DrawDistortionMesh_RenderThread(FRenderingCompositePassContext& C void FOSVRHMD::RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, FTexture2DRHIParamRef backBuffer, FTexture2DRHIParamRef srcTexture) const { check(IsInRenderingThread()); + if (mCustomPresent && mCustomPresent->IsInitialized()) + { + // we have to call this in 3 places because Unreal makes calls in + // a different order in the editor vs standalone vs packaged. + mCustomPresent->LazyOpenDisplay(); + + // the current custom present implementation may be allowing Unreal to + // allocate its render textures. If so, this is the first time we get + // access to the texture that was created. + mCustomPresent->LazySetSrcTexture(srcTexture); + } + const uint32 viewportWidth = backBuffer->GetSizeX(); const uint32 viewportHeight = backBuffer->GetSizeY(); From 6315a3b836e364aa83b7bc35f2cf60ff7f89da9a Mon Sep 17 00:00:00 2001 From: JeroMiya Date: Fri, 22 Jul 2016 14:04:43 -0400 Subject: [PATCH 06/10] Implemented makeCurrent in the OpenGL toolkit. --- .../Source/OSVR/Private/OSVRCustomPresent.h | 22 +++ .../OSVR/Private/OSVRCustomPresentOpenGL.h | 145 +++++++++++++++--- .../OSVR/Source/OSVR/Private/OSVRRender.cpp | 15 +- 3 files changed, 154 insertions(+), 28 deletions(-) diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h index 8aae789..3747fbb 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h @@ -76,10 +76,32 @@ class FOSVRCustomPresent : public FRHICustomPresent check(IsInRenderingThread()); FScopeLock lock(&mOSVRMutex); InitializeImpl(); + if (!bDisplayOpen) + { + bDisplayOpen = LazyOpenDisplayImpl(); + check(bDisplayOpen); + } + + // @todo This is giving us a black screen. FinishRendering(); return true; } + virtual void RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, FTexture2DRHIParamRef backBuffer, FTexture2DRHIParamRef srcTexture) + { + check(IsInRenderingThread()); + FScopeLock lock(&mOSVRMutex); + InitializeImpl(); + if (!bDisplayOpen) + { + bDisplayOpen = LazyOpenDisplayImpl(); + check(bDisplayOpen); + } + + // @todo This is giving us a black screen. + FinishRendering(); + } + // Initializes RenderManager but does not open the displays // Can be called from the render thread or game thread. // @todo: this call should be lazy, then IsInitialized can be removed. diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h index 0b3f8d0..d9a248f 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h @@ -38,15 +38,44 @@ #include "OpenGLDrvPrivate.h" #include "OpenGLResources.h" +class FUnrealBackBufferContainer +{ +private: + FRHICommandListImmediate* mRHICmdList; + FTexture2DRHIParamRef mBackBuffer; + +public: + FRHICommandListImmediate* getRHICmdList() + { + return mRHICmdList; + } + + void setRHICmdList(FRHICommandListImmediate* rhiCmdList) + { + mRHICmdList = rhiCmdList; + } + + FTexture2DRHIParamRef getBackBuffer() + { + return mBackBuffer; + } + + void setBackBuffer(FTexture2DRHIParamRef backBuffer) + { + mBackBuffer = backBuffer; + } +}; + + //========================================================================== // Toolkit object to handle our window creation needs. We pass it down to // the RenderManager to override its window and context creation behavior, // since we are using the Unreal window/context creation. - class FUnrealOSVRRenderManagerOpenGLToolkit { private: OSVR_OpenGLToolkitFunctions toolkit; - + FUnrealBackBufferContainer* mBackBufferContainer; + // eliminate force to bool performance warning. static inline OSVR_CBool ConvertBool(bool bValue) { @@ -58,32 +87,49 @@ class FUnrealOSVRRenderManagerOpenGLToolkit { return bValue == OSVR_TRUE ? true : false; } - static void createImpl(void* data) { + static void createImpl(void* data) + { } - static void destroyImpl(void* data) { + + static void destroyImpl(void* data) + { delete ((FUnrealOSVRRenderManagerOpenGLToolkit*)data); } - static OSVR_CBool addOpenGLContextImpl(void* data, const OSVR_OpenGLContextParams* p) { + + static OSVR_CBool addOpenGLContextImpl(void* data, const OSVR_OpenGLContextParams* p) + { return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->addOpenGLContext(p)); } - static OSVR_CBool removeOpenGLContextsImpl(void* data) { + + static OSVR_CBool removeOpenGLContextsImpl(void* data) + { return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->removeOpenGLContexts()); } - static OSVR_CBool makeCurrentImpl(void* data, size_t display) { + + static OSVR_CBool makeCurrentImpl(void* data, size_t display) + { return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->makeCurrent(display)); } - static OSVR_CBool swapBuffersImpl(void* data, size_t display) { + + static OSVR_CBool swapBuffersImpl(void* data, size_t display) + { return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->swapBuffers(display)); } - static OSVR_CBool setVerticalSyncImpl(void* data, OSVR_CBool verticalSync) { + + static OSVR_CBool setVerticalSyncImpl(void* data, OSVR_CBool verticalSync) + { return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->setVerticalSync(ConvertBool(verticalSync))); } - static OSVR_CBool handleEventsImpl(void* data) { + + static OSVR_CBool handleEventsImpl(void* data) + { return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->handleEvents()); } public: - FUnrealOSVRRenderManagerOpenGLToolkit() { + FUnrealOSVRRenderManagerOpenGLToolkit(FUnrealBackBufferContainer* backBufferContainer) + : mBackBufferContainer(backBufferContainer) + { memset(&toolkit, 0, sizeof(toolkit)); toolkit.size = sizeof(toolkit); toolkit.data = this; @@ -98,12 +144,17 @@ class FUnrealOSVRRenderManagerOpenGLToolkit { toolkit.handleEvents = handleEventsImpl; } - ~FUnrealOSVRRenderManagerOpenGLToolkit() { + ~FUnrealOSVRRenderManagerOpenGLToolkit() + { } - const OSVR_OpenGLToolkitFunctions* getToolkit() const { return &toolkit; } + const OSVR_OpenGLToolkitFunctions* getToolkit() const + { + return &toolkit; + } - bool addOpenGLContext(const OSVR_OpenGLContextParams* p) { + bool addOpenGLContext(const OSVR_OpenGLContextParams* p) + { // @todo change resolution and move the window here? // We may need to just record the params and respond to them later, // since this call has to be done on the game thread, but it's called on the @@ -113,26 +164,41 @@ class FUnrealOSVRRenderManagerOpenGLToolkit { return true; } - bool removeOpenGLContexts() { + bool removeOpenGLContexts() + { return true; } - bool makeCurrent(size_t display) { + bool makeCurrent(size_t display) + { // we are always current, since unreal creates our context // beforehand + auto backBuffer = mBackBufferContainer->getBackBuffer(); + auto& rhiCmdList = *mBackBufferContainer->getRHICmdList(); + + SetRenderTarget(*(mBackBufferContainer->getRHICmdList()), mBackBufferContainer->getBackBuffer(), FTextureRHIRef()); + const uint32 viewportWidth = backBuffer->GetSizeX(); + const uint32 viewportHeight = backBuffer->GetSizeY(); + + SetRenderTarget(rhiCmdList, backBuffer, FTextureRHIRef()); + rhiCmdList.SetViewport(0, 0, 0, viewportWidth, viewportHeight, 1.0f); return true; } - bool swapBuffers(size_t display) { + bool swapBuffers(size_t display) + { // unreal does this for us return true; } - bool setVerticalSync(bool verticalSync) { + bool setVerticalSync(bool verticalSync) + { // @todo ??? return true; } - bool handleEvents() { + + bool handleEvents() + { // @todo ??? return true; } @@ -152,6 +218,11 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent { glDeleteTextures(1, &RenderTargetTexture); } + + if (mBackBufferContainer) + { + delete mBackBufferContainer; + } } protected: @@ -160,6 +231,7 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent TArray mRenderInfos; OSVR_RenderManagerOpenGL mRenderManagerOpenGL = nullptr; GLuint RenderTargetTexture = 0; + FUnrealBackBufferContainer* mBackBufferContainer = nullptr; virtual FString GetGraphicsLibraryName() override { @@ -309,6 +381,30 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent return true; } + virtual bool Present(int32 &inOutSyncInterval) override + { + // turn this into a no-op for now, we do it in RenderTexture_RenderThread + return true; + } + + virtual void RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, FTexture2DRHIParamRef backBuffer, FTexture2DRHIParamRef srcTexture) override + { + check(IsInRenderingThread()); + FScopeLock lock(&mOSVRMutex); + auto bb = getBackBufferContainer(); + bb->setRHICmdList(&rhiCmdList); + bb->setBackBuffer(backBuffer); + InitializeImpl(); + if (!bDisplayOpen) + { + bDisplayOpen = LazyOpenDisplayImpl(); + check(bDisplayOpen); + } + + // @todo This is giving us a black screen. + FinishRendering(); + } + virtual void FinishRendering() override { check(IsInitialized()); @@ -394,12 +490,21 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent } } + virtual FUnrealBackBufferContainer* getBackBufferContainer() + { + if (!mBackBufferContainer) + { + mBackBufferContainer = new FUnrealBackBufferContainer(); + } + return mBackBufferContainer; + } + virtual OSVR_GraphicsLibraryOpenGL CreateGraphicsLibrary() { OSVR_GraphicsLibraryOpenGL ret = { 0 }; // @todo figure out why this toolkit doesn't get passed back when // we get the render info from the collection API. - auto toolkit = new FUnrealOSVRRenderManagerOpenGLToolkit(); + auto toolkit = new FUnrealOSVRRenderManagerOpenGLToolkit(getBackBufferContainer()); ret.toolkit = toolkit->getToolkit(); return ret; } diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp index 4f44104..75e06a4 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp @@ -35,10 +35,6 @@ void FOSVRHMD::RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, check(IsInRenderingThread()); if (mCustomPresent && mCustomPresent->IsInitialized()) { - // we have to call this in 3 places because Unreal makes calls in - // a different order in the editor vs standalone vs packaged. - mCustomPresent->LazyOpenDisplay(); - // the current custom present implementation may be allowing Unreal to // allocate its render textures. If so, this is the first time we get // access to the texture that was created. @@ -65,6 +61,8 @@ void FOSVRHMD::RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, SetGlobalBoundShaderState(rhiCmdList, featureLevel, boundShaderState, RendererModule->GetFilterVertexDeclaration().VertexDeclarationRHI, *vertexShader, *pixelShader); pixelShader->SetParameters(rhiCmdList, TStaticSamplerState::GetRHI(), srcTexture); + + // @todo: do we need to ask mCustomPresent whether we should draw the preview or not? RendererModule->DrawRectangle( rhiCmdList, 0, 0, // X, Y @@ -75,6 +73,11 @@ void FOSVRHMD::RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, FIntPoint(1, 1), // TextureSize *vertexShader, EDRF_Default); + + if (mCustomPresent) + { + mCustomPresent->RenderTexture_RenderThread(rhiCmdList, backBuffer, srcTexture); + } } void FOSVRHMD::GetEyeRenderParams_RenderThread(const struct FRenderingCompositePassContext& Context, FVector2D& EyeToSrcUVScaleValue, FVector2D& EyeToSrcUVOffsetValue) const @@ -112,8 +115,6 @@ void FOSVRHMD::PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmd { mCustomPresent->Initialize(); } - - mCustomPresent->LazyOpenDisplay(); } FQuat lastHmdOrientation, hmdOrientation; @@ -213,8 +214,6 @@ bool FOSVRHMD::AllocateRenderTargetTexture(uint32 index, uint32 sizeX, uint32 si check(IsInRenderingThread()); if (mCustomPresent && mCustomPresent->IsInitialized()) { - bool displayOpen = mCustomPresent->LazyOpenDisplay(); - check(displayOpen); return mCustomPresent->AllocateRenderTargetTexture(index, sizeX, sizeY, format, numMips, flags, targetableTextureFlags, outTargetableTexture, outShaderResourceTexture, numSamples); } return false; From 1dfdadccb224db61cd0e4a6040dc10faf2c82620 Mon Sep 17 00:00:00 2001 From: JeroMiya Date: Wed, 27 Jul 2016 16:49:40 -0400 Subject: [PATCH 07/10] Implemented OpenGL toolkit getDisplayFrameBuffer callback, and related refactoring. --- .../OSVR/Private/OSVRCustomPresentOpenGL.h | 80 +++++++------------ .../OSVR/Source/OSVR/Private/OSVRRender.cpp | 12 +-- 2 files changed, 33 insertions(+), 59 deletions(-) diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h index d9a248f..1d59168 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h @@ -38,32 +38,9 @@ #include "OpenGLDrvPrivate.h" #include "OpenGLResources.h" -class FUnrealBackBufferContainer +typedef struct FUnrealBackBufferContainer { -private: - FRHICommandListImmediate* mRHICmdList; - FTexture2DRHIParamRef mBackBuffer; - -public: - FRHICommandListImmediate* getRHICmdList() - { - return mRHICmdList; - } - - void setRHICmdList(FRHICommandListImmediate* rhiCmdList) - { - mRHICmdList = rhiCmdList; - } - - FTexture2DRHIParamRef getBackBuffer() - { - return mBackBuffer; - } - - void setBackBuffer(FTexture2DRHIParamRef backBuffer) - { - mBackBuffer = backBuffer; - } + GLint displayFrameBuffer; }; @@ -126,6 +103,11 @@ class FUnrealOSVRRenderManagerOpenGLToolkit { return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->handleEvents()); } + static OSVR_CBool getDisplayFrameBufferImpl(void* data, size_t display, GLint* displayFrameBufferOut) + { + return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->getDisplayFrameBuffer(display, displayFrameBufferOut)); + } + public: FUnrealOSVRRenderManagerOpenGLToolkit(FUnrealBackBufferContainer* backBufferContainer) : mBackBufferContainer(backBufferContainer) @@ -142,6 +124,7 @@ class FUnrealOSVRRenderManagerOpenGLToolkit { toolkit.swapBuffers = swapBuffersImpl; toolkit.setVerticalSync = setVerticalSyncImpl; toolkit.handleEvents = handleEventsImpl; + toolkit.getDisplayFrameBuffer = getDisplayFrameBufferImpl; } ~FUnrealOSVRRenderManagerOpenGLToolkit() @@ -173,15 +156,6 @@ class FUnrealOSVRRenderManagerOpenGLToolkit { { // we are always current, since unreal creates our context // beforehand - auto backBuffer = mBackBufferContainer->getBackBuffer(); - auto& rhiCmdList = *mBackBufferContainer->getRHICmdList(); - - SetRenderTarget(*(mBackBufferContainer->getRHICmdList()), mBackBufferContainer->getBackBuffer(), FTextureRHIRef()); - const uint32 viewportWidth = backBuffer->GetSizeX(); - const uint32 viewportHeight = backBuffer->GetSizeY(); - - SetRenderTarget(rhiCmdList, backBuffer, FTextureRHIRef()); - rhiCmdList.SetViewport(0, 0, 0, viewportWidth, viewportHeight, 1.0f); return true; } @@ -202,6 +176,12 @@ class FUnrealOSVRRenderManagerOpenGLToolkit { // @todo ??? return true; } + + bool getDisplayFrameBuffer(size_t display, GLint* displayFrameBufferOut) + { + (*displayFrameBufferOut) = mBackBufferContainer->displayFrameBuffer; + return true; + } }; class FOpenGLCustomPresent : public FOSVRCustomPresent @@ -381,33 +361,26 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent return true; } - virtual bool Present(int32 &inOutSyncInterval) override - { - // turn this into a no-op for now, we do it in RenderTexture_RenderThread - return true; - } - virtual void RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, FTexture2DRHIParamRef backBuffer, FTexture2DRHIParamRef srcTexture) override { check(IsInRenderingThread()); - FScopeLock lock(&mOSVRMutex); - auto bb = getBackBufferContainer(); - bb->setRHICmdList(&rhiCmdList); - bb->setBackBuffer(backBuffer); - InitializeImpl(); - if (!bDisplayOpen) - { - bDisplayOpen = LazyOpenDisplayImpl(); - check(bDisplayOpen); - } - - // @todo This is giving us a black screen. - FinishRendering(); + LazySetSrcTexture(srcTexture); } virtual void FinishRendering() override { check(IsInitialized()); + + // @todo: this needs to be more robust. + // Can we find this by inspection of the view somehow? + // After the first call, the framebuffer ends up set to + // 0 before entering this function, but 0 is not the + // framebuffer for the window. + if (mBackBufferContainer->displayFrameBuffer < 0) + { + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mBackBufferContainer->displayFrameBuffer); + } + UpdateRenderBuffers(); // all of the render manager samples keep the flipY at the default false, @@ -495,6 +468,7 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent if (!mBackBufferContainer) { mBackBufferContainer = new FUnrealBackBufferContainer(); + mBackBufferContainer->displayFrameBuffer = -1; } return mBackBufferContainer; } diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp index 75e06a4..45de8e6 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRRender.cpp @@ -38,9 +38,14 @@ void FOSVRHMD::RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, // the current custom present implementation may be allowing Unreal to // allocate its render textures. If so, this is the first time we get // access to the texture that was created. - mCustomPresent->LazySetSrcTexture(srcTexture); + mCustomPresent->RenderTexture_RenderThread(rhiCmdList, backBuffer, srcTexture); } + // @todo should we add an FCustomPresent function + // to ask if we should draw the preview? The OpenGL + // custom present can draw distortion directly into + // the preview window for instance, making this + // redundant. const uint32 viewportWidth = backBuffer->GetSizeX(); const uint32 viewportHeight = backBuffer->GetSizeY(); @@ -73,11 +78,6 @@ void FOSVRHMD::RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, FIntPoint(1, 1), // TextureSize *vertexShader, EDRF_Default); - - if (mCustomPresent) - { - mCustomPresent->RenderTexture_RenderThread(rhiCmdList, backBuffer, srcTexture); - } } void FOSVRHMD::GetEyeRenderParams_RenderThread(const struct FRenderingCompositePassContext& Context, FVector2D& EyeToSrcUVScaleValue, FVector2D& EyeToSrcUVOffsetValue) const From 89c4ddd620b9ea202d80910de3312446dde8a03d Mon Sep 17 00:00:00 2001 From: JeroMiya Date: Thu, 28 Jul 2016 12:23:52 -0400 Subject: [PATCH 08/10] Work in progress implementation of getDisplaySizeOVerride for the OpenGL toolkit for RenderManager. Hard coded display size for now, will need to pass in the display size from FOSVRHMD somehow. --- .../OSVR/Private/OSVRCustomPresentOpenGL.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h index 1d59168..a8b4f63 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h @@ -41,6 +41,8 @@ typedef struct FUnrealBackBufferContainer { GLint displayFrameBuffer; + int width; + int height; }; @@ -108,6 +110,11 @@ class FUnrealOSVRRenderManagerOpenGLToolkit { return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->getDisplayFrameBuffer(display, displayFrameBufferOut)); } + static OSVR_CBool getDisplaySizeOverrideImpl(void* data, size_t display, int* width, int* height) + { + return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->getDisplaySizeOverride(display, width, height)); + } + public: FUnrealOSVRRenderManagerOpenGLToolkit(FUnrealBackBufferContainer* backBufferContainer) : mBackBufferContainer(backBufferContainer) @@ -125,6 +132,7 @@ class FUnrealOSVRRenderManagerOpenGLToolkit { toolkit.setVerticalSync = setVerticalSyncImpl; toolkit.handleEvents = handleEventsImpl; toolkit.getDisplayFrameBuffer = getDisplayFrameBufferImpl; + toolkit.getDisplaySizeOverride = getDisplaySizeOverrideImpl; } ~FUnrealOSVRRenderManagerOpenGLToolkit() @@ -182,6 +190,13 @@ class FUnrealOSVRRenderManagerOpenGLToolkit { (*displayFrameBufferOut) = mBackBufferContainer->displayFrameBuffer; return true; } + + bool getDisplaySizeOverride(size_t display, int* width, int* height) + { + (*width) = mBackBufferContainer->width; + (*height) = mBackBufferContainer->height; + return true; + } }; class FOpenGLCustomPresent : public FOSVRCustomPresent @@ -469,6 +484,8 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent { mBackBufferContainer = new FUnrealBackBufferContainer(); mBackBufferContainer->displayFrameBuffer = -1; + mBackBufferContainer->width = 1280; + mBackBufferContainer->height = 720; } return mBackBufferContainer; } From 386eae37697237f5de5fc6db9b47cad160644e6b Mon Sep 17 00:00:00 2001 From: JeroMiya Date: Mon, 1 Aug 2016 17:01:20 -0400 Subject: [PATCH 09/10] Removed the RenderTexture_RenderThread implementation in FOSVRCustomPresent. Now it is a blank function. Fixes black triangle in D3D11 preview window. --- .../OSVR/Source/OSVR/Private/OSVRCustomPresent.h | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h index 3747fbb..c10504f 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h @@ -87,19 +87,11 @@ class FOSVRCustomPresent : public FRHICustomPresent return true; } - virtual void RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, FTexture2DRHIParamRef backBuffer, FTexture2DRHIParamRef srcTexture) + virtual void RenderTexture_RenderThread( + FRHICommandListImmediate& rhiCmdList, + FTexture2DRHIParamRef backBuffer, + FTexture2DRHIParamRef srcTexture) { - check(IsInRenderingThread()); - FScopeLock lock(&mOSVRMutex); - InitializeImpl(); - if (!bDisplayOpen) - { - bDisplayOpen = LazyOpenDisplayImpl(); - check(bDisplayOpen); - } - - // @todo This is giving us a black screen. - FinishRendering(); } // Initializes RenderManager but does not open the displays From b58a13a513501cc862101e552a12118b275ad58a Mon Sep 17 00:00:00 2001 From: JeroMiya Date: Tue, 23 Aug 2016 12:01:49 -0400 Subject: [PATCH 10/10] Stashing changes --- ImportFromSDK.cmd | 84 ++-- OSVRUnreal/Config/DefaultEngine.ini | 8 - OSVRUnreal/Content/Maps/basic.umap | Bin 747335 -> 809949 bytes OSVRUnreal/Plugins/OSVR/OSVR.uplugin | 4 +- .../Plugins/OSVR/Source/OSVR/OSVR.Build.cs | 15 +- .../Source/OSVR/Private/OSVRCustomPresent.cpp | 1 - .../Source/OSVR/Private/OSVRCustomPresent.h | 96 +--- .../OSVR/Private/OSVRCustomPresentD3D11.h | 111 ++-- .../OSVR/Private/OSVRCustomPresentOpenGL.h | 476 +----------------- .../Source/OSVR/Private/OSVREntryPoint.cpp | 21 +- 10 files changed, 157 insertions(+), 659 deletions(-) diff --git a/ImportFromSDK.cmd b/ImportFromSDK.cmd index fccde29..b2794a9 100644 --- a/ImportFromSDK.cmd +++ b/ImportFromSDK.cmd @@ -1,6 +1,7 @@ @echo off set PRJ_ROOT=%~dp0 +set XCOPY_ARGS=/f /y set PLUGIN_ROOT=%~dp0OSVRUnreal\Plugins\OSVR IF NOT EXIST "%PLUGIN_ROOT%" goto ERROR_WRONG_PROJ_DIR @@ -11,39 +12,33 @@ IF NOT EXIST "%DEST_ROOT%" goto ERROR_WRONG_PROJ_DIR IF %1.==. ( set /p osvr32bit=Type OSVR 32bit SDK root dir: ) ELSE ( - set osvr32bit=%~1 + set osvr32bit="%~1" ) IF %2.==. ( set /p osvr64bit=Type OSVR 64bit SDK root dir: ) ELSE ( - set osvr64bit=%~2 + set osvr64bit="%~2" ) IF %3.==. ( - set /p rm64bit=Type DirectRender 64bit SDK root dir: -) ELSE ( - set rm64bit=%~3 -) - -IF %4.==. ( set /p osvrAndroid=Type OSVR Android SDK root dir: ) ELSE ( - set osvrAndroid=%~4 + set osvrAndroid="%~3" ) -set rm32bit=%rm64bit% - rem Get rid of the old RMDIR /S /Q "%DEST_ROOT%\include" > NUL RMDIR /S /Q "%DEST_ROOT%\lib" > NUL del "%DEST_ROOT%\*.txt" > NUL -call :copy_arch_indep %osvr32bit% %rm32bit% %DEST_ROOT% +call :copy_arch_indep %osvr32bit% %DEST_ROOT% -call :copy_arch %osvr32bit% %rm32bit% %PLUGIN_ROOT% %DEST_ROOT% 32 -call :copy_arch %osvr64bit% %rm64bit% %PLUGIN_ROOT% %DEST_ROOT% 64 +call :copy_arch %osvr32bit% %PLUGIN_ROOT% %DEST_ROOT% 32 +call :copy_arch %osvr64bit% %PLUGIN_ROOT% %DEST_ROOT% 64 call :copy_android %osvrAndroid% %PLUGIN_ROOT% %DEST_ROOT% +echo, +echo Done echo Note: The 32-bit target is not yet supported. Please use the 64-bit target only for now. goto :eof @@ -51,19 +46,24 @@ goto :eof :copy_arch_indep rem Architecture-independent files +echo, +echo Architecture-independent files +echo, setlocal set SRC=%1 -set SRC_RM=%2 -set DEST_ROOT=%3 -xcopy %SRC%\include\osvr\ClientKit "%DEST_ROOT%\include\osvr\ClientKit" /S /I /y -xcopy %SRC%\include\osvr\Util "%DEST_ROOT%\include\osvr\Util" /S /I /y -xcopy %SRC%\include\osvr\Client "%DEST_ROOT%\include\osvr\Client" /S /I /y -xcopy %SRC%\include\osvr\Common "%DEST_ROOT%\include\osvr\Common" /S /I /y -xcopy %SRC_RM%\include\osvr\RenderKit "%DEST_ROOT%\include\osvr\RenderKit" /S /I /y +set DEST_ROOT=%2 +xcopy %SRC%\include\osvr\ClientKit "%DEST_ROOT%\include\osvr\ClientKit" /S /I %XCOPY_ARGS% +xcopy %SRC%\include\osvr\Util "%DEST_ROOT%\include\osvr\Util" /S /I %XCOPY_ARGS% +xcopy %SRC%\include\osvr\Client "%DEST_ROOT%\include\osvr\Client" /S /I %XCOPY_ARGS% +xcopy %SRC%\include\osvr\Common "%DEST_ROOT%\include\osvr\Common" /S /I %XCOPY_ARGS% +xcopy %SRC%\include\osvr\RenderKit "%DEST_ROOT%\include\osvr\RenderKit" /S /I %XCOPY_ARGS% endlocal goto :eof :copy_android +echo, +echo Android files +echo, set SRC=%1 set PLUGIN_ROOT=%2 @@ -71,8 +71,8 @@ set DEST_ROOT=%3 set SRC_LIB=%SRC%\NDK\osvr\builds\armeabi-v7a\lib for %%F in (%SRC_LIB%\libcrystax.so,%SRC_LIB%\libfunctionality.so,%SRC_LIB%\libgnustl_shared.so,%SRC_LIB%\libjsoncpp.so,%SRC_LIB%\libosvrAnalysisPluginKit.so,%SRC_LIB%\libosvrClient.so,%SRC_LIB%\libosvrClientKit.so,%SRC_LIB%\libosvrCommon.so,%SRC_LIB%\libosvrConnection.so,%SRC_LIB%\libosvrJointClientKit.so,%SRC_LIB%\libosvrPluginHost.so,%SRC_LIB%\libosvrServer.so,%SRC_LIB%\libosvrUtil.so,%SRC_LIB%\libosvrVRPNServer.so) do ( - echo xcopy %%F "%DEST_ROOT%\bin\Android\armeabi-v7a\" /Y - xcopy %%F "%DEST_ROOT%\bin\Android\armeabi-v7a\" /Y + rem echo xcopy %%F "%DEST_ROOT%\bin\Android\armeabi-v7a\" %XCOPY_ARGS% + xcopy %%F "%DEST_ROOT%\bin\Android\armeabi-v7a\" %XCOPY_ARGS% ) endlocal @@ -82,26 +82,31 @@ goto :eof rem Architecture-dependent files setlocal set SRC=%1 -set SRC_RM=%2 -set PLUGIN_ROOT=%3 -set DEST_ROOT=%4 -set BITS=%5 +set PLUGIN_ROOT=%2 +set DEST_ROOT=%3 +set BITS=%4 +echo, +echo Architecture-dependent files %BITS% +echo, -copy "%SRC%\osvr-ver.txt" "%DEST_ROOT%\Win%BITS%osvr-ver.txt" /y +echo.> "%DEST_ROOT%\Win%BITS%osvr-ver.txt" +rem echo xcopy %SRC%\bin\osvr-ver.txt "%DEST_ROOT%\Win%BITS%osvr-ver.txt" /i %XCOPY_ARGS% +xcopy %SRC%\bin\osvr-ver.txt "%DEST_ROOT%\Win%BITS%osvr-ver.txt" %XCOPY_ARGS% rem One copy to the bin directory, for use in deployment. -call :copy_dll %SRC% %SRC_RM% %DEST_ROOT%\bin %BITS% +call :copy_dll %SRC% %DEST_ROOT%\bin %BITS% rem One copy to the plugin Binaries directory, for editor support. -call :copy_dll %SRC% %SRC_RM% %PLUGIN_ROOT%\Binaries %BITS% +call :copy_dll %SRC% %PLUGIN_ROOT%\Binaries %BITS% rem libs for %%F in (%SRC%\lib\osvrClientKit.lib) do ( - xcopy %%F "%DEST_ROOT%\lib\Win%BITS%\" /Y + xcopy %%F "%DEST_ROOT%\lib\Win%BITS%\" %XCOPY_ARGS% ) -for %%F in (%SRC_RM%\lib\osvrRenderManager.lib) do ( - xcopy %%F "%DEST_ROOT%\lib\Win%BITS%\" /Y +for %%F in (%SRC%\lib\osvrRenderManager.lib) do ( + rem echo xcopy %%F "%DEST_ROOT%\lib\Win%BITS%\" %XCOPY_ARGS% + xcopy %%F "%DEST_ROOT%\lib\Win%BITS%\" %XCOPY_ARGS% ) endlocal @@ -111,15 +116,14 @@ goto :eof rem Copy DLL files setlocal set SRC=%1 -set SRC_RM=%2 -set DEST=%3 -set BITS=%4 +set DEST=%2 +set BITS=%3 for %%F in (%SRC%\bin\osvrClientKit.dll,%SRC%\bin\osvrClient.dll,%SRC%\bin\osvrUtil.dll,%SRC%\bin\osvrCommon.dll) do ( - xcopy %%F "%DEST%\Win%BITS%\" /Y + xcopy %%F "%DEST%\Win%BITS%\" %XCOPY_ARGS% ) -for %%F in (%SRC_RM%\osvrRenderManager.dll,%SRC_RM%\d3dcompiler_47.dll,%SRC_RM%\glew32.dll,%SRC_RM%\SDL2.dll) do ( - echo xcopy %%F "%DEST%\Win%BITS%\" /Y - xcopy %%F "%DEST%\Win%BITS%\" /Y +for %%F in (%SRC%\bin\osvrRenderManager.dll,%SRC%\bin\d3dcompiler_47.dll,%SRC%\bin\glew32.dll,%SRC%\bin\SDL2.dll) do ( + rem echo xcopy %%F "%DEST%\Win%BITS%\" %XCOPY_ARGS% + xcopy %%F "%DEST%\Win%BITS%\" %XCOPY_ARGS% ) endlocal goto :eof diff --git a/OSVRUnreal/Config/DefaultEngine.ini b/OSVRUnreal/Config/DefaultEngine.ini index be84d24..019e74b 100644 --- a/OSVRUnreal/Config/DefaultEngine.ini +++ b/OSVRUnreal/Config/DefaultEngine.ini @@ -71,11 +71,3 @@ UIScaleRule=ShortestSide CustomScalingRuleClass=None UIScaleCurve=(EditorCurveData=(PreInfinityExtrap=RCCE_Constant,PostInfinityExtrap=RCCE_Constant,Keys=((Time=480.000000,Value=0.444000),(Time=720.000000,Value=0.666000),(Time=1080.000000,Value=1.000000),(Time=8640.000000,Value=8.000000)),DefaultValue=340282346638528859811704183484516925440.000000),ExternalCurve=None) -[/Script/WindowsTargetPlatform.WindowsTargetSettings] --TargetedRHIs=PCD3D_SM5 --TargetedRHIs=GLSL_150 -+TargetedRHIs=GLSL_150 -MinimumOSVersion=MSOS_Vista -AudioDevice= - - diff --git a/OSVRUnreal/Content/Maps/basic.umap b/OSVRUnreal/Content/Maps/basic.umap index c2abe942fdb73fb3eed711f07fd126aa034f1fcb..294b14043819226e8fd8ed22d882eda954c700b5 100644 GIT binary patch delta 87977 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 diff --git a/OSVRUnreal/Plugins/OSVR/OSVR.uplugin b/OSVRUnreal/Plugins/OSVR/OSVR.uplugin index b7ede58..4082511 100644 --- a/OSVRUnreal/Plugins/OSVR/OSVR.uplugin +++ b/OSVRUnreal/Plugins/OSVR/OSVR.uplugin @@ -17,13 +17,13 @@ "Name": "OSVR", "Type": "Runtime", "LoadingPhase": "PreDefault", - "WhitelistPlatforms": [ "Win64" ] + "WhitelistPlatforms": [ "Win32", "Win64" ] }, { "Name": "OSVRInput", "Type": "Runtime", "LoadingPhase": "PreDefault", - "WhitelistPlatforms": [ "Win64" ] + "WhitelistPlatforms": [ "Win32", "Win64" ] } ] } diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/OSVR.Build.cs b/OSVRUnreal/Plugins/OSVR/Source/OSVR/OSVR.Build.cs index e685def..398c68f 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/OSVR.Build.cs +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/OSVR.Build.cs @@ -7,15 +7,9 @@ public OSVR(TargetInfo Target) { PrivateIncludePathModuleNames.Add("TargetPlatform"); - var EngineDir = Path.GetFullPath(BuildConfiguration.RelativeEnginePath); - var openglDrvPrivatePath = Path.Combine(EngineDir, @"Source\Runtime\OpenGLDrv\Private"); - var openglPath = Path.Combine(EngineDir, @"Source\ThirdParty\OpenGL"); - PrivateIncludePaths.AddRange( new string[] { "OSVR/Private", - openglDrvPrivatePath, - openglPath // ... add other private include paths required here ... } ); @@ -35,8 +29,7 @@ public OSVR(TargetInfo Target) "Renderer", "ShaderCore", "HeadMountedDisplay", - "Json", - "OpenGLDrv" + "Json" // ... add private dependencies that you statically link with here ... } ); @@ -45,14 +38,12 @@ public OSVR(TargetInfo Target) PrivateDependencyModuleNames.Add("UnrealEd"); } - AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenGL"); - - if (Target.Platform == UnrealTargetPlatform.Win32 || Target.Platform == UnrealTargetPlatform.Win64) + if(Target.Platform == UnrealTargetPlatform.Win32 || Target.Platform == UnrealTargetPlatform.Win64) { PrivateDependencyModuleNames.AddRange(new string[] { "D3D11RHI" }); // Required for some private headers needed for the rendering support. - + var EngineDir = Path.GetFullPath(BuildConfiguration.RelativeEnginePath); PrivateIncludePaths.AddRange( new string[] { Path.Combine(EngineDir, @"Source\Runtime\Windows\D3D11RHI\Private"), diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.cpp b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.cpp index f29e096..f1f4287 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.cpp +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.cpp @@ -18,4 +18,3 @@ #include "OSVRCustomPresent.h" DEFINE_LOG_CATEGORY(FOSVRCustomPresentLog); - diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h index c10504f..ef25d6c 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresent.h @@ -23,7 +23,7 @@ DECLARE_LOG_CATEGORY_EXTERN(FOSVRCustomPresentLog, Log, All); -//template +template class FOSVRCustomPresent : public FRHICustomPresent { public: @@ -76,27 +76,11 @@ class FOSVRCustomPresent : public FRHICustomPresent check(IsInRenderingThread()); FScopeLock lock(&mOSVRMutex); InitializeImpl(); - if (!bDisplayOpen) - { - bDisplayOpen = LazyOpenDisplayImpl(); - check(bDisplayOpen); - } - - // @todo This is giving us a black screen. FinishRendering(); return true; } - virtual void RenderTexture_RenderThread( - FRHICommandListImmediate& rhiCmdList, - FTexture2DRHIParamRef backBuffer, - FTexture2DRHIParamRef srcTexture) - { - } - - // Initializes RenderManager but does not open the displays - // Can be called from the render thread or game thread. - // @todo: this call should be lazy, then IsInitialized can be removed. + // implement this in the sub-class virtual bool Initialize() { FScopeLock lock(&mOSVRMutex); @@ -108,70 +92,8 @@ class FOSVRCustomPresent : public FRHICustomPresent return bInitialized; } - virtual bool LazySetSrcTexture(FTexture2DRHIParamRef srcTexture) - { - FScopeLock lock(&mOSVRMutex); - return LazySetSrcTextureImpl(srcTexture); - } - - virtual bool LazyOpenDisplay() - { - FScopeLock lock(&mOSVRMutex); - if(IsInRenderingThread() && IsInitialized() && !bDisplayOpen) - { - bDisplayOpen = LazyOpenDisplayImpl(); - } - return bDisplayOpen; - } - - virtual void GetProjectionMatrix(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) - { - OSVR_ReturnCode rc; - rc = osvrRenderManagerGetDefaultRenderParams(&mRenderParams); - check(rc == OSVR_RETURN_SUCCESS); - - mRenderParams.nearClipDistanceMeters = static_cast(nearClip); - mRenderParams.farClipDistanceMeters = static_cast(farClip); - - // this method gets called with alternating eyes starting with the left. We get the render info when - // the left eye (index 0) is requested (releasing the old one, if any), - // and re-use the same collection when the right eye (index 0) is requested - if (eye == 0 || !mCachedRenderInfoCollection) { - if (mCachedRenderInfoCollection) { - rc = osvrRenderManagerReleaseRenderInfoCollection(mCachedRenderInfoCollection); - check(rc == OSVR_RETURN_SUCCESS); - } - rc = osvrRenderManagerGetRenderInfoCollection(mRenderManager, mRenderParams, &mCachedRenderInfoCollection); - check(rc == OSVR_RETURN_SUCCESS); - } - - GetProjectionMatrixImpl(eye, left, right, bottom, top, nearClip, farClip); - } - - virtual bool UpdateViewport(const FViewport& InViewport, class FRHIViewport* InViewportRHI) - { - FScopeLock lock(&mOSVRMutex); - - check(IsInGameThread()); - if (!IsInitialized()) - { - UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("UpdateViewport called but custom present is not initialized - doing nothing")); - return false; - } - else - { - check(InViewportRHI); - auto oldCustomPresent = InViewportRHI->GetCustomPresent(); - if (oldCustomPresent != this) - { - InViewportRHI->SetCustomPresent(this); - } - // UpdateViewport is called before we're initialized, so we have to - // defer updates to the render buffers until we're in the render thread. - //bRenderBuffersNeedToUpdate = true; - return true; - } - } + virtual void GetProjectionMatrix(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) = 0; + virtual bool UpdateViewport(const FViewport& InViewport, class FRHIViewport* InViewportRHI) = 0; // RenderManager normalizes displays a bit. We create the render target assuming horizontal side-by-side. // RenderManager then rotates that render texture if needed for vertical side-by-side displays. @@ -190,7 +112,6 @@ class FOSVRCustomPresent : public FRHICustomPresent bool bRenderBuffersNeedToUpdate = true; bool bInitialized = false; - bool bDisplayOpen = false; bool bOwnClientContext = true; float mScreenScale = 1.0f; OSVR_ClientContext mClientContext = nullptr; @@ -198,13 +119,10 @@ class FOSVRCustomPresent : public FRHICustomPresent OSVR_RenderInfoCollection mCachedRenderInfoCollection = nullptr; virtual bool CalculateRenderTargetSizeImpl(uint32& InOutSizeX, uint32& InOutSizeY) = 0; - virtual void GetProjectionMatrixImpl(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) = 0; + virtual bool InitializeImpl() = 0; - virtual bool LazyOpenDisplayImpl() = 0; - virtual bool LazySetSrcTextureImpl(FTexture2DRHIParamRef srcTexture) = 0; - - template - TGraphicsDevice* GetGraphicsDevice() + + virtual TGraphicsDevice* GetGraphicsDevice() { auto ret = RHIGetNativeDevice(); return reinterpret_cast(ret); diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h index 7f1e926..7039d8e 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentD3D11.h @@ -26,14 +26,42 @@ #include "HideWindowsPlatformTypes.h" #include "Runtime/Windows/D3D11RHI/Private/D3D11RHIPrivate.h" -class FDirect3D11CustomPresent : public FOSVRCustomPresent// +class FCurrentCustomPresent : public FOSVRCustomPresent { public: - FDirect3D11CustomPresent(OSVR_ClientContext clientContext, float screenScale) : + FCurrentCustomPresent(OSVR_ClientContext clientContext, float screenScale) : FOSVRCustomPresent(clientContext, screenScale) { } + virtual bool UpdateViewport(const FViewport& InViewport, class FRHIViewport* InViewportRHI) override + { + FScopeLock lock(&mOSVRMutex); + + check(IsInGameThread()); + if (!IsInitialized()) + { + UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("UpdateViewport called but custom present is not initialized - doing nothing")); + return false; + } + else + { + check(InViewportRHI); + //const FTexture2DRHIRef& rt = InViewport.GetRenderTargetTexture(); + //check(IsValidRef(rt)); + //SetRenderTargetTexture((ID3D11Texture2D*)rt->GetNativeResource()); // @todo: do we need to do this? + auto oldCustomPresent = InViewportRHI->GetCustomPresent(); + if (oldCustomPresent != this) + { + InViewportRHI->SetCustomPresent(this); + } + // UpdateViewport is called before we're initialized, so we have to + // defer updates to the render buffers until we're in the render thread. + //bRenderBuffersNeedToUpdate = true; + return true; + } + } + virtual bool AllocateRenderTargetTexture(uint32 index, uint32 sizeX, uint32 sizeY, uint8 format, uint32 numMips, uint32 flags, uint32 targetableTextureFlags, FTexture2DRHIRef& outTargetableTexture, FTexture2DRHIRef& outShaderResourceTexture, uint32 numSamples = 1) override { // @todo how should we determine SRGB? @@ -46,7 +74,7 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// if (IsInitialized()) { auto d3d11RHI = static_cast(GDynamicRHI); - auto graphicsDevice = GetGraphicsDevice(); + auto graphicsDevice = GetGraphicsDevice(); HRESULT hr; D3D11_TEXTURE2D_DESC textureDesc; memset(&textureDesc, 0, sizeof(textureDesc)); @@ -130,17 +158,28 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// return false; } -protected: - ID3D11Texture2D* RenderTargetTexture = NULL; - ID3D11RenderTargetView* RenderTargetView = NULL; - TArray mRenderBuffers; - TArray mRenderInfos; - OSVR_RenderManagerD3D11 mRenderManagerD3D11 = nullptr; - - virtual void GetProjectionMatrixImpl(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) override + virtual void GetProjectionMatrix(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) override { OSVR_ReturnCode rc; + rc = osvrRenderManagerGetDefaultRenderParams(&mRenderParams); + check(rc == OSVR_RETURN_SUCCESS); + + mRenderParams.nearClipDistanceMeters = static_cast(nearClip); + mRenderParams.farClipDistanceMeters = static_cast(farClip); + + // this method gets called with alternating eyes starting with the left. We get the render info when + // the left eye (index 0) is requested (releasing the old one, if any), + // and re-use the same collection when the right eye (index 0) is requested + if (eye == 0 || !mCachedRenderInfoCollection) { + if (mCachedRenderInfoCollection) { + rc = osvrRenderManagerReleaseRenderInfoCollection(mCachedRenderInfoCollection); + check(rc == OSVR_RETURN_SUCCESS); + } + rc = osvrRenderManagerGetRenderInfoCollection(mRenderManager, mRenderParams, &mCachedRenderInfoCollection); + check(rc == OSVR_RETURN_SUCCESS); + } + OSVR_RenderInfoD3D11 renderInfo; rc = osvrRenderManagerGetRenderInfoFromCollectionD3D11(mCachedRenderInfoCollection, eye, &renderInfo); check(rc == OSVR_RETURN_SUCCESS); @@ -154,6 +193,14 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// bottom = static_cast(renderInfo.projection.bottom); } +protected: + ID3D11Texture2D* RenderTargetTexture = NULL; + ID3D11RenderTargetView* RenderTargetView = NULL; + + TArray mRenderBuffers; + TArray mRenderInfos; + OSVR_RenderManagerD3D11 mRenderManagerD3D11 = nullptr; + virtual bool CalculateRenderTargetSizeImpl(uint32& InOutSizeX, uint32& InOutSizeY) override { if (InitializeImpl()) @@ -224,35 +271,21 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// return false; } - // The display is not opened here, but in a separate call (LazyOpenDisplay) - // because we may be in the game thread here + OSVR_OpenResultsD3D11 results; + rc = osvrRenderManagerOpenDisplayD3D11(mRenderManagerD3D11, &results); + if (rc == OSVR_RETURN_FAILURE || results.status == OSVR_OPEN_STATUS_FAILURE) + { + UE_LOG(FOSVRCustomPresentLog, Warning, + TEXT("osvrRenderManagerOpenDisplayD3D11 call failed, or the result status was OSVR_OPEN_STATUS_FAILURE. Potential causes could be that the display is already open in direct mode with another app, or the display does not support direct mode")); + return false; + } - bInitialized = true; - } - return true; - } + // @todo: create the textures? - virtual bool LazyOpenDisplayImpl() override - { - // we can assume we're initialized and running on the rendering thread - // and we haven't already opened the display here (done in parent class) - OSVR_OpenResultsD3D11 results; - OSVR_ReturnCode rc = osvrRenderManagerOpenDisplayD3D11(mRenderManagerD3D11, &results); - if (rc == OSVR_RETURN_FAILURE || results.status == OSVR_OPEN_STATUS_FAILURE) - { - UE_LOG(FOSVRCustomPresentLog, Warning, - TEXT("osvrRenderManagerOpenDisplayD3D11 call failed, or the result status was OSVR_OPEN_STATUS_FAILURE. Potential causes could be that the display is already open in direct mode with another app, or the display does not support direct mode")); - return false; + bInitialized = true; } return true; } - - virtual bool LazySetSrcTextureImpl(FTexture2DRHIParamRef srcTexture) override - { - // The Direct3D11 implementation can allocate the texture right away, - // so we don't need to defer setting this. - return true; - } virtual void FinishRendering() override { @@ -265,7 +298,7 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// rc = osvrRenderManagerStartPresentRenderBuffers(&presentState); check(rc == OSVR_RETURN_SUCCESS); check(mRenderBuffers.Num() == mRenderInfos.Num() && mRenderBuffers.Num() == mViewportDescriptions.Num()); - for (size_t i = 0; i < mRenderBuffers.Num(); i++) + for (int32 i = 0; i < mRenderBuffers.Num(); i++) { rc = osvrRenderManagerPresentRenderBufferD3D11(presentState, mRenderBuffers[i], mRenderInfos[i], mViewportDescriptions[i]); check(rc == OSVR_RETURN_SUCCESS); @@ -300,7 +333,7 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// D3D11_TEXTURE2D_DESC renderTextureDesc; RenderTargetTexture->GetDesc(&renderTextureDesc); - auto graphicsDevice = GetGraphicsDevice(); + auto graphicsDevice = GetGraphicsDevice(); //D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc; //memset(&renderTargetViewDesc, 0, sizeof(renderTargetViewDesc)); @@ -340,7 +373,7 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// hr = osvrRenderManagerStartRegisterRenderBuffers(&state); check(hr == OSVR_RETURN_SUCCESS); - for (size_t i = 0; i < mRenderBuffers.Num(); i++) + for (int32 i = 0; i < mRenderBuffers.Num(); i++) { hr = osvrRenderManagerRegisterRenderBufferD3D11(state, mRenderBuffers[i]); check(hr == OSVR_RETURN_SUCCESS); @@ -376,7 +409,7 @@ class FDirect3D11CustomPresent : public FOSVRCustomPresent// OSVR_GraphicsLibraryD3D11 ret; // Put the device and context into a structure to let RenderManager // know to use this one rather than creating its own. - ret.device = GetGraphicsDevice(); + ret.device = GetGraphicsDevice(); ID3D11DeviceContext *ctx = NULL; ret.device->GetImmediateContext(&ctx); check(ctx); diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h index a8b4f63..963bb8c 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVRCustomPresentOpenGL.h @@ -1,5 +1,5 @@ // -// Copyright 2016 Sensics, Inc. +// Copyright 2014, 2015 Razer Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,217 +16,28 @@ #pragma once - +#if !PLATFORM_WINDOWS #include "IOSVR.h" #include "OSVRCustomPresent.h" -#include -#include - -#if OSVR_ANDROID -// @todo this may not work - if not, what headers is unreal expecting? -#define OSVR_RM_USE_OPENGLES20 1 -#else -// you can't include and in the same source file, -// and unreal is going to include -#define OSVR_RM_SKIP_GL_INCLUDE 1 -#define OSVR_RM_SKIP_GLEXT_INCLUDE 1 -#include -#endif - -#include - -#include "OpenGLDrvPrivate.h" -#include "OpenGLResources.h" - -typedef struct FUnrealBackBufferContainer -{ - GLint displayFrameBuffer; - int width; - int height; -}; - - -//========================================================================== -// Toolkit object to handle our window creation needs. We pass it down to -// the RenderManager to override its window and context creation behavior, -// since we are using the Unreal window/context creation. -class FUnrealOSVRRenderManagerOpenGLToolkit { -private: - OSVR_OpenGLToolkitFunctions toolkit; - FUnrealBackBufferContainer* mBackBufferContainer; - - // eliminate force to bool performance warning. - static inline OSVR_CBool ConvertBool(bool bValue) - { - return bValue ? OSVR_TRUE : OSVR_FALSE; - } - - static inline bool ConvertBool(OSVR_CBool bValue) - { - return bValue == OSVR_TRUE ? true : false; - } - - static void createImpl(void* data) - { - } - - static void destroyImpl(void* data) - { - delete ((FUnrealOSVRRenderManagerOpenGLToolkit*)data); - } - - static OSVR_CBool addOpenGLContextImpl(void* data, const OSVR_OpenGLContextParams* p) - { - return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->addOpenGLContext(p)); - } - - static OSVR_CBool removeOpenGLContextsImpl(void* data) - { - return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->removeOpenGLContexts()); - } - - static OSVR_CBool makeCurrentImpl(void* data, size_t display) - { - return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->makeCurrent(display)); - } - - static OSVR_CBool swapBuffersImpl(void* data, size_t display) - { - return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->swapBuffers(display)); - } - - static OSVR_CBool setVerticalSyncImpl(void* data, OSVR_CBool verticalSync) - { - return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->setVerticalSync(ConvertBool(verticalSync))); - } - - static OSVR_CBool handleEventsImpl(void* data) - { - return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->handleEvents()); - } - - static OSVR_CBool getDisplayFrameBufferImpl(void* data, size_t display, GLint* displayFrameBufferOut) - { - return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->getDisplayFrameBuffer(display, displayFrameBufferOut)); - } - - static OSVR_CBool getDisplaySizeOverrideImpl(void* data, size_t display, int* width, int* height) - { - return ConvertBool(((FUnrealOSVRRenderManagerOpenGLToolkit*)data)->getDisplaySizeOverride(display, width, height)); - } - -public: - FUnrealOSVRRenderManagerOpenGLToolkit(FUnrealBackBufferContainer* backBufferContainer) - : mBackBufferContainer(backBufferContainer) - { - memset(&toolkit, 0, sizeof(toolkit)); - toolkit.size = sizeof(toolkit); - toolkit.data = this; - - toolkit.create = createImpl; - toolkit.destroy = destroyImpl; - toolkit.addOpenGLContext = addOpenGLContextImpl; - toolkit.removeOpenGLContexts = removeOpenGLContextsImpl; - toolkit.makeCurrent = makeCurrentImpl; - toolkit.swapBuffers = swapBuffersImpl; - toolkit.setVerticalSync = setVerticalSyncImpl; - toolkit.handleEvents = handleEventsImpl; - toolkit.getDisplayFrameBuffer = getDisplayFrameBufferImpl; - toolkit.getDisplaySizeOverride = getDisplaySizeOverrideImpl; - } - - ~FUnrealOSVRRenderManagerOpenGLToolkit() - { - } - - const OSVR_OpenGLToolkitFunctions* getToolkit() const - { - return &toolkit; - } - - bool addOpenGLContext(const OSVR_OpenGLContextParams* p) - { - // @todo change resolution and move the window here? - // We may need to just record the params and respond to them later, - // since this call has to be done on the game thread, but it's called on the - // window thread. - - //FSystemResolution::RequestResolutionChange(p->width, p->height, EWindowMode::Windowed); - return true; - } - - bool removeOpenGLContexts() - { - return true; - } - - bool makeCurrent(size_t display) - { - // we are always current, since unreal creates our context - // beforehand - return true; - } - - bool swapBuffers(size_t display) - { - // unreal does this for us - return true; - } - - bool setVerticalSync(bool verticalSync) - { - // @todo ??? - return true; - } - - bool handleEvents() - { - // @todo ??? - return true; - } - bool getDisplayFrameBuffer(size_t display, GLint* displayFrameBufferOut) - { - (*displayFrameBufferOut) = mBackBufferContainer->displayFrameBuffer; - return true; - } - - bool getDisplaySizeOverride(size_t display, int* width, int* height) - { - (*width) = mBackBufferContainer->width; - (*height) = mBackBufferContainer->height; - return true; - } -}; +#include -class FOpenGLCustomPresent : public FOSVRCustomPresent +// @todo OpenGL implementation +class FCurrentCustomPresent : public FOSVRCustomPresent { public: - FOpenGLCustomPresent(OSVR_ClientContext clientContext, float screenScale) : + FCurrentCustomPresent(OSVR_ClientContext clientContext, float screenScale) : FOSVRCustomPresent(clientContext, screenScale) { } - virtual ~FOpenGLCustomPresent() - { - if (RenderTargetTexture > 0) - { - glDeleteTextures(1, &RenderTargetTexture); - } - - if (mBackBufferContainer) - { - delete mBackBufferContainer; - } - } - protected: - - TArray mRenderBuffers; - TArray mRenderInfos; - OSVR_RenderManagerOpenGL mRenderManagerOpenGL = nullptr; - GLuint RenderTargetTexture = 0; - FUnrealBackBufferContainer* mBackBufferContainer = nullptr; + //virtual osvr::renderkit::GraphicsLibrary CreateGraphicsLibrary() override + //{ + // osvr::renderkit::GraphicsLibrary ret; + // // OpenGL path not implemented yet + // return ret; + //} virtual FString GetGraphicsLibraryName() override { @@ -238,267 +49,6 @@ class FOpenGLCustomPresent : public FOSVRCustomPresent return false; } - virtual bool AllocateRenderTargetTexture(uint32 index, uint32 sizeX, uint32 sizeY, uint8 format, uint32 numMips, uint32 flags, uint32 targetableTextureFlags, FTexture2DRHIRef& outTargetableTexture, FTexture2DRHIRef& outShaderResourceTexture, uint32 numSamples = 1) override - { - // For OpenGL, we allow RenderManager to create our render textures for us and then - // we pass that texture id in on the first RenderTexture_RenderThread call. - - // signal UpdateRenderBuffers to register the new render buffer when we - // next get it passed in via LazySetSrcTexture - bRenderBuffersNeedToUpdate = true; - return false; - } - - virtual void GetProjectionMatrixImpl(OSVR_RenderInfoCount eye, float &left, float &right, float &bottom, float &top, float nearClip, float farClip) override - { - OSVR_ReturnCode rc; - OSVR_RenderInfoOpenGL renderInfo; - rc = osvrRenderManagerGetRenderInfoFromCollectionOpenGL(mCachedRenderInfoCollection, eye, &renderInfo); - check(rc == OSVR_RETURN_SUCCESS); - - // previously we divided these by renderInfo.projection.nearClip but we need - // to pass these unmodified through to the OSVR_Projection_to_D3D call (and OpenGL - // equivalent) - left = static_cast(renderInfo.projection.left); - right = static_cast(renderInfo.projection.right); - top = static_cast(renderInfo.projection.top); - bottom = static_cast(renderInfo.projection.bottom); - } - - virtual bool CalculateRenderTargetSizeImpl(uint32& InOutSizeX, uint32& InOutSizeY) override - { - if (InitializeImpl()) - { - // Should we create a RenderParams? - OSVR_ReturnCode rc; - - rc = osvrRenderManagerGetDefaultRenderParams(&mRenderParams); - check(rc == OSVR_RETURN_SUCCESS); - - OSVR_RenderInfoCollection renderInfoCollection = { 0 }; - rc = osvrRenderManagerGetRenderInfoCollection(mRenderManager, mRenderParams, &renderInfoCollection); - check(rc == OSVR_RETURN_SUCCESS); - - OSVR_RenderInfoCount numRenderInfo = { 0 }; - rc = osvrRenderManagerGetNumRenderInfoInCollection(renderInfoCollection, &numRenderInfo); - check(rc == OSVR_RETURN_SUCCESS); - - mRenderInfos.Empty(); - for (size_t i = 0; i < numRenderInfo; i++) - { - OSVR_RenderInfoOpenGL renderInfo = { 0 }; - rc = osvrRenderManagerGetRenderInfoFromCollectionOpenGL(renderInfoCollection, i, &renderInfo); - check(rc == OSVR_RETURN_SUCCESS); - - mRenderInfos.Add(renderInfo); - } - - rc = osvrRenderManagerReleaseRenderInfoCollection(renderInfoCollection); - check(rc == OSVR_RETURN_SUCCESS); - - // check some assumptions. Should all be the same height. - check(mRenderInfos.Num() == 2); - check(mRenderInfos[0].viewport.height == mRenderInfos[1].viewport.height); - - mRenderInfos[0].viewport.width = int(float(mRenderInfos[0].viewport.width) * mScreenScale); - mRenderInfos[0].viewport.height = int(float(mRenderInfos[0].viewport.height) * mScreenScale); - mRenderInfos[1].viewport.width = mRenderInfos[0].viewport.width; - mRenderInfos[1].viewport.height = mRenderInfos[0].viewport.height; - mRenderInfos[1].viewport.left = mRenderInfos[0].viewport.width; - - InOutSizeX = mRenderInfos[0].viewport.width + mRenderInfos[1].viewport.width; - InOutSizeY = mRenderInfos[0].viewport.height; - check(InOutSizeX != 0 && InOutSizeY != 0); - return true; - } - return false; - } - - virtual bool InitializeImpl() override - { - if (!IsInitialized()) - { - auto graphicsLibrary = CreateGraphicsLibrary(); - auto graphicsLibraryName = GetGraphicsLibraryName(); - OSVR_ReturnCode rc; - - if (!mClientContext) - { - UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("Can't initialize FOSVRCustomPresent without a valid client context")); - return false; - } - - rc = osvrCreateRenderManagerOpenGL( - mClientContext, TCHAR_TO_ANSI(*graphicsLibraryName), - graphicsLibrary, &mRenderManager, &mRenderManagerOpenGL); - - if (rc == OSVR_RETURN_FAILURE || !mRenderManager || !mRenderManagerOpenGL) - { - UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("osvrCreateRenderManagerD3D11 call failed, or returned numm renderManager/renderManagerD3D11 instances")); - return false; - } - - rc = osvrRenderManagerGetDoingOkay(mRenderManager); - if (rc == OSVR_RETURN_FAILURE) - { - UE_LOG(FOSVRCustomPresentLog, Warning, TEXT("osvrRenderManagerGetDoingOkay call failed. Perhaps there was an error during initialization?")); - return false; - } - - // We don't open the display here, because this might be called from the game thread - // LazyOpenDisplay needs to be called on the rendering thread to open the display - bInitialized = true; - } - return true; - } - - virtual bool LazyOpenDisplayImpl() override - { - // we can assume we're initialized and running on the rendering thread - // and we haven't already opened the display here (done in parent class) - OSVR_OpenResultsOpenGL results; - OSVR_ReturnCode rc = osvrRenderManagerOpenDisplayOpenGL(mRenderManagerOpenGL, &results); - if (rc == OSVR_RETURN_FAILURE || results.status == OSVR_OPEN_STATUS_FAILURE) - { - UE_LOG(FOSVRCustomPresentLog, Warning, - TEXT("osvrRenderManagerOpenDisplayD3D11 call failed, or the result status was OSVR_OPEN_STATUS_FAILURE. Potential causes could be that the display is already open in direct mode with another app, or the display does not support direct mode")); - return false; - } - return true; - } - - virtual bool LazySetSrcTextureImpl(FTexture2DRHIParamRef srcTexture) override - { - auto textureOpenGL = static_cast(&(*srcTexture)); - RenderTargetTexture = textureOpenGL->Resource; - mRenderTexture = srcTexture; // @todo do we need mRenderTexture? - UpdateRenderBuffers(); - return true; - } - - virtual void RenderTexture_RenderThread(FRHICommandListImmediate& rhiCmdList, FTexture2DRHIParamRef backBuffer, FTexture2DRHIParamRef srcTexture) override - { - check(IsInRenderingThread()); - LazySetSrcTexture(srcTexture); - } - - virtual void FinishRendering() override - { - check(IsInitialized()); - - // @todo: this needs to be more robust. - // Can we find this by inspection of the view somehow? - // After the first call, the framebuffer ends up set to - // 0 before entering this function, but 0 is not the - // framebuffer for the window. - if (mBackBufferContainer->displayFrameBuffer < 0) - { - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &mBackBufferContainer->displayFrameBuffer); - } - - UpdateRenderBuffers(); - - // all of the render manager samples keep the flipY at the default false, - // for both OpenGL and DirectX. Is this even needed anymore? - OSVR_ReturnCode rc; - OSVR_RenderManagerPresentState presentState; - rc = osvrRenderManagerStartPresentRenderBuffers(&presentState); - check(rc == OSVR_RETURN_SUCCESS); - check(mRenderBuffers.Num() == mRenderInfos.Num() && mRenderBuffers.Num() == mViewportDescriptions.Num()); - for (size_t i = 0; i < mRenderBuffers.Num(); i++) - { - auto renderBuffer = mRenderBuffers[i]; - auto renderInfo = mRenderInfos[i]; - auto viewportDescription = mViewportDescriptions[i]; - rc = osvrRenderManagerPresentRenderBufferOpenGL(presentState, renderBuffer, renderInfo, viewportDescription); - check(rc == OSVR_RETURN_SUCCESS); - } - - // @todo: figure out why this call is failing (release DLLs make debugging - // difficult here - can't determine which failure branch is being taken). - rc = osvrRenderManagerFinishPresentRenderBuffers(mRenderManager, presentState, mRenderParams, ShouldFlipY() ? OSVR_TRUE : OSVR_FALSE); - check(rc == OSVR_RETURN_SUCCESS); - } - - virtual void UpdateRenderBuffers() override - { - OSVR_ReturnCode rc; - - check(IsInitialized()); - if (bRenderBuffersNeedToUpdate) - { - //check(RenderTargetTexture); - mRenderBuffers.Empty(); - - // Adding two RenderBuffers, but they both point to the same D3D11 texture target - for (int i = 0; i < 2; i++) - { - OSVR_RenderBufferOpenGL buffer = { 0 }; - buffer.colorBufferName = RenderTargetTexture; - mRenderBuffers.Add(buffer); - } - - // We need to register these new buffers. - // @todo RegisterRenderBuffers doesn't do anything other than set a flag and crash - // if you pass it a non-empty vector here. Passing it a dummy array for now. - - { - OSVR_RenderManagerRegisterBufferState state; - rc = osvrRenderManagerStartRegisterRenderBuffers(&state); - check(rc == OSVR_RETURN_SUCCESS); - - for (size_t i = 0; i < mRenderBuffers.Num(); i++) - { - rc = osvrRenderManagerRegisterRenderBufferOpenGL(state, mRenderBuffers[i]); - check(rc == OSVR_RETURN_SUCCESS); - } - - rc = osvrRenderManagerFinishRegisterRenderBuffers(mRenderManager, state, false); - check(rc == OSVR_RETURN_SUCCESS); - } - - // Now specify the viewports for each. - mViewportDescriptions.Empty(); - - OSVR_ViewportDescription leftEye, rightEye; - - leftEye.left = 0; - leftEye.lower = 0; - leftEye.width = 0.5; - leftEye.height = 1.0; - mViewportDescriptions.Add(leftEye); - - rightEye.left = 0.5; - rightEye.lower = 0; - rightEye.width = 0.5; - rightEye.height = 1.0; - mViewportDescriptions.Add(rightEye); - - bRenderBuffersNeedToUpdate = false; - } - } - - virtual FUnrealBackBufferContainer* getBackBufferContainer() - { - if (!mBackBufferContainer) - { - mBackBufferContainer = new FUnrealBackBufferContainer(); - mBackBufferContainer->displayFrameBuffer = -1; - mBackBufferContainer->width = 1280; - mBackBufferContainer->height = 720; - } - return mBackBufferContainer; - } - - virtual OSVR_GraphicsLibraryOpenGL CreateGraphicsLibrary() - { - OSVR_GraphicsLibraryOpenGL ret = { 0 }; - // @todo figure out why this toolkit doesn't get passed back when - // we get the render info from the collection API. - auto toolkit = new FUnrealOSVRRenderManagerOpenGLToolkit(getBackBufferContainer()); - ret.toolkit = toolkit->getToolkit(); - return ret; - } }; - +#endif diff --git a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVREntryPoint.cpp b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVREntryPoint.cpp index 18fd915..fca63f7 100644 --- a/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVREntryPoint.cpp +++ b/OSVRUnreal/Plugins/OSVR/Source/OSVR/Private/OSVREntryPoint.cpp @@ -29,16 +29,23 @@ DEFINE_LOG_CATEGORY(OSVREntryPointLog); OSVREntryPoint::OSVREntryPoint() { + // avoid BuildCookRun hangs + if (IsRunningCommandlet() || IsRunningDedicatedServer()) + { + UE_LOG(OSVREntryPointLog, Display, TEXT("OSVREntryPoint::OSVREntryPoint(): running as commandlet or dedicated server - skipping client context startup.")); + return; + } + osvrClientAttemptServerAutoStart(); osvrClientContext = osvrClientInit("com.osvr.unreal.plugin"); { bool bClientContextOK = false; - bool bFailure = false; - auto begin = FDateTime::Now().GetSecond(); - auto end = begin + 3; - while (FDateTime::Now().GetSecond() < end && !bClientContextOK && !bFailure) + bool bFailure = false; + auto begin = FDateTime::Now().GetTicks(); + auto end = begin + 3 * ETimespan::TicksPerSecond; + while (FDateTime::Now().GetTicks() < end && !bClientContextOK && !bFailure) { bClientContextOK = osvrClientCheckStatus(osvrClientContext) == OSVR_RETURN_SUCCESS; if (!bClientContextOK) @@ -71,7 +78,11 @@ OSVREntryPoint::~OSVREntryPoint() InterfaceCollection = nullptr; #endif - osvrClientShutdown(osvrClientContext); + if (osvrClientContext) + { + osvrClientShutdown(osvrClientContext); + } + osvrClientReleaseAutoStartedServer(); }