Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save JeroMiya/385abd497a6e282466af to your computer and use it in GitHub Desktop.
Save JeroMiya/385abd497a6e282466af to your computer and use it in GitHub Desktop.
/** @file
@brief Example program that uses the OSVR direct-to-display interface
and D3D to render a scene with low latency.
@date 2015
@author
Russ Taylor working through ReliaSolve.com for Sensics, Inc.
<http://sensics.com/osvr>
*/
// Copyright 2015 Sensics, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Internal Includes
#include <osvr/ClientKit/Context.h>
#include <osvr/ClientKit/Interface.h>
#include "osvr/RenderKit/RenderManager.h"
// Library/third-party includes
#include <windows.h>
#include <initguid.h>
#include <d3d11.h>
#include <wrl.h>
#include <DirectXMath.h>
// Standard includes
#include <iostream>
#include <string>
#include <stdlib.h> // For exit()
#include <thread>
#include <mutex>
#include <functional>
//This must come after we include <d3d11.h> so its pointer types are defined.
#include "osvr/RenderKit/GraphicsLibraryD3D11.h"
// Includes from our own directory
#include "pixelshader3d.h"
#include "vertexshader3d.h"
using namespace DirectX;
#include "D3DCube.h"
#include "D3DSimpleShader.h"
// Set to true when it is time for the application to quit.
// Handlers below that set it to true when the user causes
// any of a variety of events so that we shut down the system
// cleanly. This only works on Windows, but so does D3D...
static bool quit = false;
static Cube roomCube(5.0f, true);
static SimpleShader simpleShader;
#ifdef _WIN32
// Note: On Windows, this runs in a different thread from
// the main application.
static BOOL CtrlHandler(DWORD fdwCtrlType)
{
switch (fdwCtrlType)
{
// Handle the CTRL-C signal.
case CTRL_C_EVENT:
// CTRL-CLOSE: confirm that the user wants to exit.
case CTRL_CLOSE_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT:
quit = true;
return TRUE;
default:
return FALSE;
}
}
#endif
// This callback sets a boolean value whose pointer is passed in to
// the state of the button that was pressed. This lets the callback
// be used to handle any button press that just needs to update state.
void myButtonCallback(void *userdata, const OSVR_TimeValue * /*timestamp*/,
const OSVR_ButtonReport *report)
{
bool *result = static_cast<bool*>(userdata);
*result = (report->state != 0);
}
// Callbacks to draw things in world space, left-hand space, and right-hand
// space.
void RenderView(
const osvr::renderkit::RenderInfo &renderInfo //< Info needed to render
, ID3D11RenderTargetView *renderTargetView
, ID3D11DepthStencilView *depthStencilView
, ID3D11Device *device
, ID3D11DeviceContext *context
)
{
// Make sure our pointers are filled in correctly. The config file selects
// the graphics library to use, and may not match our needs.
if (renderInfo.library.D3D11 == nullptr) {
std::cerr << "SetupDisplay: No D3D11 GraphicsLibrary, this should not happen" << std::endl;
return;
}
//auto context = renderInfo.library.D3D11->context;
//auto device = renderInfo.library.D3D11->device;
float projectionD3D[16];
float viewD3D[16];
XMMATRIX identity = XMMatrixIdentity();
// Set up to render to the textures for this eye
context->OMSetRenderTargets(1, &renderTargetView, depthStencilView);
// Set up the viewport we're going to draw into.
CD3D11_VIEWPORT viewport(
static_cast<float>(renderInfo.viewport.left),
static_cast<float>(renderInfo.viewport.lower),
static_cast<float>(renderInfo.viewport.width),
static_cast<float>(renderInfo.viewport.height));
context->RSSetViewports(1, &viewport);
// Make a grey background
FLOAT colorRgba[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
context->ClearRenderTargetView(renderTargetView, colorRgba);
context->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
osvr::renderkit::OSVR_PoseState_to_D3D(viewD3D, renderInfo.pose);
osvr::renderkit::OSVR_Projection_to_D3D(projectionD3D, renderInfo.projection);
XMMATRIX _projectionD3D(projectionD3D), _viewD3D(viewD3D);
// draw room
simpleShader.use(device, context, _projectionD3D, _viewD3D, identity);
roomCube.draw(device, context);
}
void Usage(std::string name)
{
std::cerr << "Usage: " << name << std::endl;
exit(-1);
}
class RenderManagerThread
{
private:
std::shared_ptr<osvr::renderkit::RenderManager> mRenderManager = nullptr;
std::mutex mLock;
std::shared_ptr<std::thread> mThread = nullptr;
osvr::clientkit::ClientContext *mContext;
bool mQuit = false;
bool mHaveBuffer = false;
bool mStarted = false;
std::vector<osvr::renderkit::RenderBuffer> mRenderBuffers;
std::vector<IDXGIKeyedMutex *> mKeyedMutexes;
std::vector<osvr::renderkit::RenderInfo> *mRenderInfo;
public:
RenderManagerThread(osvr::clientkit::ClientContext *context) {
mContext = context;
// Open Direct3D and set up the context for rendering to
// an HMD. Do this using the OSVR RenderManager interface,
// which maps to the nVidia or other vendor direct mode
// to reduce the latency.
mRenderManager.reset(
osvr::renderkit::createRenderManager(context->get(), "Direct3D11"));
if (mRenderManager == nullptr || !mRenderManager->doingOkay()) {
std::cerr << "Could not create RenderManager" << std::endl;
throw std::exception("Could not create RenderManager");
}
// Open the display and make sure this worked.
osvr::renderkit::RenderManager::OpenResults ret = mRenderManager->OpenDisplay();
if (ret.status == osvr::renderkit::RenderManager::OpenStatus::FAILURE) {
std::cerr << "Could not open display" << std::endl;
throw std::exception("Could not open display");
}
std::cerr << "Successfully opened direct mode display." << std::endl;
if (ret.library.D3D11 == nullptr) {
std::cerr << "Attempted to run a Direct3D11 program with a config file "
<< "that specified a different rendering library." << std::endl;
throw std::exception("Attempted to run a Direct3D11 program with a config file "
"that specified a different rendering library.");
}
}
~RenderManagerThread() {
if (mThread) {
stop();
mThread->join();
}
}
std::vector<osvr::renderkit::RenderInfo> GetRenderInfo() {
std::lock_guard<std::mutex> lock(mLock);
return mRenderManager->GetRenderInfo();
}
bool RegisterRenderBuffers(
std::vector<HANDLE> &sharedHandles,
bool appWillNotOverwriteBeforeNewPresent = false) {
if (mRenderBuffers.size() > 0) {
std::cerr << "Only call RenderManagerThread::RegisterRenderBuffers once." << std::endl;
return false;
}
std::vector<osvr::renderkit::RenderInfo> renderInfo = GetRenderInfo();
for (size_t i = 0; i < sharedHandles.size(); i++) {
auto device = renderInfo[i].library.D3D11->device;
ID3D11Texture2D *texture2D = nullptr;
auto hr = device->OpenSharedResource(sharedHandles[i], __uuidof(ID3D11Texture2D),
(LPVOID*)&texture2D);
if (FAILED(hr)) {
std::cerr << "RenderManagerThread::RegisterRenderBuffers() - failed to open shared resource." << std::endl;
return false;
}
IDXGIKeyedMutex *dxgiKeyedMutex = nullptr;
hr = texture2D->QueryInterface(__uuidof(IDXGIKeyedMutex), (LPVOID*)&dxgiKeyedMutex);
if (FAILED(hr) || dxgiKeyedMutex == nullptr) {
std::cerr << "RenderManagerThread::RegisterRenderBuffers() - failed to create keyed mutex." << std::endl;
return false;
}
mKeyedMutexes.push_back(dxgiKeyedMutex);
// Fill in the resource view for your render texture buffer here
D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc;
memset(&renderTargetViewDesc, 0, sizeof(renderTargetViewDesc));
// This must match what was created in the texture to be rendered
// @todo Figure this out by introspection on the texture?
//renderTargetViewDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
renderTargetViewDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
renderTargetViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
renderTargetViewDesc.Texture2D.MipSlice = 0;
// Create the render target view.
ID3D11RenderTargetView *renderTargetView; //< Pointer to our render target view
hr = renderInfo[i].library.D3D11->device->CreateRenderTargetView(
texture2D, &renderTargetViewDesc, &renderTargetView);
if (FAILED(hr)) {
std::cerr << "Could not create render target for eye " << i
<< std::endl;
return false;
}
osvr::renderkit::RenderBuffer buffer;
buffer.D3D11 = new osvr::renderkit::RenderBufferD3D11();
buffer.D3D11->colorBuffer = texture2D;
buffer.D3D11->colorBufferView = renderTargetView;
mRenderBuffers.push_back(buffer);
}
return mRenderManager->RegisterRenderBuffers(mRenderBuffers, appWillNotOverwriteBeforeNewPresent);
}
void PresentRenderBuffers(std::vector<osvr::renderkit::RenderInfo> *renderInfo) {
std::lock_guard<std::mutex> lock(mLock);
if (mHaveBuffer) {
std::cerr << "ReaderManagerThread::PresentRenderBuffers called multiple times before frame processed."
<< " A frame may be missed." << std::endl;
}
mHaveBuffer = true;
mRenderInfo = renderInfo;
}
void start() {
if (mStarted) {
std::cerr << "RenderManagerThread::start() - thread loop already started." << std::endl;
} else {
mThread.reset(new std::thread(std::bind(&RenderManagerThread::threadFunc, this)));
}
}
void stop() {
std::lock_guard<std::mutex> lock(mLock);
if (!mStarted) {
std::cerr << "RenderManagerThread::stop() - thread loop not already started." << std::endl;
}
mQuit = true;
}
private:
bool getQuit() {
std::lock_guard<std::mutex> lock(mLock);
return mQuit;
}
void threadFunc() {
bool quit = getQuit();
while (!quit) {
std::lock_guard<std::mutex> lock(mLock);
if (mHaveBuffer) {
// Update the context so we get our callbacks called and
// update tracker state.
mContext->update();
UINT acqKey = 1;
UINT relKey = 0;
DWORD hr;
for (size_t i = 0; i < mRenderBuffers.size(); i++) {
auto keyedMutex = mKeyedMutexes[i];
hr = keyedMutex->AcquireSync(acqKey, INFINITE);
if (FAILED(hr)) {
std::cerr << "Could not AcquireSync in render manager thread." << std::endl;
throw std::runtime_error("Could not AcquireSync in render manager thread.");
}
}
// Send the rendered results to the screen
if (!mRenderManager->PresentRenderBuffers(mRenderBuffers, *mRenderInfo)) {
std::cerr << "PresentRenderBuffers() returned false, maybe because it was asked to quit" << std::endl;
mQuit = true;
}
for (size_t i = 0; i < mRenderBuffers.size(); i++) {
auto keyedMutex = mKeyedMutexes[i];
hr = keyedMutex->ReleaseSync(relKey);
if (FAILED(hr)) {
std::cerr << "Could not ReleaseSync in the render manager thread." << std::endl;
throw std::runtime_error("Could not ReleaseSync in the render manager thread.");
}
}
mHaveBuffer = false;
}
quit = mQuit;
}
}
};
int main(int argc, char *argv[])
{
// Parse the command line
int realParams = 0;
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
Usage(argv[0]);
}
else switch (++realParams) {
case 1:
default:
Usage(argv[0]);
}
}
if (realParams != 0) { Usage(argv[0]); }
// Get an OSVR client context to use to access the devices
// that we need.
osvr::clientkit::ClientContext context(
"org.opengoggles.exampleclients.TrackerCallback");
//// Construct button devices and connect them to a callback
//// that will set the "quit" variable to true when it is
//// pressed. Use button "1" on the left-hand or
//// right-hand controller.
//osvr::clientkit::Interface leftButton1 =
// context.getInterface("/controller/left/1");
//leftButton1.registerCallback(&myButtonCallback, &quit);
//osvr::clientkit::Interface rightButton1 =
// context.getInterface("/controller/right/1");
//rightButton1.registerCallback(&myButtonCallback, &quit);
// Create a D3D11 device and context to be used, rather than
// having RenderManager make one for us. This is an example
// of using an external one, which would be needed for clients
// that already have a rendering pipeline, like Unity.
ID3D11Device *myDevice = nullptr; // Fill this in
ID3D11DeviceContext *myContext = nullptr; // Fill this in.
// Here, we open the device and context ourselves, but if you
// are working with a render library that provides them for you,
// just stick them into the values rather than constructing
// them. (This is a bit of a toy example, because we could
// just let RenderManager do this work for us and use the library
// it sends back. However, it does let us set parameters on the
// device and context construction the way that we want, so it
// might be useful. Be sure to get D3D11 and have set
// D3D11_CREATE_DEVICE_BGRA_SUPPORT in the device/context
// creation, however it is done).
D3D_FEATURE_LEVEL acceptibleAPI = D3D_FEATURE_LEVEL_11_0;
D3D_FEATURE_LEVEL foundAPI;
auto hr = D3D11CreateDevice(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
&acceptibleAPI, 1, D3D11_SDK_VERSION, &myDevice,
&foundAPI, &myContext);
if (FAILED(hr)) {
std::cerr << "Could not create D3D11 device and context" << std::endl;
return -1;
}
// Set up a handler to cause us to exit cleanly.
#ifdef _WIN32
SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
#endif
RenderManagerThread renderThread(&context);
// Do a call to get the information we need to construct our
// color and depth render-to-texture buffers.
std::vector<osvr::renderkit::RenderInfo> renderInfo;
context.update();
renderInfo = renderThread.GetRenderInfo();
// Set up the vector of textures to render to and any framebuffer
// we need to group them.
std::vector<osvr::renderkit::RenderBuffer> renderBuffers;
std::vector<ID3D11Texture2D *> depthStencilTextures;
std::vector<ID3D11DepthStencilView *> depthStencilViews;
std::vector<IDXGIKeyedMutex *> dxgiKeyedMutexes;
std::vector<HANDLE> sharedHandles;
for (size_t i = 0; i < renderInfo.size(); i++) {
// The color buffer for this eye. We need to put this into
// a generic structure for the Present function, but we only need
// to fill in the Direct3D portion.
// Note that this texture format must be RGBA and unsigned byte,
// so that we can present it to Direct3D for DirectMode.
ID3D11Texture2D *D3DTexture = nullptr;
unsigned width = static_cast<int>(renderInfo[i].viewport.width);
unsigned height = static_cast<int>(renderInfo[i].viewport.height);
// Initialize a new render target texture description.
D3D11_TEXTURE2D_DESC textureDesc;
memset(&textureDesc, 0, sizeof(textureDesc));
textureDesc.Width = width;
textureDesc.Height = height;
textureDesc.MipLevels = 1;
textureDesc.ArraySize = 1;
//textureDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Usage = D3D11_USAGE_DEFAULT;
// We need it to be both a render target and a shader resource
textureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
textureDesc.CPUAccessFlags = 0;
textureDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
// Create a new render target texture to use.
hr = renderInfo[i].library.D3D11->device->CreateTexture2D(
&textureDesc, NULL, &D3DTexture);
if (FAILED(hr)) {
std::cerr << "Can't create texture for eye " << i << std::endl;
return -1;
}
IDXGIResource* dxgiResource = NULL;
hr = D3DTexture->QueryInterface(__uuidof(IDXGIResource), (LPVOID*)&dxgiResource);
if (FAILED(hr)) {
std::cerr << "Can't get the IDXGIResource for the texture resource." << std::endl;
return -1;
}
HANDLE sharedHandle;
hr = dxgiResource->GetSharedHandle(&sharedHandle);
if (FAILED(hr)) {
std::cerr << "Can't get the shared handle from the dxgiResource." << std::endl;
return -1;
}
dxgiResource->Release();
sharedHandles.push_back(sharedHandle);
IDXGIKeyedMutex* dxgiKeyedMutex = nullptr;
hr = D3DTexture->QueryInterface(__uuidof(IDXGIKeyedMutex), (LPVOID*)&dxgiKeyedMutex);
if (FAILED(hr) || dxgiKeyedMutex == nullptr) {
std::cerr << "Can't get the IDXGIKeyedMutex from the texture resource." << std::endl;
return -1;
}
dxgiKeyedMutexes.push_back(dxgiKeyedMutex);
// Fill in the resource view for your render texture buffer here
D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc;
memset(&renderTargetViewDesc, 0, sizeof(renderTargetViewDesc));
// This must match what was created in the texture to be rendered
// @todo Figure this out by introspection on the texture?
//renderTargetViewDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
renderTargetViewDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
renderTargetViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
renderTargetViewDesc.Texture2D.MipSlice = 0;
// Create the render target view.
ID3D11RenderTargetView *renderTargetView; //< Pointer to our render target view
hr = renderInfo[i].library.D3D11->device->CreateRenderTargetView(
D3DTexture, &renderTargetViewDesc, &renderTargetView);
if (FAILED(hr)) {
std::cerr << "Could not create render target for eye " << i
<< std::endl;
return -2;
}
// Push the filled-in RenderBuffer onto the stack.
osvr::renderkit::RenderBufferD3D11 *rbD3D = new osvr::renderkit::RenderBufferD3D11;
rbD3D->colorBuffer = D3DTexture;
rbD3D->colorBufferView = renderTargetView;
osvr::renderkit::RenderBuffer rb;
rb.D3D11 = rbD3D;
renderBuffers.push_back(rb);
//==================================================================
// Create a depth buffer
// Make the depth/stencil texture.
D3D11_TEXTURE2D_DESC textureDescription = { 0 };
textureDescription.SampleDesc.Count = 1;
textureDescription.SampleDesc.Quality = 0;
textureDescription.Usage = D3D11_USAGE_DEFAULT;
textureDescription.BindFlags = D3D11_BIND_DEPTH_STENCIL;
textureDescription.Width = width;
textureDescription.Height = height;
textureDescription.MipLevels = 1;
textureDescription.ArraySize = 1;
textureDescription.CPUAccessFlags = 0;
textureDescription.MiscFlags = 0;
/// @todo Make this a parameter
textureDescription.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
ID3D11Texture2D *depthStencilBuffer;
hr = renderInfo[i].library.D3D11->device->CreateTexture2D(
&textureDescription, NULL, &depthStencilBuffer);
if (FAILED(hr)) {
std::cerr << "Could not create depth/stencil texture for eye "
<< i << std::endl;
return -4;
}
depthStencilTextures.push_back(depthStencilBuffer);
// Create the depth/stencil view description
D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDescription;
memset(&depthStencilViewDescription, 0, sizeof(depthStencilViewDescription));
depthStencilViewDescription.Format = textureDescription.Format;
depthStencilViewDescription.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
depthStencilViewDescription.Texture2D.MipSlice = 0;
ID3D11DepthStencilView *depthStencilView;
hr = renderInfo[i].library.D3D11->device->CreateDepthStencilView(
depthStencilBuffer,
&depthStencilViewDescription,
&depthStencilView);
if (FAILED(hr)) {
std::cerr << "Could not create depth/stencil view for eye "
<< i << std::endl;
return -5;
}
depthStencilViews.push_back(depthStencilView);
}
// Create depth stencil state.
// Describe how depth and stencil tests should be performed.
D3D11_DEPTH_STENCIL_DESC depthStencilDescription = { 0 };
depthStencilDescription.DepthEnable = true;
depthStencilDescription.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthStencilDescription.DepthFunc = D3D11_COMPARISON_LESS;
depthStencilDescription.StencilEnable = true;
depthStencilDescription.StencilReadMask = 0xFF;
depthStencilDescription.StencilWriteMask = 0xFF;
// Front-facing stencil operations
depthStencilDescription.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDescription.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
depthStencilDescription.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDescription.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
// Back-facing stencil operations
depthStencilDescription.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDescription.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
depthStencilDescription.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDescription.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
ID3D11DepthStencilState *depthStencilState;
hr = renderInfo[0].library.D3D11->device->CreateDepthStencilState(
&depthStencilDescription,
&depthStencilState);
if (FAILED(hr)) {
std::cerr << "Could not create depth/stencil state"
<< std::endl;
return -3;
}
// Register our constructed buffers so that we can use them for
// presentation.
if (!renderThread.RegisterRenderBuffers(sharedHandles)) {
std::cerr << "RegisterRenderBuffers() returned false, cannot continue" << std::endl;
quit = true;
}
renderThread.start();
// Continue rendering until it is time to quit.
while (!quit) {
// Update the context so we get our callbacks called and
// update tracker state.
//context.update();
renderInfo = renderThread.GetRenderInfo();
// Render into each buffer using the specified information.
for (size_t i = 0; i < renderInfo.size(); i++) {
auto keyedMutex = dxgiKeyedMutexes[i];
renderInfo[i].library.D3D11->context->OMSetDepthStencilState(depthStencilState, 1);
UINT acqKey = 0;
UINT relKey = 1;
hr = keyedMutex->AcquireSync(acqKey, INFINITE);
if (FAILED(hr)) {
std::cerr << "Could not AcquireSync in game render thread." << std::endl;
return -4;
}
RenderView(renderInfo[i], renderBuffers[i].D3D11->colorBufferView,
depthStencilViews[i], myDevice, myContext);
hr = keyedMutex->ReleaseSync(relKey);
if (FAILED(hr)) {
std::cerr << "Could not ReleaseSync in the game render thread." << std::endl;
return -5;
}
}
// Send the rendered results to the screen
renderThread.PresentRenderBuffers(&renderInfo);
}
renderThread.stop();
// Clean up after ourselves.
myContext->Release();
myDevice->Release();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment