Commit 351a2917 authored by Robert Menzel's avatar Robert Menzel
Browse files

updated Rift SDK handling and support for 360 gamepad on windows

parent 3539d151
......@@ -17,6 +17,9 @@ lib/*
# QT creator:
#
*.user
*.sln
*.vcxproj.filters
*.vcxproj
#
# build files
......
#pragma once
/**
* IMPORTANT: DON'T EXPECT THIS CLASS TO HAVE A FINAL AND STABLE API!
* IMPORTANT: DON'T EXPECT THIS TO HAVE A FINAL AND STABLE API!
*
* This class needs the LibOVR version 0.3.2 or higher to work.
* This needs the LibOVR version 0.4 (or higher, unless the API breaks) to work.
* Headers of this lib need to be placed in the search path.
*
* In addition ACGL_USE_OCULUS_RIFT has to be defined.
*
* IMPORTANT:
* The order of SDK and window initialisation is important:
* 1. call initSDK();
* 2. call createHMD
* 3. create a window and a OpenGL context
* 4. ACGL::init();
* 5. build textures to render into (one for each eye), the size can be getOptimalRenderSizePerEye() or smaller
* 6. build a desription of those textures for the Rift SDK using generateEyeTextureDescription ( <EyeTexture> )
* 7. configureRendering - store the ovrEyeRenderDesc values for later, _renderTagetSize is the size of your Rift window
*
* At runtime:
* * call deactivateHealthWarning when the user presses a button
*
* Each frame:
* 1. beginFrame()
* 2. TODO: camera handling -> generates <poseOfEyes>
* 3. ovrHmd_EndFrame( <HMD> , <poseOfEyes> , (ovrTexture*) <EyeTexture> );
* IMPORTANT: if ovrHmd_EndFrame gets called, don't call any swapBuffers more on your own!
*
* clean up:
* 1. destroyHMD
* 2. shutdownSDK
*
*/
#ifdef ACGL_USE_OCULUS_RIFT
#include <OVR_Version.h>
#if ((OVR_MAJOR_VERSION > 0) || (OVR_MINOR_VERSION >= 4))
#include <ACGL/ACGL.hh>
#include <glm/glm.hpp>
#if ACGL_RIFT_SDK_VERSION >= 40
#include <OVR_CAPI.h>
//class GLFWWindow;
#include <ACGL/OpenGL/GL.hh>
#include <ACGL/OpenGL/Objects/Texture.hh>
#include <GLFW/glfw3.h>
#include <OVR_CAPI_GL.h>
namespace ACGL{
namespace HardwareSupport{
namespace OVR{
// init the SDK once before calling any of the following functions:
bool initSDK();
bool initRiftSDK();
void shutdownRiftSDK();
// shut it down at the end:
void shutdownSDK();
// create a standard Rift, can be replaced with own, more specialized code:
ovrHmd createRift(bool _headTrackingIsRequired = false, bool _headTranslationTrackingIsAllowed = true);
void destroyRift(ovrHmd _hmd);
ovrHmd createHMD(bool _headTrackingIsRequired = false, bool _headTranslationTrackingIsAllowed = true);
void destroyHMD(ovrHmd _hmd);
// SDK gives different sizes per eye, return the max to make things easier:
glm::uvec2 getOptimalRenderSizePerEye(ovrHmd _hmd);
#ifdef ACGL_COMPILE_WITH_GLFW
// the Rift SDK need to know our window, this function hides the window system/OS specifics:
// returns a ovrEyeRenderDesc[2] used by the SDK in _eyeRenderDesc. The returned struct stores the size of the
// textures, so update this if the textures change in size!
// _eyeRenderDesc has to be an array with at least two elements!
void generateEyeTextureDescription(ACGL::OpenGL::SharedTexture2D _leftTexture,
ACGL::OpenGL::SharedTexture2D _rightTexture,
ovrGLTexture _eyeRenderDesc[2]);
// the Rift SDK needs to know our window, this function hides the window system/OS specifics:
// it's limited to OpenGL / no multisampling back buffers, if more flexibility is needed,
// write your own similar function:
// write your own similar function.
// _eyeRenderDesc will be set by this function!
#ifdef ACGL_COMPILE_WITH_GLFW
bool configureRendering(ovrHmd _hmd, ovrEyeRenderDesc _eyeRenderDesc[2], glm::uvec2 _renderTagetSize, GLFWwindow *_window);
#endif
......@@ -47,8 +81,12 @@ namespace HardwareSupport{
// returns true if the warning is still displayed. Can be called multiple times.
bool deactivateHealthWarning(ovrHmd _hmd);
// call at the beginning of a frame:
void beginFrame(ovrHmd _hmd);
// used only for debugging:
void printOVRDistortionCaps(unsigned int _caps);
void printDistortionCaps(unsigned int _caps);
}
}
}
......
......@@ -148,7 +148,7 @@ GamePad::GamePad( int _n )
setAxisMapping( RIGHT_ANALOG_STICK_Y , 3);
setMinAxisSensitivity( 0.05f );
} else if (mJoystickName == "Microsoft X-Box 360 pad") {
} else if (mJoystickName == "Microsoft X-Box 360 pad") { // 360 USB gamepad on Linux
// real buttons:
setButtonMapping( SELECT, 6);
......@@ -179,7 +179,37 @@ GamePad::GamePad( int _n )
setMinAxisSensitivity( 0.2f );
} else {
} else if (mJoystickName == "Microsoft PC-joystick driver") { // 360 USB gamepad on Windows
// real buttons:
setButtonMapping(SELECT, 6);
setButtonMapping(START, 7);
setButtonMapping(RIGHT_PAD_NORTH, 3);
setButtonMapping(RIGHT_PAD_EAST, 1);
setButtonMapping(RIGHT_PAD_SOUTH, 0);
setButtonMapping(RIGHT_PAD_WEST, 2);
setButtonMapping(LEFT_SHOULDER, 4);
setButtonMapping(RIGHT_SHOULDER, 5);
setButtonMapping(LEFT_PAD_NORTH, 10);
setButtonMapping(LEFT_PAD_EAST, 11);
setButtonMapping(LEFT_PAD_SOUTH, 12);
setButtonMapping(LEFT_PAD_WEST, 13);
//setButtonMapping(LEFT_TRIGGER, 2 + mNumberOfButtons); // axis 2
//setButtonMapping(RIGHT_TRIGGER, 5 + mNumberOfButtons); // axis 5
setAxisMapping(LEFT_ANALOG_TRIGGER, 2); mAxesMultiplier[2] = 0.5f; mAxesAdd[2] = 0.5f;
setAxisMapping(RIGHT_ANALOG_TRIGGER, 5); mAxesMultiplier[5] = -0.5f; mAxesAdd[5] = 0.5f;
setAxisMapping(LEFT_ANALOG_STICK_X, 0);
setAxisMapping(LEFT_ANALOG_STICK_Y, 1);
setAxisMapping(RIGHT_ANALOG_STICK_X, 3); mAxesMultiplier[3] = -1.0f;
setAxisMapping(RIGHT_ANALOG_STICK_Y, 4); mAxesMultiplier[4] = -1.0f;
setMinAxisSensitivity(0.2f);
} else {
debug() << "unknown gamepad: " << mJoystickName << " can't configure buttons" << endl;
}
}
......@@ -343,6 +373,9 @@ void GamePad::update()
float sign = ((mAxes[ i ] < 0.0f) && (tmp != 0.0f)) ? -1.0f : 1.0f;
mAxes[ i ] = tmp * sign;
}
//debug().unmute();
//printState();
//debug().mute();
}
void GamePad::printState()
......
......@@ -9,200 +9,230 @@
#if ACGL_RIFT_SDK_VERSION >= 40
#ifdef ACGL_COMPILE_WITH_GLFW
#define GLFW_EXPOSE_NATIVE_WIN32
#define GLFW_EXPOSE_NATIVE_WGL
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_WIN32
#define GLFW_EXPOSE_NATIVE_WGL
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#endif
#include <OVR_CAPI_GL.h>
using namespace std;
using namespace ACGL::Utils;
namespace ACGL{
namespace HardwareSupport{
namespace OVR{
using namespace std;
using namespace ACGL::Utils;
// C API helpers:
// C API helpers:
static bool ACGL_RiftSDKInitialized = false;
static bool ACGL_RiftSDKInitialized = false;
bool initRiftSDK()
{
cout << "initRiftSDK()" << endl;
bool initSDK()
{
//cout << "initRiftSDK()" << endl;
if (ACGL_RiftSDKInitialized) return true; // don't init twice
if (ACGL_RiftSDKInitialized) return true; // don't init twice
ovrBool ok = ovr_Initialize();
if (!ok) {
error() << "could not initialize Oculus Rift library" << endl;
}
else {
ACGL_RiftSDKInitialized = true;
}
return ACGL_RiftSDKInitialized;
ovrBool ok = ovr_Initialize();
if (!ok) {
error() << "could not initialize Oculus Rift library" << endl;
}
else {
ACGL_RiftSDKInitialized = true;
}
return ACGL_RiftSDKInitialized;
}
void shutdownRiftSDK()
{
ovr_Shutdown();
void shutdownSDK()
{
if (ACGL_RiftSDKInitialized) ovr_Shutdown();
ACGL_RiftSDKInitialized = false;
}
// For more sophisticated use cases build your own Rift for your needs based on the Rift SDK instead of using this default Rift.
//
// _headTrackingIsRequired = if false, the call will create a dummy device that won't generate any data in case no real Rift is connected
// (for developing without an actual device).
// _headTranslationTrackingIsAllowed = if true the Tracking of DK2 will get supported, if false even a DK2 will behave like a DK1
ovrHmd createHMD(bool _headTrackingIsRequired, bool _headTranslationTrackingIsAllowed)
{
//cout << "createRift()" << endl;
if (!ACGL_RiftSDKInitialized) {
error() << "Rift SDK not initialized correctly - did you call/check initRiftSDK()?" << endl;
}
// For more sophisticated use cases build your own Rift for your needs based on the Rift SDK instead of using this default Rift.
//
// _headTrackingIsRequired = if false, the call will create a dummy device that won't generate any data in case no real Rift is connected
// (for developing without an actual device).
// _headTranslationTrackingIsAllowed = if true the Tracking of DK2 will get supported, if false even a DK2 will behave like a DK1
ovrHmd createRift(bool _headTrackingIsRequired, bool _headTranslationTrackingIsAllowed)
{
cout << "createRift()" << endl;
if (!ACGL_RiftSDKInitialized) {
error() << "Rift SDK not initialized correctly - did you call/check initRiftSDK()?" << endl;
}
ovrHmd mHmd = ovrHmd_Create(0);
if (!mHmd && _headTrackingIsRequired) {
error() << "could not connect to an Oculus Rift HMD" << endl;
return NULL;
}
else if (!mHmd && !_headTrackingIsRequired) {
ovrHmd mHmd = ovrHmd_Create(0);
if (!mHmd && _headTrackingIsRequired) {
error() << "could not connect to an Oculus Rift HMD" << endl;
return NULL;
}
else if (!mHmd && !_headTrackingIsRequired) {
#if ACGL_RIFT_USE_DUMMY
warning() << "could not connect to a real Oculus Rift HMD - generating sensorless dummy" << endl;
mHmd = ovrHmd_CreateDebug(ovrHmd_DK1);
warning() << "could not connect to a real Oculus Rift HMD - generating sensorless dummy" << endl;
mHmd = ovrHmd_CreateDebug(ovrHmd_DK1);
#else
debug() << "could not connect to a real Oculus Rift HMD" << endl;
mHmd = NULL;
debug() << "could not connect to a real Oculus Rift HMD" << endl;
mHmd = NULL;
#endif
//ovrHmd_SetEnabledCaps(mHmd, ovrHmdCap_NoMirrorToWindow);
//ovrHmd_SetEnabledCaps(mHmd, ovrHmdCap_NoMirrorToWindow);
return mHmd;
}
// debug output:
debug() << "Connected to: " << mHmd->ProductName << endl;
return mHmd;
}
// start the tracking:
// what the application supports:
unsigned int supportedCaps = ovrTrackingCap_Orientation | ovrTrackingCap_MagYawCorrection | ovrTrackingCap_Position; // | ovrHmdCap_LowPersistence | ovrHmdCap_DynamicPrediction;
if (_headTranslationTrackingIsAllowed) supportedCaps |= ovrTrackingCap_Position;
// what the device must deliver as a bare minimum:
unsigned int requiredCaps = 0;
if (_headTrackingIsRequired) requiredCaps |= ovrTrackingCap_Orientation;
// debug output:
debug() << "Connected to: " << mHmd->ProductName << endl;
// start the tracking:
// what the application supports:
unsigned int supportedCaps = ovrTrackingCap_Orientation | ovrTrackingCap_MagYawCorrection | ovrTrackingCap_Position; // | ovrHmdCap_LowPersistence | ovrHmdCap_DynamicPrediction;
if (_headTranslationTrackingIsAllowed) supportedCaps |= ovrTrackingCap_Position;
ovrBool ok = ovrHmd_ConfigureTracking(mHmd, supportedCaps, requiredCaps);
if (!ok) {
error() << "could not get connected to a Rift tracker - only rendering is supported" << endl;
}
return mHmd;
}
// what the device must deliver as a bare minimum:
unsigned int requiredCaps = 0;
if (_headTrackingIsRequired) requiredCaps |= ovrTrackingCap_Orientation;
void destroyRift(ovrHmd _hmd)
{
ovrHmd_Destroy(_hmd);
ovrBool ok = ovrHmd_ConfigureTracking(mHmd, supportedCaps, requiredCaps);
if (!ok) {
error() << "could not get connected to a Rift tracker - only rendering is supported" << endl;
}
return mHmd;
}
void destroyHMD(ovrHmd _hmd)
{
if (_hmd) ovrHmd_Destroy(_hmd);
}
glm::uvec2 getOptimalRenderSizePerEye(ovrHmd _hmd)
{
if (_hmd == NULL) return glm::uvec2(640, 800);
glm::uvec2 getOptimalRenderSizePerEye(ovrHmd _hmd)
{
if (_hmd == NULL) return glm::uvec2(640, 800);
ovrSizei optimalLeft = ovrHmd_GetFovTextureSize(_hmd, ovrEye_Left, _hmd->DefaultEyeFov[0], 1.0f);
ovrSizei optimalRight = ovrHmd_GetFovTextureSize(_hmd, ovrEye_Right, _hmd->DefaultEyeFov[1], 1.0f);
ovrSizei optimalLeft = ovrHmd_GetFovTextureSize(_hmd, ovrEye_Left, _hmd->DefaultEyeFov[0], 1.0f);
ovrSizei optimalRight = ovrHmd_GetFovTextureSize(_hmd, ovrEye_Right, _hmd->DefaultEyeFov[1], 1.0f);
debug() << "optimalLeft " << optimalLeft.w << " " << optimalLeft.h << endl;
debug() << "optimalRight " << optimalRight.w << " " << optimalRight.h << endl;
//debug() << "optimalLeft " << optimalLeft.w << " " << optimalLeft.h << endl;
//debug() << "optimalRight " << optimalRight.w << " " << optimalRight.h << endl;
debug() << "hmd: " << _hmd->ProductName << endl;
debug() << "hmd WindowsPos: " << _hmd->WindowsPos.x << " " << _hmd->WindowsPos.y << endl;
//debug() << "hmd: " << _hmd->ProductName << endl;
//debug() << "hmd WindowsPos: " << _hmd->WindowsPos.x << " " << _hmd->WindowsPos.y << endl;
return glm::uvec2(glm::max(optimalLeft.w, optimalRight.w), glm::max(optimalLeft.h, optimalRight.h));
return glm::uvec2(glm::max(optimalLeft.w, optimalRight.w), glm::max(optimalLeft.h, optimalRight.h));
}
//return glm::uvec2(960, 1200);
void generateEyeTextureDescription(ACGL::OpenGL::SharedTexture2D _leftTexture, ACGL::OpenGL::SharedTexture2D _rightTexture, ovrGLTexture _eyeRenderDesc[2])
{
// same for both eyes:
for (int eyeIndex = 0; eyeIndex < 2; ++eyeIndex) {
_eyeRenderDesc[eyeIndex].OGL.Header.API = ovrRenderAPI_OpenGL;
_eyeRenderDesc[eyeIndex].OGL.Header.RenderViewport.Pos.x = 0;
_eyeRenderDesc[eyeIndex].OGL.Header.RenderViewport.Pos.y = 0;
}
_eyeRenderDesc[0].OGL.Header.TextureSize.w = _leftTexture->getWidth();
_eyeRenderDesc[0].OGL.Header.TextureSize.h = _leftTexture->getHeight();
_eyeRenderDesc[1].OGL.Header.TextureSize.w = _rightTexture->getWidth();
_eyeRenderDesc[1].OGL.Header.TextureSize.h = _rightTexture->getHeight();
_eyeRenderDesc[0].OGL.Header.RenderViewport.Size.w = _leftTexture->getWidth();
_eyeRenderDesc[0].OGL.Header.RenderViewport.Size.h = _leftTexture->getHeight();
_eyeRenderDesc[1].OGL.Header.RenderViewport.Size.w = _rightTexture->getWidth();
_eyeRenderDesc[1].OGL.Header.RenderViewport.Size.h = _rightTexture->getHeight();
_eyeRenderDesc[0].OGL.TexId = _leftTexture->getObjectName();
_eyeRenderDesc[1].OGL.TexId = _rightTexture->getObjectName();
}
#ifdef ACGL_COMPILE_WITH_GLFW
bool configureRendering( ovrHmd _hmd, ovrEyeRenderDesc _eyeRenderDesc[2], glm::uvec2 _renderTagetSize, GLFWwindow *_window )
{
glBindVertexArray(0);
bool configureRendering(ovrHmd _hmd, ovrEyeRenderDesc _eyeRenderDesc[2], glm::uvec2 _renderTagetSize, GLFWwindow *_window)
{
if (!_hmd) return false;
// Update ovr Rendering configuration
ovrGLConfig cfg;
cfg.OGL.Header.API = ovrRenderAPI_OpenGL;
cfg.OGL.Header.RTSize.w = _renderTagetSize.x;
cfg.OGL.Header.RTSize.h = _renderTagetSize.y;
cfg.OGL.Header.Multisample = 0;
// Update ovr Rendering configuration
ovrGLConfig cfg;
cfg.OGL.Header.API = ovrRenderAPI_OpenGL;
cfg.OGL.Header.RTSize.w = _renderTagetSize.x;
cfg.OGL.Header.RTSize.h = _renderTagetSize.y;
cfg.OGL.Header.Multisample = 0;
#ifdef OVR_OS_WIN32
debug() << "configure Windows specifics" << std::endl;
//debug() << "configure Windows specifics" << std::endl;
cfg.OGL.Window = glfwGetWin32Window( _window );
cfg.OGL.DC = GetDC(cfg.OGL.Window);
cfg.OGL.Window = glfwGetWin32Window(_window);
cfg.OGL.DC = GetDC(cfg.OGL.Window);
#endif
#ifdef OVR_OS_MAC
debug() << "configure Mac specifics: TODO" << std::endl;
debug() << "configure Mac specifics: TODO" << std::endl;
#endif
#ifdef OVR_OS_LINUX
debug() << "configure Linux specifics: TODO" << std::endl;
debug() << "configure Linux specifics: TODO" << std::endl;
#endif
debug() << "call ovrHmd_ConfigureRendering():" << std::endl;
printOVRDistortionCaps(_hmd->DistortionCaps);
unsigned int distortionCaps = _hmd->DistortionCaps ^ ovrDistortionCap_FlipInput; // don't flip the input
//distortionCaps ^= ovrDistortionCap_NoRestore;
distortionCaps ^= ovrDistortionCap_SRGB;
printOVRDistortionCaps(distortionCaps);
ovrBool ok = ovrHmd_ConfigureRendering(_hmd, (ovrRenderAPIConfig*)&cfg, distortionCaps, _hmd->DefaultEyeFov, _eyeRenderDesc);
if (ok) {
bool riftIsUsingExtendedDesktop = (bool)(_hmd->HmdCaps & ovrHmdCap_ExtendDesktop);
if (riftIsUsingExtendedDesktop) {
debug() << "rendering on extended desktop" << endl;
} else {
debug() << "rendering in direct rendering mode" << endl;
ovrHmd_AttachToWindow(_hmd, cfg.OGL.Window, nullptr, nullptr);
}
debug() << "OVR Rendering configured" << std::endl;
}
else {
error() << "OVR Rendering failed to get configured" << std::endl;
}
return (bool)ok;
//debug() << "call ovrHmd_ConfigureRendering():" << std::endl;
//printDistortionCaps(_hmd->DistortionCaps);
unsigned int distortionCaps = _hmd->DistortionCaps ^ ovrDistortionCap_FlipInput; // don't flip the input
//distortionCaps ^= ovrDistortionCap_NoRestore;
distortionCaps ^= ovrDistortionCap_SRGB;
#ifdef OVR_OS_WIN32
ovrHmd_AttachToWindow(_hmd, cfg.OGL.Window, nullptr, nullptr);
#endif
//printDistortionCaps(distortionCaps);
ovrBool ok = ovrHmd_ConfigureRendering(_hmd, (ovrRenderAPIConfig*)&cfg, distortionCaps, _hmd->DefaultEyeFov, _eyeRenderDesc);
if (ok) {
bool riftIsUsingExtendedDesktop = (bool)(_hmd->HmdCaps & ovrHmdCap_ExtendDesktop);
//debug() << "OVR Rendering configured" << std::endl;
}
else {
error() << "OVR Rendering failed to get configured" << std::endl;
}
return (bool)ok;
}
#endif
bool deactivateHealthWarning(ovrHmd _hmd)
{
ovrHSWDisplayState displayState;
ovrHmd_GetHSWDisplayState(_hmd, &displayState);
bool deactivateHealthWarning(ovrHmd _hmd)
{
if (!_hmd) return false;
if (displayState.Displayed) {
ovrHmd_DismissHSWDisplay(_hmd);
return true;
}
ovrHSWDisplayState displayState;
ovrHmd_GetHSWDisplayState(_hmd, &displayState);
return false;
if (displayState.Displayed) {
ovrHmd_DismissHSWDisplay(_hmd);
return true;
}
// prints the distortion caps for debugging:
void printOVRDistortionCaps(unsigned int _caps) {
debug() << endl << "Distortion caps: " << _caps << " = ";
if (_caps & ovrDistortionCap_Chromatic) debug() << "Chromatic ";
if (_caps & ovrDistortionCap_TimeWarp) debug() << "TimeWarp ";
if (_caps & ovrDistortionCap_Vignette) debug() << "Vignette ";
if (_caps & ovrDistortionCap_NoRestore) debug() << "GLStateChangedBySDK ";
if (_caps & ovrDistortionCap_FlipInput) debug() << "FlipInput ";
if (_caps & ovrDistortionCap_SRGB) debug() << "sRGB_Input ";
if (_caps & ovrDistortionCap_Overdrive) debug() << "reduceDK2Artefacts ";
debug() << endl << endl;
}
return false;
}
void beginFrame(ovrHmd _hmd)
{
glFrontFace(GL_CCW); // not reset by the health warning :-(
glDepthMask(GL_TRUE); // not reset by Rift SDK :-(
ovrFrameTiming frameTiming = ovrHmd_BeginFrame(_hmd, 0);
}
// prints the distortion caps for debugging:
void printDistortionCaps(unsigned int _caps) {
debug() << endl << "Distortion caps: " << _caps << " = ";
if (_caps & ovrDistortionCap_Chromatic) debug() << "Chromatic ";
if (_caps & ovrDistortionCap_TimeWarp) debug() << "TimeWarp ";
if (_caps & ovrDistortionCap_Vignette) debug() << "Vignette ";
if (_caps & ovrDistortionCap_NoRestore) debug() << "GLStateChangedBySDK ";
if (_caps & ovrDistortionCap_FlipInput) debug() << "FlipInput ";
if (_caps & ovrDistortionCap_SRGB) debug() << "sRGB_Input ";
if (_caps & ovrDistortionCap_Overdrive) debug() << "reduceDK2Artefacts ";
debug() << endl << endl;
}
}
}
}
......
......@@ -57,7 +57,7 @@ void OculusRiftCamera::connectWithRift(ovrHmd _hmd)
// both eyes are equal,
// two texture rendering, NOT side-by-side:
glm::uvec2 renderTargetSizeForOneEye = getOptimalRenderSizePerEye(_hmd);
glm::uvec2 renderTargetSizeForOneEye = OVR::getOptimalRenderSizePerEye(_hmd);
ovrSizei ovrRenderTargetSizeForOneEye;
ovrRenderTargetSizeForOneEye.w = renderTargetSizeForOneEye.x;
ovrRenderTargetSizeForOneEye.h = renderTargetSizeForOneEye.y;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment