I have a Direct2D app that I am making, and I am writing a Direct2D library that makes using Direct2D easier for me as well. I'll post the exact problematic code if I need to, but my main issue is that I have an ID2D1HwndRenderTarget in the definition of one class, I extend that class with another class, in the child class I have a method that calls a method of the parent class that initializes the Render Target, and then in turn calls the load method of the child class. However, as soon as the program reaches the load content method of the child class, the __vfptr variable (I don't have a clue what that is) in the IUnknown portion of the ID2D1HwndRenderTarget is now null. The only reason I figured this out is in some other code I was getting an access violation error when using the render target to create a ID2D1Bitmap from an IWicBitmapSource. I don't understand how this happens because after initializing the Render Target, that _vfptr variable becomes null as soon as the method with the initialization code returns. Can anyone explain why this may be happening? My relevant code is below.
This code is called once to create the hwnd render target and the offscreen render target. This is in a dll project.
GameBase.cpp
HRESULT GameBase::Initialize(HINSTANCE hInst, HWND winHandle, struct DX2DInitOptions options)
{
this->mainRenderTarget = NULL;
this->offscreenRendTarget = NULL;
this->factory = NULL;
HRESULT result;
D2D1_FACTORY_OPTIONS factOptions;
D2D1_FACTORY_TYPE factType;
if(options.enableDebugging)
factOptions.debugLevel = D2D1_DEBUG_LEVEL::D2D1_DEBUG_LEVEL_ERROR;
else
factOptions.debugLevel = D2D1_DEBUG_LEVEL::D2D1_DEBUG_LEVEL_NONE;
if(options.singleThreadedApp)
factType = D2D1_FACTORY_TYPE_SINGLE_THREADED;
else
factType = D2D1_FACTORY_TYPE_MULTI_THREADED;
result = D2D1CreateFactory(factType, factOptions, &this->factory);
if(FAILED(result))
{
OutputDebugString(L"Failed to create a Direct 2D Factory!");
return result;
}
this->instance = hInst;
this->hwnd = winHandle;
D2D1_SIZE_U size = D2D1::SizeU(options.winWidth, options.winHeight);
this->width = options.winWidth;
this->height = options.winHeight;
result = factory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(winHandle, size), &this->mainRenderTarget);
if(FAILED(result))
{
OutputDebugString(L"Failed to create a render target to draw to the window with!");
return result;
}
result = this->mainRenderTarget->CreateCompatibleRenderTarget(&this->offscreenRendTarget);
if(FAILED(result))
{
OutputDebugString(L"Failed to create an offscreen render target from the main render target.");
return result;
}
return LoadContent();
}
After the call to LoadContent, at no point in time do I change the value of the mainRenderTarget.
DX2DImage.cpp
HRESULT DX2DImageLoader::LoadFromResource(LPCWSTR resourceName, LPCWSTR resourceType, HMODULE progModule, DX2DImage* image)
{
if(!this->isInit)
{
OutputDebugStringA("You must call InitializeImageLoader before using this image loader!");
return E_FAIL;
}
IWICBitmapDecoder *decoder = NULL;
IWICBitmapFrameDecode *source = NULL;
IWICStream *stream = NULL;
IWICFormatConverter *converter = NULL;
HRSRC imageResHandle = NULL;
HGLOBAL imageResDataHandle = NULL;
void *imageFile = NULL;
DWORD imageFileSize = 0;
HRESULT result;
//Find the image.
imageResHandle = FindResource(progModule, resourceName, resourceType);
if(!imageResHandle)
{
OutputDebugStringA("Failed to get a handle to the resource!");
return E_FAIL;
}
//Load the data handle of the image.
imageResDataHandle = LoadResource(progModule, imageResHandle);
if(!imageResDataHandle)
{
OutputDebugStringA("Failed to load the image from the module!");
return E_FAIL;
}
//Lock and retrieve the image.
imageFile = LockResource(imageResDataHandle);
if(!imageFile)
{
OutputDebugStringA("Failed to lock the image in the module!");
return E_FAIL;
}
//Get the size of the image.
imageFileSize = SizeofResource(progModule, imageResHandle);
if(!imageFileSize)
{
OutputDebugStringA("Failed to retrieve the size of the image in the module!");
return E_FAIL;
}
//Create a stream that will read the image data.
result = this->factory->CreateStream(&stream);
if(FAILED(result))
{
OutputDebugStringA("Failed to create an IWICStream!");
return result;
}
//Open a stream to the image.
result = stream->InitializeFromMemory(reinterpret_cast<BYTE*>(imageFile), imageFileSize);
if(FAILED(result))
{
OutputDebugStringA("Failed to initialize the stream!");
return result;
}
//Create a decoder from the stream
result = this->factory->CreateDecoderFromStream(stream, NULL, WICDecodeMetadataCacheOnDemand, &decoder);
if(FAILED(result))
{
OutputDebugStringA("Failed to create a decoder from the stream!");
return result;
}
//Get the first frame from the image.
result = decoder->GetFrame(0, &source);
if(FAILED(result))
{
OutputDebugStringA("Failed to get the first frame from the decoder!");
return result;
}
//Create a format converter to convert image to 32bppPBGRA
result = this->factory->CreateFormatConverter(&converter);
if(FAILED(result))
{
OutputDebugStringA("Failed to create a format converter!");
return result;
}
//Convert the image to the new format.
result = converter->Initialize(source, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.0f, WICBitmapPaletteTypeMedianCut);
if(FAILED(result))
{
OutputDebugStringA("Failed to convert the image to the correct format!");
return result;
}
//Create the Direct2D Bitmap from the Wic Bitmap.
result = this->renderTarget->CreateBitmapFromWicBitmap(converter, NULL, &image->bitmap);
if(FAILED(result))
{
OutputDebugStringA("Failed to create a Direct 2D Bitmap from a WIC Bitmap!");
return result;
}
image->width = static_cast<UINT>(image->bitmap->GetSize().width);
image->height = static_cast<UINT>(image->bitmap->GetSize().height);
SafeRelease(&source);
SafeRelease(&converter);
SafeRelease(&decoder);
SafeRelease(&stream);
return S_OK;
}
The Access Violation exception occurs on the line
result = this->renderTarget->CreateBitmapFromWicBitmap(converter, NULL, &image->bitmap);
where image->bitmap is a currently NULL (Like it's supposed to be) ID2D1Bitmap.
Here, the renderTarget variable is the same mainRenderTarget variable from GameBase.cpp above. When I debug the line, all the parents of the RenderTarget are not null, however once I get to the IUnknown interface under it all, the _vfptr thing is null. This is not the case with the converter variable, this variable, or image variable.
I don't have enough code to debug your code, but from what I see I suspect the call to converter->Initialize(...) be invalid since MSDN say:
If you do not have a predefined palette, you must first create one. Use
InitializeFromBitmap to create the palette object, then pass it in along
with your other parameters.
dither, pIPalette, alphaThresholdPercent, and paletteTranslate are used to
mitigate color loss when converting to a reduced bit-depth format. For
conversions that do not need these settings, the following parameters values
should be used: dither set to WICBitmapDitherTypeNone, pIPalette set to NULL,
alphaThresholdPercent set to 0.0f, and paletteTranslate set to
WICBitmapPaletteTypeCustom.
And in your code you do not provide a valid pallete(you used NULL) and your paletteTranslate is not WICBitmapPaletteTypeCustom
Related
I'm building an application that is used for taking and sharing screenshots in real time between multiple clients over network.
I'm using the MS Desktop Duplication API to get the image data and it's working smoothly except in some edge cases.
I have been using four games as test applications in order to test how the screencapture behaves in fullscreen and they are Heroes of the Storm, Rainbow Six Siege, Counter Strike and PlayerUnknown's Battlegrounds.
On my own machine which has a GeForce GTX 1070 graphics card; everything works fine both in and out of fullscreen for all test applications. On two other machines that runs a GeForce GTX 980 however; all test applications except PUBG works. When PUBG is running in fullscreen, my desktop duplication instead produces an all black image and I can't figure out why as the
Desktop Duplication Sample works fine for all test machines and test applications.
What I'm doing is basically the same as the sample except I'm extracting the pixel data and creating my own SDL(OpenGL) texture from that data instead of using the acquired ID3D11Texture2D directly.
Why is PUBG in fullscreen on GTX 980 the only test case that fails?
Is there something wrong with the way I'm getting the frame, handling the "DXGI_ERROR_ACCESS_LOST" error or how I'm copying the data from the GPU?
Declarations:
IDXGIOutputDuplication* m_OutputDup = nullptr;
Microsoft::WRL::ComPtr<ID3D11Device> m_Device = nullptr;
ID3D11DeviceContext* m_DeviceContext = nullptr;
D3D11_TEXTURE2D_DESC m_TextureDesc;
Initialization:
bool InitializeScreenCapture()
{
HRESULT result = E_FAIL;
if (!m_Device)
{
D3D_FEATURE_LEVEL featureLevels = D3D_FEATURE_LEVEL_11_0;
D3D_FEATURE_LEVEL featureLevel;
result = D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
0,
&featureLevels,
1,
D3D11_SDK_VERSION,
&m_Device,
&featureLevel,
&m_DeviceContext);
if (FAILED(result) || !m_Device)
{
Log("Failed to create D3DDevice);
return false;
}
}
// Get DXGI device
ComPtr<IDXGIDevice> DxgiDevice;
result = m_Device.As(&DxgiDevice);
if (FAILED(result))
{
Log("Failed to get DXGI device);
return false;
}
// Get DXGI adapter
ComPtr<IDXGIAdapter> DxgiAdapter;
result = DxgiDevice->GetParent(__uuidof(IDXGIAdapter), &DxgiAdapter);
if (FAILED(result))
{
Log("Failed to get DXGI adapter);
return false;
}
DxgiDevice.Reset();
// Get output
UINT Output = 0;
ComPtr<IDXGIOutput> DxgiOutput;
result = DxgiAdapter->EnumOutputs(Output, &DxgiOutput);
if (FAILED(result))
{
Log("Failed to get DXGI output);
return false;
}
DxgiAdapter.Reset();
ComPtr<IDXGIOutput1> DxgiOutput1;
result = DxgiOutput.As(&DxgiOutput1);
if (FAILED(result))
{
Log("Failed to get DXGI output1);
return false;
}
DxgiOutput.Reset();
// Create desktop duplication
result = DxgiOutput1->DuplicateOutput(m_Device.Get(), &m_OutputDup);
if (FAILED(result))
{
Log("Failed to create output duplication);
return false;
}
DxgiOutput1.Reset();
DXGI_OUTDUPL_DESC outputDupDesc;
m_OutputDup->GetDesc(&outputDupDesc);
// Create CPU access texture description
m_TextureDesc.Width = outputDupDesc.ModeDesc.Width;
m_TextureDesc.Height = outputDupDesc.ModeDesc.Height;
m_TextureDesc.Format = outputDupDesc.ModeDesc.Format;
m_TextureDesc.ArraySize = 1;
m_TextureDesc.BindFlags = 0;
m_TextureDesc.MiscFlags = 0;
m_TextureDesc.SampleDesc.Count = 1;
m_TextureDesc.SampleDesc.Quality = 0;
m_TextureDesc.MipLevels = 1;
m_TextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG::D3D11_CPU_ACCESS_READ;
m_TextureDesc.Usage = D3D11_USAGE::D3D11_USAGE_STAGING;
return true;
}
Screen capture:
void TeamSystem::CaptureScreen()
{
if (!m_ScreenCaptureInitialized)
{
Log("Attempted to capture screen without ScreenCapture being initialized");
return false;
}
HRESULT result = E_FAIL;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
ComPtr<IDXGIResource> desktopResource = nullptr;
ID3D11Texture2D* copyTexture = nullptr;
ComPtr<ID3D11Resource> image;
int32_t attemptCounter = 0;
DWORD startTicks = GetTickCount();
do // Loop until we get a non empty frame
{
m_OutputDup->ReleaseFrame();
result = m_OutputDup->AcquireNextFrame(1000, &frameInfo, &desktopResource);
if (FAILED(result))
{
if (result == DXGI_ERROR_ACCESS_LOST) // Access may be lost when changing from/to fullscreen mode(any application); when this happens we need to reaquirce the outputdup
{
m_OutputDup->ReleaseFrame();
m_OutputDup->Release();
m_OutputDup = nullptr;
m_ScreenCaptureInitialized = InitializeScreenCapture();
if (m_ScreenCaptureInitialized)
{
result = m_OutputDup->AcquireNextFrame(1000, &frameInfo, &desktopResource);
}
else
{
Log("Failed to reinitialize screen capture after access was lost");
return false;
}
}
if (FAILED(result))
{
Log("Failed to acquire next frame);
return false;
}
}
attemptCounter++;
if (GetTickCount() - startTicks > 3000)
{
Log("Screencapture timed out after " << attemptCounter << " attempts");
return false;
}
} while(frameInfo.TotalMetadataBufferSize <= 0 || frameInfo.LastPresentTime.QuadPart <= 0); // This is how you wait for an image containing image data according to SO (https://stackoverflow.com/questions/49481467/acquirenextframe-not-working-desktop-duplication-api-d3d11)
Log("ScreenCapture succeeded after " << attemptCounter << " attempt(s)");
// Query for IDXGIResource interface
result = desktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(©Texture));
desktopResource->Release();
desktopResource = nullptr;
if (FAILED(result))
{
Log("Failed to acquire texture from resource);
m_OutputDup->ReleaseFrame();
return false;
}
// Copy image into a CPU access texture
ID3D11Texture2D* stagingTexture = nullptr;
result = m_Device->CreateTexture2D(&m_TextureDesc, nullptr, &stagingTexture);
if (FAILED(result) || stagingTexture == nullptr)
{
Log("Failed to copy image data to access texture);
m_OutputDup->ReleaseFrame();
return false;
}
D3D11_MAPPED_SUBRESOURCE mappedResource;
m_DeviceContext->CopyResource(stagingTexture, copyTexture);
m_DeviceContext->Map(stagingTexture, 0, D3D11_MAP_READ, 0, &mappedResource);
void* copy = malloc(m_TextureDesc.Width * m_TextureDesc.Height * 4);
memcpy(copy, mappedResource.pData, m_TextureDesc.Width * m_TextureDesc.Height * 4);
m_DeviceContext->Unmap(stagingTexture, 0);
stagingTexture->Release();
m_OutputDup->ReleaseFrame();
// Create a new SDL texture from the data in the copy varialbe
free(copy);
return true;
}
Some notes:
I have modified my original code to make it more readable so some cleanup and logging in the error handling is missing.
None of the error or timeout cases(except DXGI_ERROR_ACCESS_LOST) trigger in any testing scenario.
The "attemptCounter" never goes above 2 in any testing scenario.
The test cases are limited since I don't have access to a computer which produces the black image case.
The culprit was CopyResource() and how I created the CPU access texture.
CopyResource() returns void and that is why I didn't look into it before; I didn't think it could fail in any significant way since I expected it to return bool or HRESULT if that was the case.
In the documentation of CopyResource() does however disclose a couple of fail cases.
This method is unusual in that it causes the GPU to perform the copy operation (similar to a memcpy by the CPU). As a result, it has a few restrictions designed for improving performance. For instance, the source and destination resources:
Must be different resources.
Must be the same type.
Must have identical dimensions (including width, height, depth, and size as appropriate).
Must have compatible DXGI formats, which means the formats must be identical or at least from the same type group.
Can't be currently mapped.
Since the initialization code runs before the test application enters fullscreen, the CPU access texture description is set up using the desktop resolution, format etc. This caused CopyResouce() to fail silently and simply now write anything to stagingTexture in the test cases where a non native resoltuion was used for the test application.
In conclusion; I just moved the m_TextureDescription setup to CaptureScreen() and used the description of copyTexture to get the variables I didn't want to change between the textures.
// Create CPU access texture
D3D11_TEXTURE2D_DESC copyTextureDesc;
copyTexture->GetDesc(©TextureDesc);
D3D11_TEXTURE2D_DESC textureDesc;
textureDesc.Width = copyTextureDesc.Width;
textureDesc.Height = copyTextureDesc.Height;
textureDesc.Format = copyTextureDesc.Format;
textureDesc.ArraySize = copyTextureDesc.ArraySize;
textureDesc.BindFlags = 0;
textureDesc.MiscFlags = 0;
textureDesc.SampleDesc = copyTextureDesc.SampleDesc;
textureDesc.MipLevels = copyTextureDesc.MipLevels;
textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG::D3D11_CPU_ACCESS_READ;
textureDesc.Usage = D3D11_USAGE::D3D11_USAGE_STAGING;
ID3D11Texture2D* stagingTexture = nullptr;
result = m_Device->CreateTexture2D(&textureDesc, nullptr, &stagingTexture);
While this solved the issues I was having; I still don't know why the reinitialization in the handling of DXGI_ERROR_ACCESS_LOST didn't resolve the issue anyway. Does the DesctopDuplicationDescription not use the same dimensions and format as the copyTexture?
I also don't know why this didn't fail in the same way on computers with newer graphics cards. I did however notice that these machines were able to capture fullscreen applications using a simple BitBlt() of the desktop surface.
I'm trying to write a Windows C++ program that will try to pick out a color of interest from whatever is currently being displayed on the screen.
I've tried following examples for GDI, Direct3D9, and Direct3D11 DXGI, and they all seem to work only for capturing the Windows desktop and/or my own application's own output. When I launch a full-screen Direct3D game, I seem to to end up with some flavor of blank pixel data.
It must be possible to accomplish this, or else OBS Studio, FRAPS, etc. would not work as transparently as they do.
I know I could try to reverse engineer OBS Studio, but does anybody have a more succinct C++ solution for capturing an arbitrary Windows application's video output as some kind of pixel buffer?
Edit: I should also mention that capture of regular desktop windows seems to work. It's fullscreen games that are giving me trouble.
Edit: A commenter requested my GDI code. Here is my GDI and D3D9 code. As you can see, I tried a few variations based on conflicting examples that I found:
std::wstring GetScreenColor(COLORREF& colorRef)
{
std::wstring retVal;
//const int desktopWidth(GetDeviceCaps(desktopHdc, HORZRES));
//const int desktopHeight(GetDeviceCaps(desktopHdc, VERTRES));
// const int desktopWidth(GetSystemMetrics(SM_CXVIRTUALSCREEN));
// const int desktopHeight(GetSystemMetrics(SM_CYVIRTUALSCREEN));
const int desktopWidth(GetSystemMetrics(SM_CXSCREEN));
const int desktopHeight(GetSystemMetrics(SM_CYSCREEN));
HWND desktopHwnd(GetDesktopWindow());
// HDC desktopHdc(GetDC(NULL));
HDC desktopHdc(GetDC(desktopHwnd));
HDC myHdc(CreateCompatibleDC(desktopHdc));
const HBITMAP desktopBitmap(CreateCompatibleBitmap(desktopHdc, desktopWidth, desktopHeight));
SelectObject(myHdc, desktopBitmap);
// BitBlt(myHdc, 0, 0, desktopWidth, desktopHeight, desktopHdc, 0, 0, SRCCOPY);
BitBlt(myHdc, 0, 0, desktopWidth, desktopHeight, desktopHdc, 0, 0, SRCCOPY | CAPTUREBLT);
//SelectObject(myHdc, hOld);
ReleaseDC(NULL, desktopHdc);
BITMAPINFO bitmapInfo = { 0 };
bitmapInfo.bmiHeader.biSize = sizeof(bitmapInfo.bmiHeader);
bitmapInfo.bmiHeader.biWidth = desktopWidth;
bitmapInfo.bmiHeader.biHeight = -desktopHeight;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 24;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biSizeImage = 0;
// TODO: use a persistent buffer?
const unsigned long numPixels(desktopHeight * desktopWidth);
ColorBGRS* rawPixels(new ColorBGRS[numPixels]);
if (!GetDIBits(myHdc, desktopBitmap, 0, desktopHeight, rawPixels, &bitmapInfo, DIB_RGB_COLORS))
{
delete[] rawPixels;
ReleaseDC(desktopHwnd, desktopHdc);
DeleteDC(myHdc);
DeleteObject(desktopBitmap);
return L"GetDIBits() failed";
}
unsigned long redSum(0);
unsigned long greenSum(0);
unsigned long blueSum(0);
for (unsigned long index(0); index < numPixels; ++index)
{
blueSum += rawPixels[index].blue;
greenSum += rawPixels[index].green;
redSum += rawPixels[index].red;
}
const unsigned long redAverage(redSum / numPixels);
const unsigned long blueAverage(blueSum / numPixels);
const unsigned long greenAverage(greenSum / numPixels);
colorRef = RGB(redAverage, greenAverage, blueAverage);
delete[] rawPixels;
ReleaseDC(desktopHwnd, desktopHdc);
DeleteDC(myHdc);
DeleteObject(desktopBitmap);
return std::wstring();
}
std::wstring GetScreenColor2(COLORREF& colorRef)
{
IDirect3D9* d3d9(Direct3DCreate9(D3D_SDK_VERSION));
if (!d3d9)
{
d3d9->Release();
return L"Direct3DCreate9() failed";
}
D3DDISPLAYMODE d3dDisplayMode;
if (FAILED(d3d9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3dDisplayMode)))
{
return L"GetAdapterDisplayMode() failed";
}
D3DPRESENT_PARAMETERS d3dPresentParams;
ZeroMemory(&d3dPresentParams, sizeof(D3DPRESENT_PARAMETERS));
d3dPresentParams.Windowed = TRUE;
d3dPresentParams.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
d3dPresentParams.BackBufferFormat = d3dDisplayMode.Format;
d3dPresentParams.BackBufferCount = 1;
d3dPresentParams.BackBufferHeight = d3dDisplayMode.Height;
d3dPresentParams.BackBufferWidth = d3dDisplayMode.Width;
d3dPresentParams.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dPresentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
//d3dPresentParams.SwapEffect = D3DSWAPEFFECT_COPY;
d3dPresentParams.hDeviceWindow = NULL; //hWnd;
d3dPresentParams.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
d3dPresentParams.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
IDirect3DDevice9* d3d9Device(0);
if (FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3dPresentParams.hDeviceWindow, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dPresentParams, &d3d9Device)))
{
d3d9->Release();
return L"CreateDevice() failed";
}
IDirect3DSurface9* d3d9Surface(0);
// if (FAILED(d3d9Device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &d3d9Surface))) return L"GetBackBuffer() failed";
if (FAILED(d3d9Device->CreateOffscreenPlainSurface(d3dDisplayMode.Width, d3dDisplayMode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &d3d9Surface, NULL)))
// if (FAILED(d3d9Device->CreateOffscreenPlainSurface(d3dDisplayMode.Width, d3dDisplayMode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &d3d9Surface, NULL)))
{
d3d9Device->Release();
d3d9->Release();
return L"CreateOffscreenPlainSurface() failed";
}
if (FAILED(d3d9Device->GetFrontBufferData(0, d3d9Surface)))
{
d3d9Surface->Release();
d3d9Device->Release();
d3d9->Release();
return L"GetFrontBufferData() failed";
}
D3DLOCKED_RECT d3dLockedRect;
if (FAILED(d3d9Surface->LockRect(&d3dLockedRect, 0, D3DLOCK_NO_DIRTY_UPDATE |
D3DLOCK_NOSYSLOCK |
D3DLOCK_READONLY)))
{
d3d9Surface->UnlockRect();
d3d9Surface->Release();
d3d9Device->Release();
d3d9->Release();
return L"LockRect() failed";
}
const unsigned long numPixels(d3dDisplayMode.Height * d3dDisplayMode.Width);
BYTE* rawPixels((BYTE*)(d3dLockedRect.pBits));
colorRef = RGB(*(rawPixels + 2), *(rawPixels + 1), *(rawPixels));
d3d9Surface->UnlockRect();
d3d9Surface->Release();
d3d9Device->Release();
d3d9->Release();
return std::wstring();
}
There is the Desktop Duplication API since Windows 8 that is able to record fullscreen applications like games. I have recently made this library for one of my projects that you maybe can use for reference. Only in your case you need to get the raw pixel data from the textures instead of writing them to video or images.
edit: added a small example of reinitialization on lost access.
{
CComPtr<ID3D11Device> pDevice;
CComPtr<IDXGIOutputDuplication> pDeskDupl;
//(...)create devices and duplication interface etc here..
InitializeDesktopDupl(pDevice, &pDeskDupl, &OutputDuplDesc);
while(true) //capture loop gist.
{
IDXGIResource *pDesktopResource = nullptr;
DXGI_OUTDUPL_FRAME_INFO FrameInfo;
RtlZeroMemory(&FrameInfo, sizeof(FrameInfo));
// Get new frame
HRESULT hr = pDeskDupl->AcquireNextFrame(
99,//timeout in ms
&FrameInfo,
&pDesktopResource);
if (hr == DXGI_ERROR_ACCESS_LOST) {
pDeskDupl->ReleaseFrame();
pDeskDupl.Release();
pDesktopResource->Release();
hr = InitializeDesktopDupl(pDevice, &pDeskDupl, &OutputDuplDesc);
if(FAILED(hr)){
//Check if everything is OK before continuing
}
}
}
}
HRESULT InitializeDesktopDupl(ID3D11Device *pDevice, IDXGIOutputDuplication **ppDesktopDupl, DXGI_OUTDUPL_DESC *pOutputDuplDesc)
{
*ppDesktopDupl = NULL;
*pOutputDuplDesc;
// Get DXGI device
CComPtr<IDXGIDevice> pDxgiDevice;
CComPtr<IDXGIOutputDuplication> pDeskDupl = NULL;
DXGI_OUTDUPL_DESC OutputDuplDesc;
HRESULT hr = pDevice->QueryInterface(IID_PPV_ARGS(&pDxgiDevice));
if (FAILED(hr)) { return hr; }
// Get DXGI adapter
CComPtr<IDXGIAdapter> pDxgiAdapter;
hr = pDxgiDevice->GetParent(
__uuidof(IDXGIAdapter),
reinterpret_cast<void**>(&pDxgiAdapter));
pDxgiDevice.Release();
if (FAILED(hr)) { return hr; }
// Get output
CComPtr<IDXGIOutput> pDxgiOutput;
hr = pDxgiAdapter->EnumOutputs(
m_DisplayOutput,
&pDxgiOutput);
if (FAILED(hr)) { return hr; }
pDxgiAdapter.Release();
CComPtr<IDXGIOutput1> pDxgiOutput1;
hr = pDxgiOutput->QueryInterface(IID_PPV_ARGS(&pDxgiOutput1));
if (FAILED(hr)) { return hr; }
pDxgiOutput.Release();
// Create desktop duplication
hr = pDxgiOutput1->DuplicateOutput(
pDevice,
&pDeskDupl);
if (FAILED(hr)) { return hr; }
pDxgiOutput1.Release();
// Create GUI drawing texture
pDeskDupl->GetDesc(&OutputDuplDesc);
pDxgiOutput1.Release();
*ppDesktopDupl = pDeskDupl;
(*ppDesktopDupl)->AddRef();
*pOutputDuplDesc = OutputDuplDesc;
return hr;
}
I am new to DirectX an I am trying to do a simple application that reads a video and display it on a Quad.
I read the video using Windows Media Foundation (IMFSourceReader), that sends me a callback when a sample is decoded (IMFSample).
I want to convert this IMFSample* to a ID3D11ShaderResourceView* in order to use it as a texture to draw my quad, however the conversion fails.
Here is what I do (I removed non relevant error checks):
HRESULT SourceReaderCB::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample)
{
...
DWORD NumBuffers = 0;
hr = pSample->GetBufferCount(&NumBuffers);
if (FAILED(hr) || NumBuffers < 1)
{
...
}
IMFMediaBuffer* SourceMediaPtr = nullptr;
hr = pSample->GetBufferByIndex(0, &SourceMediaPtr);
if (FAILED(hr))
{
...
}
ComPtr<IMFMediaBuffer> _pInputBuffer = SourceMediaPtr;
ComPtr<IMF2DBuffer2> _pInputBuffer2D2;
bool isVideoFrame = (_pInputBuffer.As(&_pInputBuffer2D2) == S_OK);
if (isVideoFrame)
{
IMFDXGIBuffer* pDXGIBuffer = NULL;
ID3D11Texture2D* pSurface = NULL;
hr = _pInputBuffer->QueryInterface(__uuidof(IMFDXGIBuffer), (LPVOID*)&pDXGIBuffer);
if (FAILED(hr))
{
SafeRelease(&SourceMediaPtr);
goto done;
}
hr = pDXGIBuffer->GetResource(__uuidof(ID3D11Texture2D), (LPVOID*)&pSurface);
if (FAILED(hr))
{
...
}
ID3D11ShaderResourceView* resourceView;
if (pSurface)
{
D3D11_TEXTURE2D_DESC textureDesc;
pSurface->GetDesc(&textureDesc);
D3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc;
shaderResourceViewDesc.Format = DXGI_FORMAT_R8_UNORM;
shaderResourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
shaderResourceViewDesc.Texture2D.MostDetailedMip = 0;
shaderResourceViewDesc.Texture2D.MipLevels = 1;
ID3D11ShaderResourceView* resourceView;
hr = d3d11device->CreateShaderResourceView(pSurface, &shaderResourceViewDesc, &resourceView);
if (FAILED(hr))
{
... // CODE FAILS HERE
}
...
}
}
}
My first issue is that I set the shaderResourceViewDesc.Format as DXGI_FORMAT_R8_UNORM which will probably just give me red image (I will have to investigate this later).
The second and blocking issue I am facing ius that the conversion of ID3D11Texture2D to ID3D11ShaderResourceView fails with following error message:
ID3D11Device::CreateShaderResourceView: A ShaderResourceView cannot be created of a Resource that did not specify the D3D11_BIND_SHADER_RESOURCE BindFlag. [ STATE_CREATION ERROR #129: CREATESHADERRESOURCEVIEW_INVALIDRESOURCE]
I understand that there is a flag missing at the creation of the texture that prevents me to do what I want to do, but as the data buffer is created by WMF, I am not sure what I am supposed to do to fix this issue.
Thanks for your help
I see you code, and I can say that your way is wrong - no offense. Firstly, video decoder creates simple texture - in you situation DirectX11 texture - it is a regular texture - it is not shader resource, as a result it cannot be used in shader code. In my view, there are two way for resolving of your task:
Research - Walkthrough: Using MF to render video in a Direct3D app - this link present way for "Walkthrough: Using Microsoft Media Foundation for Windows Phone 8" - from your code I see that you try write solution for WindowsStore - UWP and code for Windows Phone is workable - this code needs MediaEnginePlayer - The MediaEnginePlayer class serves as a helper class that wraps the MF APIs;
Find on GitHub Windows-classic-samples and find in that DX11VideoRenderer - this is full code of Media Foundation renderer with DirectX11 - it includes very good example for using of DirectX11 Video Processor which does blitting of regular video texture from decoder into the rendering video texture of swap-chain:
2.1. Get rendering texture from Swap Chain:
// Get Backbuffer
hr = m_pSwapChain1->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pDXGIBackBuffer);
if (FAILED(hr))
{
break;
}
2.2. Create from rendering texture output view of video processor:
//
// Create Output View of Output Surfaces.
//
D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC OutputViewDesc;
ZeroMemory( &OutputViewDesc, sizeof( OutputViewDesc ) );
if (m_b3DVideo && m_bStereoEnabled)
{
OutputViewDesc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2DARRAY;
}
else
{
OutputViewDesc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D;
}
OutputViewDesc.Texture2D.MipSlice = 0;
OutputViewDesc.Texture2DArray.MipSlice = 0;
OutputViewDesc.Texture2DArray.FirstArraySlice = 0;
if (m_b3DVideo && 0 != m_vp3DOutput)
{
OutputViewDesc.Texture2DArray.ArraySize = 2; // STEREO
}
QueryPerformanceCounter(&lpcStart);
hr = m_pDX11VideoDevice->CreateVideoProcessorOutputView(pDXGIBackBuffer, m_pVideoProcessorEnum, &OutputViewDesc, &pOutputView);
2.3. Create from regular decoder video texture input view for video processor:
D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC InputLeftViewDesc;
ZeroMemory( &InputLeftViewDesc, sizeof( InputLeftViewDesc ) );
InputLeftViewDesc.FourCC = 0;
InputLeftViewDesc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
InputLeftViewDesc.Texture2D.MipSlice = 0;
InputLeftViewDesc.Texture2D.ArraySlice = dwLeftViewIndex;
hr = m_pDX11VideoDevice->CreateVideoProcessorInputView(pLeftTexture2D, m_pVideoProcessorEnum, &InputLeftViewDesc, &pLeftInputView);
if (FAILED(hr))
{
break;
}
2.4. Do blitting of regular decoder video texture on rendering texture from Swap Chain:
D3D11_VIDEO_PROCESSOR_STREAM StreamData;
ZeroMemory( &StreamData, sizeof( StreamData ) );
StreamData.Enable = TRUE;
StreamData.OutputIndex = 0;
StreamData.InputFrameOrField = 0;
StreamData.PastFrames = 0;
StreamData.FutureFrames = 0;
StreamData.ppPastSurfaces = NULL;
StreamData.ppFutureSurfaces = NULL;
StreamData.pInputSurface = pLeftInputView;
StreamData.ppPastSurfacesRight = NULL;
StreamData.ppFutureSurfacesRight = NULL;
if (m_b3DVideo && MFVideo3DSampleFormat_MultiView == m_vp3DOutput && pRightTexture2D)
{
StreamData.pInputSurfaceRight = pRightInputView;
}
hr = pVideoContext->VideoProcessorBlt(m_pVideoProcessor, pOutputView, 0, 1, &StreamData );
if (FAILED(hr))
{
break;
}
Yes, they are sections of complex code, and it needs research whole DX11VideoRenderer project for understanding of it - it will take huge amount of time.
Regards,
Evgeny Pereguda
Debug output suggests that the texture is not compatible, as it was created without D3D11_BIND_SHADER_RESOURCE flag (specified in BindFlag field of D3D11_TEXTURE2D_DESC structure.
You read the texture already created by Media Foundation primitive. In some cases you can alter the creation flags, however the general case is that you need to create a compatible texture on your own, copy the data between the textures, and then call CreateShaderResourceView method with your texture as an argument rather than original texture.
I'm trying to use the SaveWICTextureToFile method from DirectXTK to grab a screenshot in my Windows Store app (Windows 8.1). I'm using XAML with a SwapChainPanel element. Unfortunately, the method always saves a rectangle of the screen size filled with solid color rather than the current screen content. Every time I call the SaveWICTextureToFile method it saves a different color.
This is my code (simplified):
void DirectXPage::SaveButton_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
critical_section::scoped_lock lock(m_main->GetCriticalSection());
auto folder = Windows::Storage::ApplicationData::Current->TemporaryFolder;
WCHAR fname[_MAX_PATH];
wcscpy_s(fname, folder->Path->Data());
wcscat_s(fname, L"\\screenshot.png");
auto context = m_deviceResources->GetD3DDeviceContext();
auto swapChain = m_deviceResources->GetSwapChain();
ID3D11Texture2D* backBuffer = nullptr;
HRESULT hr = swapChain->GetBuffer(0, __uuidof(*backBuffer), (LPVOID*)&backBuffer);
if (SUCCEEDED(hr))
{
HRESULT hr = SaveWICTextureToFile(context, backBuffer, GUID_ContainerFormatPng, fname);
DX::ThrowIfFailed(hr);
// ... mode code for FileSavePicker etc.
}
}
What am I doing wrong?
Thanks,
Leszek
INTRODUCTION AND RELEVANT INFORMATION:
I am trying to learn to print with XPS Document API.
For a simple start, I have decided to draw a rectangle, and some text below it.
After going through the official examples I was able to achieve my goal.
PROBLEM:
Basically, I have concatenated 2 code examples provided from the above link. Now I wanted to polish the code, mainly to use single brush to draw both rectangle and the text.
After rewriting the code, I get the following error:
First-chance exception at 0x7555D3CF in XPS printing.exe: Microsoft C++ exception: SplException::THResultException at memory location 0x002CEF9C.
If there is a handler for this exception, the program may be safely continued.
SSCCEE:
Below is the function I rewrote. I have marked the crashing point with the appropriate comments.
void XPS_TEST()
{
IXpsOMObjectFactory *xpsFactory;
HRESULT hr = S_OK;
// Init COM for this thread if it hasn't
// been initialized, yet.
hr = CoInitializeEx(0, COINIT_MULTITHREADED);
hr = CoCreateInstance(
__uuidof(XpsOMObjectFactory),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IXpsOMObjectFactory),
reinterpret_cast<LPVOID*>(&xpsFactory));
if (SUCCEEDED(hr))
{
// Declare the variables used in this section.
IOpcPartUri *opcPartUri = NULL;
IXpsOMPackage *xpsPackage = NULL;
IXpsOMDocumentSequence *xpsFDS = NULL;
IXpsOMDocumentCollection *fixedDocuments = NULL;
IXpsOMDocument *xpsFD = NULL;
IXpsOMPage *xpsPage = NULL;
IXpsOMPageReferenceCollection *pageRefs = NULL;
IXpsOMPageReference *xpsPageRef = NULL;
// test size of the document
XPS_SIZE pageSize = { 200, 200 };
// Create the package.
hr = xpsFactory->CreatePackage(&xpsPackage);
// Create the URI for the fixed document sequence part and then
// create the fixed document sequence
hr = xpsFactory->CreatePartUri(
L"/FixedDocumentSequence.fdseq", &opcPartUri);
hr = xpsFactory->CreateDocumentSequence(opcPartUri, &xpsFDS);
// Release this URI to reuse the interface pointer.
if (NULL != opcPartUri) { opcPartUri->Release(); opcPartUri = NULL; }
// Create the URI for the document part and then create the document.
hr = xpsFactory->CreatePartUri(
L"/Documents/1/FixedDocument.fdoc", &opcPartUri);
hr = xpsFactory->CreateDocument(opcPartUri, &xpsFD);
// Release this URI to reuse the interface pointer.
if (NULL != opcPartUri) { opcPartUri->Release(); opcPartUri = NULL; }
// Create a blank page.
hr = xpsFactory->CreatePartUri(
L"/Documents/1/Pages/1.fpage", &opcPartUri);
hr = xpsFactory->CreatePage(
&pageSize, // Page size
L"en-US", // Page language
opcPartUri, // Page part name
&xpsPage);
// Release this URI to reuse the interface pointer.
if (NULL != opcPartUri) { opcPartUri->Release(); opcPartUri = NULL; }
// Create a page reference for the page.
hr = xpsFactory->CreatePageReference(&pageSize, &xpsPageRef);
// Add the fixed document sequence to the package.
hr = xpsPackage->SetDocumentSequence(xpsFDS);
// Get the document collection of the fixed document sequence
// and then add the document to the collection.
hr = xpsFDS->GetDocuments(&fixedDocuments);
hr = fixedDocuments->Append(xpsFD);
// Get the page reference collection from the document
// and add the page reference and blank page.
hr = xpsFD->GetPageReferences(&pageRefs);
hr = pageRefs->Append(xpsPageRef);
hr = xpsPageRef->SetPage(xpsPage);
//======================== draw rectangle ====================//
XPS_COLOR xpsColor;
IXpsOMSolidColorBrush *xpsFillBrush = NULL;
// the brush I want to reuse !!
IXpsOMSolidColorBrush *xpsStrokeBrush = NULL;
// Set the fill brush color to RED.
xpsColor.colorType = XPS_COLOR_TYPE_SRGB;
xpsColor.value.sRGB.alpha = 0xFF;
xpsColor.value.sRGB.red = 0xFF;
xpsColor.value.sRGB.green = 0x00;
xpsColor.value.sRGB.blue = 0x00;
// Use the object factory to create the brush.
hr = xpsFactory->CreateSolidColorBrush(
&xpsColor,
NULL, // color profile resource
&xpsFillBrush);
// The color profile resource parameter is NULL because
// this color type does not use a color profile resource.
// Set the stroke brush color to BLACK.
xpsColor.colorType = XPS_COLOR_TYPE_SRGB;
xpsColor.value.sRGB.alpha = 0xFF;
xpsColor.value.sRGB.red = 0x00;
xpsColor.value.sRGB.green = 0x00;
xpsColor.value.sRGB.blue = 0x00;
// Use the object factory to create the brush.
hr = xpsFactory->CreateSolidColorBrush(
&xpsColor,
NULL, // This color type does not use a color profile resource.
&xpsStrokeBrush);
// test rectangle
XPS_RECT rect = { 0, 0, 200, 20 };
IXpsOMGeometryFigure *rectFigure;
IXpsOMGeometry *imageRectGeometry;
IXpsOMGeometryFigureCollection *geomFigureCollection;
// Define the start point and create an empty figure.
XPS_POINT origin = { rect.x, rect.y };
hr = xpsFactory->CreateGeometryFigure(&origin, &rectFigure);
// Define the segments of the geometry figure.
// First, define the type of each segment.
XPS_SEGMENT_TYPE segmentTypes[3] =
{
XPS_SEGMENT_TYPE_LINE, // each segment is a straight line
XPS_SEGMENT_TYPE_LINE,
XPS_SEGMENT_TYPE_LINE
};
// Define the x and y coordinates of each corner of the figure
// the start point has already been defined so only the
// remaining three corners need to be defined.
FLOAT segmentData[6] =
{
rect.x, (rect.y + rect.height),
(rect.x + rect.width), (rect.y + rect.height),
(rect.x + rect.width), rect.y
};
// Describe if the segments are stroked (that is if the segment lines
// should be drawn as a line).
BOOL segmentStrokes[3] =
{
TRUE, TRUE, TRUE // Yes, draw each of the segment lines.
};
// Add the segment data to the figure.
hr = rectFigure->SetSegments(
3,
6,
segmentTypes,
segmentData,
segmentStrokes);
// Set the closed and filled properties of the figure.
hr = rectFigure->SetIsClosed(TRUE);
hr = rectFigure->SetIsFilled(TRUE);
// Create the geometry object.
hr = xpsFactory->CreateGeometry(&imageRectGeometry);
// Get a pointer to the figure collection interface of the geometry...
hr = imageRectGeometry->GetFigures(&geomFigureCollection);
// ...and then add the figure created above to this geometry.
hr = geomFigureCollection->Append(rectFigure);
// If not needed for anything else, release the rectangle figure.
rectFigure->Release();
// when done adding figures, release the figure collection.
geomFigureCollection->Release();
IXpsOMPath *rectPath = NULL;
IXpsOMVisualCollection *pageVisuals = NULL;
// Create the new path object.
hr = xpsFactory->CreatePath(&rectPath);
// Add the geometry to the path.
// imageRectGeometry is initialized outside of this example.
hr = rectPath->SetGeometryLocal(imageRectGeometry);
// Set the short description of the path to provide
// a textual description of the object for accessibility.
hr = rectPath->SetAccessibilityShortDescription(L"Red Rectangle");
// Set the fill and stroke brushes to use the brushes
// created in the first section.
hr = rectPath->SetFillBrushLocal(xpsFillBrush);
hr = rectPath->SetStrokeBrushLocal(xpsStrokeBrush);
// Get the visual collection of this page and add this path to it.
hr = xpsPage->GetVisuals(&pageVisuals);
hr = pageVisuals->Append(rectPath);
// If not needed for anything else, release the rectangle path.
rectPath->Release();
// When finished with the brushes, release the interface pointers.
if (NULL != xpsFillBrush) xpsFillBrush->Release();
//******************** I have commented out below code, ****************//
//******************** because I plan to use the brush to draw text ****//
//if (NULL != xpsStrokeBrush) xpsStrokeBrush->Release();
// When done with the geometry interface, release it.
imageRectGeometry->Release();
//========================= draw text =====================//
GUID fontNameGuid;
WCHAR guidString[128] = { 0 };
WCHAR uriString[256] = { 0 };
IStream *fontStream = NULL;
IOpcPartUri *fontUri = NULL;
IXpsOMFontResource *fontResource = NULL;
// Create font stream.
hr = xpsFactory->CreateReadOnlyStreamOnFile(
// I have hardcoded Arial here, just for testing
L"C:\\Windows\\Fonts\\Arial.ttf",
&fontStream);
// Create new obfuscated part name for this resource using a GUID.
hr = CoCreateGuid(&fontNameGuid);
hr = StringFromGUID2(
fontNameGuid,
guidString,
ARRAYSIZE(guidString));
// Create a URI string for this font resource that will place
// the font part in the /Resources/Fonts folder of the package.
wcscpy_s(uriString, ARRAYSIZE(uriString), L"/Resources/Fonts/");
// Create the part name using the GUID string as the name and
// ".odttf" as the extension GUID string start and ends with
// curly braces so they are removed.
wcsncat_s(uriString, ARRAYSIZE(uriString),
guidString + 1, wcslen(guidString) - 2);
wcscat_s(uriString, ARRAYSIZE(uriString), L".odttf");
// Create the font URI interface.
hr = xpsFactory->CreatePartUri(
uriString,
&fontUri);
// Create the font resource.
hr = xpsFactory->CreateFontResource(
fontStream,
XPS_FONT_EMBEDDING_OBFUSCATED,
fontUri,
FALSE, // isObfSourceStream
&fontResource);
if (NULL != fontUri) fontUri->Release();
LPCWSTR unicodeString = L"Test string";
// move test string below our rectangle
origin.y += 30.0f;
FLOAT fontEmSize = 7.56f;
IXpsOMGlyphsEditor *glyphsEditor = NULL;
IXpsOMGlyphs *xpsGlyphs = NULL;
// Create a new Glyphs object and set its properties.
hr = xpsFactory->CreateGlyphs(fontResource, &xpsGlyphs);
hr = xpsGlyphs->SetOrigin(&origin);
hr = xpsGlyphs->SetFontRenderingEmSize(fontEmSize);
//*************** I GET A CRASH BELOW !!!! ***************//
hr = xpsGlyphs->SetFillBrushLocal(xpsStrokeBrush); // <<---
// Some properties are inter-dependent so they
// must be changed by using a GlyphsEditor.
hr = xpsGlyphs->GetGlyphsEditor(&glyphsEditor);
hr = glyphsEditor->SetUnicodeString(unicodeString);
hr = glyphsEditor->ApplyEdits();
// Add the new Glyphs object to the page
hr = pageVisuals->Append(xpsGlyphs);
// Release interface pointers.
if (NULL != xpsGlyphs) xpsGlyphs->Release();
if (NULL != glyphsEditor) glyphsEditor->Release();
if (NULL != pageVisuals) pageVisuals->Release();
//******************** Releasing the brush here *******//
if (NULL != xpsStrokeBrush) xpsStrokeBrush->Release();
//========================= write to file ====================//
hr = xpsPackage->WriteToFile(
L"C:\\Users\\Smiljkovic\\Desktop\\xpsTest.xps",
NULL, // LPSECURITY_ATTRIBUTES
FILE_ATTRIBUTE_NORMAL,
FALSE); // Optimize Markup Size
//========================== cleanup ==================//
// Release interface pointer
if (NULL != xpsPage) xpsPage->Release();
if (NULL != pageRefs) pageRefs->Release();
if (NULL != fixedDocuments) fixedDocuments->Release();
if (NULL != xpsPageRef) xpsPageRef->Release();
if (NULL != xpsFD) xpsFD->Release();
if (NULL != xpsFDS) xpsFDS->Release();
if (NULL != xpsPackage) xpsPackage->Release();
xpsFactory->Release();
}
// Uninitialize COM when finished
CoUninitialize();
}
QUESTION:
How can I use the same brush ( xpsStrokeBrush from the above example ) for drawing both the text and the rectangle outline?
per SetStrokeBrushLocal documentation:
After you call SetStrokeBrushLocal, the stroke brush lookup key is released and GetStrokeBrushLookup returns a NULL pointer in the lookup parameter.
You could use Clone on the brush before using it.
But, if you plan on re-using brushes, then use the CreateDictionary, SetDictionaryLocal and then Append your brush there; which will let you use SetFillBrushLookup.