329 lines
11 KiB
C++

/********************************************************************************
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
********************************************************************************/
#include "GraphicsCaptureHook.h"
#include <D3D10_1.h>
#include <D3D11.h>
#include "DXGIStuff.h"
FARPROC oldD3D11Release = NULL;
FARPROC newD3D11Release = NULL;
CaptureInfo d3d11CaptureInfo;
extern LPVOID lpCurrentSwap;
extern LPVOID lpCurrentDevice;
SharedTexData *texData;
extern DWORD curCapture;
extern BOOL bHasTextures;
extern BOOL bIsMultisampled;
extern LONGLONG lastTime;
extern DXGI_FORMAT dxgiFormat;
ID3D11Resource *copyTextureGame = NULL;
HANDLE sharedHandle = NULL;
extern bool bD3D101Hooked;
void ClearD3D11Data()
{
bHasTextures = false;
texData = NULL;
sharedHandle = NULL;
SafeRelease(copyTextureGame);
DestroySharedMemory();
keepAliveTime = 0;
resetCount++;
logOutput << CurrentTimeString() << "---------------------- Cleared D3D11 Capture ----------------------" << endl;
}
void SetupD3D11(IDXGISwapChain *swapChain)
{
logOutput << CurrentTimeString() << "setting up d3d11 data" << endl;
ClearD3D11Data();
DXGI_SWAP_CHAIN_DESC scd;
if(SUCCEEDED(swapChain->GetDesc(&scd)))
{
d3d11CaptureInfo.format = ConvertGIBackBufferFormat(scd.BufferDesc.Format);
if(d3d11CaptureInfo.format != GS_UNKNOWNFORMAT)
{
if( dxgiFormat != scd.BufferDesc.Format ||
d3d11CaptureInfo.cx != scd.BufferDesc.Width ||
d3d11CaptureInfo.cy != scd.BufferDesc.Height ||
d3d11CaptureInfo.hwndCapture != (DWORD)scd.OutputWindow)
{
dxgiFormat = FixCopyTextureFormat(scd.BufferDesc.Format);
d3d11CaptureInfo.cx = scd.BufferDesc.Width;
d3d11CaptureInfo.cy = scd.BufferDesc.Height;
d3d11CaptureInfo.hwndCapture = (DWORD)scd.OutputWindow;
bIsMultisampled = scd.SampleDesc.Count > 1;
logOutput << CurrentTimeString() << "found dxgi format (dx11) of: " << UINT(dxgiFormat) <<
", size: {" << scd.BufferDesc.Width << ", " << scd.BufferDesc.Height <<
"}, multisampled: " << (bIsMultisampled ? "true" : "false") << endl;
}
}
}
lastTime = 0;
OSInitializeTimer();
}
typedef HRESULT (WINAPI *CREATEDXGIFACTORY1PROC)(REFIID riid, void **ppFactory);
bool DoD3D11Hook(ID3D11Device *device)
{
HRESULT hErr;
D3D11_TEXTURE2D_DESC texGameDesc;
ZeroMemory(&texGameDesc, sizeof(texGameDesc));
texGameDesc.Width = d3d11CaptureInfo.cx;
texGameDesc.Height = d3d11CaptureInfo.cy;
texGameDesc.MipLevels = 1;
texGameDesc.ArraySize = 1;
texGameDesc.Format = dxgiFormat;
texGameDesc.SampleDesc.Count = 1;
texGameDesc.BindFlags = D3D11_BIND_RENDER_TARGET|D3D11_BIND_SHADER_RESOURCE;
texGameDesc.Usage = D3D11_USAGE_DEFAULT;
texGameDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
ID3D11Texture2D *d3d11Tex;
if(FAILED(hErr = device->CreateTexture2D(&texGameDesc, NULL, &d3d11Tex)))
{
RUNEVERYRESET logOutput << CurrentTimeString() << "DoD3D11Hook: creation of intermediary texture failed, result = " << UINT(hErr) << endl;
return false;
}
if(FAILED(hErr = d3d11Tex->QueryInterface(__uuidof(ID3D11Resource), (void**)&copyTextureGame)))
{
RUNEVERYRESET logOutput << CurrentTimeString() << "DoD3D11Hook: d3d11Tex->QueryInterface(ID3D11Resource) failed, result = " << UINT(hErr) << endl;
d3d11Tex->Release();
return false;
}
IDXGIResource *res;
if(FAILED(hErr = d3d11Tex->QueryInterface(IID_IDXGIResource, (void**)&res)))
{
RUNEVERYRESET logOutput << CurrentTimeString() << "DoD3D11Hook: d3d11Tex->QueryInterface(IID_IDXGIResource) failed, result = " << UINT(hErr) << endl;
d3d11Tex->Release();
return false;
}
if(FAILED(hErr = res->GetSharedHandle(&sharedHandle)))
{
RUNEVERYRESET logOutput << CurrentTimeString() << "DoD3D11Hook: res->GetSharedHandle failed, result = " << UINT(hErr) << endl;
d3d11Tex->Release();
res->Release();
return false;
}
d3d11Tex->Release();
res->Release();
return true;
}
UINT STDMETHODCALLTYPE D3D11DeviceReleaseHook(ID3D10Device *device)
{
/*device->AddRef();
ULONG refVal = (*(RELEASEPROC)oldD3D11Release)(device);
if(bHasTextures)
{
if(refVal == 8) //our two textures are holding the reference up, so always clear at 3
{
ClearD3D11Data();
lpCurrentDevice = NULL;
bTargetAcquired = false;
}
}
else if(refVal == 1)
{
lpCurrentDevice = NULL;
bTargetAcquired = false;
}*/
ULONG refVal = (*(RELEASEPROC)oldD3D11Release)(device);
return refVal;
}
void DoD3D11Capture(IDXGISwapChain *swap)
{
HRESULT hRes;
ID3D11Device *device = NULL;
if(SUCCEEDED(hRes = swap->GetDevice(__uuidof(ID3D11Device), (void**)&device)))
{
if(bCapturing && WaitForSingleObject(hSignalEnd, 0) == WAIT_OBJECT_0)
bStopRequested = true;
if(bCapturing && !IsWindow(hwndOBS))
{
hwndOBS = NULL;
bStopRequested = true;
}
if(!lpCurrentDevice)
{
lpCurrentDevice = device;
/*FARPROC curRelease = GetVTable(device, (8/4));
if(curRelease != newD3D11Release)
{
oldD3D11Release = curRelease;
newD3D11Release = (FARPROC)DeviceReleaseHook;
SetVTable(device, (8/4), newD3D11Release);
}*/
}
ID3D11DeviceContext *context;
device->GetImmediateContext(&context);
if(bCapturing && bStopRequested)
{
RUNEVERYRESET logOutput << CurrentTimeString() << "stop requested, terminating d3d11 capture" << endl;
ClearD3D11Data();
bCapturing = false;
bStopRequested = false;
}
if(!bCapturing && WaitForSingleObject(hSignalRestart, 0) == WAIT_OBJECT_0)
{
hwndOBS = FindWindow(OBS_WINDOW_CLASS, NULL);
if(hwndOBS)
bCapturing = true;
}
if(!bHasTextures && bCapturing)
{
if(dxgiFormat && hwndOBS)
{
BOOL bSuccess = DoD3D11Hook(device);
if(bSuccess)
{
d3d11CaptureInfo.mapID = InitializeSharedMemoryGPUCapture(&texData);
if(!d3d11CaptureInfo.mapID)
{
RUNEVERYRESET logOutput << CurrentTimeString() << "SwapPresentHook: creation of shared memory failed" << endl;
bSuccess = false;
}
}
if(bSuccess)
{
bHasTextures = true;
d3d11CaptureInfo.captureType = CAPTURETYPE_SHAREDTEX;
d3d11CaptureInfo.bFlip = FALSE;
texData->texHandle = (DWORD)sharedHandle;
memcpy(infoMem, &d3d11CaptureInfo, sizeof(CaptureInfo));
SetEvent(hSignalReady);
logOutput << CurrentTimeString() << "DoD3D11Hook: success" << endl;
}
else
{
ClearD3D11Data();
}
}
}
LONGLONG timeVal = OSGetTimeMicroseconds();
//check keep alive state, dumb but effective
if(bCapturing)
{
if (!keepAliveTime)
keepAliveTime = timeVal;
if((timeVal-keepAliveTime) > 5000000)
{
HANDLE hKeepAlive = OpenEvent(EVENT_ALL_ACCESS, FALSE, strKeepAlive.c_str());
if (hKeepAlive) {
CloseHandle(hKeepAlive);
} else {
ClearD3D11Data();
logOutput << CurrentTimeString() << "Keepalive no longer found on d3d11, freeing capture data" << endl;
bCapturing = false;
}
keepAliveTime = timeVal;
}
}
if(bHasTextures)
{
LONGLONG frameTime;
if(bCapturing)
{
if(texData)
{
if(frameTime = texData->frameTime)
{
LONGLONG timeElapsed = timeVal-lastTime;
if(timeElapsed >= frameTime)
{
lastTime += frameTime;
if(timeElapsed > frameTime*2)
lastTime = timeVal;
DWORD nextCapture = curCapture == 0 ? 1 : 0;
ID3D11Resource *backBuffer = NULL;
if(SUCCEEDED(hRes = swap->GetBuffer(0, IID_ID3D11Resource, (void**)&backBuffer)))
{
if(bIsMultisampled)
context->ResolveSubresource(copyTextureGame, 0, backBuffer, 0, dxgiFormat);
else
context->CopyResource(copyTextureGame, backBuffer);
RUNEVERYRESET logOutput << CurrentTimeString() << "successfully capturing d3d11 frames via GPU" << endl;
backBuffer->Release();
} else {
RUNEVERYRESET logOutput << CurrentTimeString() << "DoD3D11Capture: swap->GetBuffer failed: result = " << UINT(hRes) << endl;
}
curCapture = nextCapture;
}
}
}
} else {
RUNEVERYRESET logOutput << CurrentTimeString() << "no longer capturing, terminating d3d11 capture" << endl;
ClearD3D11Data();
}
}
device->Release();
context->Release();
}
}