dev.nlited.com

>>

Direct2D Errors

<<<< prev
next >>>>

2017-05-07 23:41:16 chip Page 1978 📢 PUBLIC

May 7 2017

Direct2D errors can be hard to find, especially since most graphic operations are deferred until EndDraw() so the individual commands (FillRectangle, DrawBitmap, etc) will always succeed even when the actual operation is destined to fail.

The error codes can be found in WinError.h (Program Files (x86)\Windows Kits\8.1\Include\shared\). Search for "D2DERR_WRONG_STATE".

I finally started using the D2D1_TAG values returned by DrawEnd(), and they are extremely useful. In DirectX, graphic operations are queued up by the function calls but not submitted to the GPU until DrawEnd(). This lets the GPU operate much more efficiently, but makes it more difficult to know which operation caused DrawEnd() to fail. SetTags() lets me set the tags in the operations queue and those tags will be returned by DrawEnd() if an error occurs.


SetTags: void DrawScene(ID2D1RenderTarget *pRT) { D2D1_TAG Tag1=0,Tag2=0; pRT->BeginDraw(); Tag1++; pRT->FillRectangle(rImg,pbrBkgnd); Tag1++; pRT->DrawBitmap(pBitmap,&rBmp); if(!SUCCEEDED(WinErr= pRT->EndDraw(&Tag1,&Tag2))) { Error("DrawScene failed: Tag1=%d",Tag1); } }

Tag1 now tells me which operation failed: 1 means FillRectange() failed, 2 means DrawBitmap() failed.

Blank output: Make sure BeginDraw() and EndDraw() were called.

0x88990001 - D2DERR_WRONG_STATE: Make sure BeginDraw() was called. Make sure no operations were called before BeginDraw().


0x88990015

The resource was realized on the wrong render target.

Resizing the window requires resizing the offscreen images, which is essentially the same as destroying and recreating them. The draw operations are failing after the resize with 0x88990015. This error code seems to imply that I am using an invalid or mismatched RenderTarget to draw.

This error took some time to unravel, but I was indeed using the wrong render target. I neglected to write this at the time and now I can't remember the details. :(


Resizing a windows requires destroying everything and recreating it. The window will be redrawn with a new RenderTarget and anything derived from the old RenderTarget will be invalid. If I want to avoid recreating everything in every redraw, I need to check whether the RenderTarget has changed.



Device Context

I found this paragraph in the Direct2D docs:


Mistake icon

On Windows 7 and earlier, you use a ID2D1HwndRenderTarget or another render target interface to render to a window or surface. Starting with Windows 8, we do not recommend rendering by using methods that rely on interfaces like ID2D1HwndRenderTarget because they won't work with Windows Store apps. You can use a device context to render to an Hwnd if you want to make a desktop app and still take advantage of the device context's additional features. However, the device context is required to render content in a Windows Store apps with Direct2D.


I guess all my RenderTarget code is obsolete already.

ID2D1DeviceContext draws directly to the device, which makes sense, but in this context the "device" is the display. I still need to create an ID2D1BitmapRenderTarget if I want to render to an offscreen bitmap, then render the composed image to the device context during the final paint.

I create an ID2D1DeviceContext, use it to create an ID2D1Bitmap, then set the bitmap as the target. Create only one device context and use GetTarget() and SetTarget() to select different bitmaps.

ID2D1Factory does not have a function to create a device or device context, it includes only CreateDCRenderTarget() (to connect to a GDI HDC) and CreateHwndRenderTarget() - which is disallowed for Windows Store. I need to use ID2D1Factory6, which includes CreateDevice().

I got stuck trying to obtain an ID2D1Bitmap target for an existing window, the MSDN docs have a horrendous example that uses CoreWindow to dig down to the final swap chain buffer. Including CoreWindow is a whole new kettle of snakes...

I am abandoning the DeviceContext approach and going back to using RenderTarget.

ID2D1Bitmap::Release() needs to be called on the same thread as it was created? I am crashing on exit if I delete from a different thread. This is also true for IWICImagingFactory, it must be created and destroyed from the same thread.


Resizing

If I use ID2D1BitmapRenderTarget, resizing the window becomes a huge problem. There is no way to resize or recreate the underlying ID2D1Bitmap for the renderer, I need to destroy and rebuild it. The real problem is that all my sprite bitmaps are tied to the original renderer and refuse to be drawn onto the new renderer. Not only do I need to rebuild the image but I also need to destroy and rebuild every sprite, requiring the sprites to be owned by the image.

I don't need to rebuild the sprites if I keep the original window render target and use ID2D1HwndRenderTarget::Resize(), then rebuild the image render using CreateCompatibleRenderTarget().

Window Resize: int Image::Resize(const RECT &_rWnd) { int Err= ERR_OK; if(RWID(_rWnd)!=RWID(rWnd) || RHGT(_rWnd)!=RHGT(rWnd)) { rWnd= _rWnd; if(pWndRT) pWndRT->Resize(SizeU(RWID(rWnd),RHGT(rWnd))); if(pImgRT) { HRESULT WinErr; SafeRelease(pImgBM); SafeRelease(pImgRT); rImg= { 0,0,(FLOAT)RWID(rWnd),(FLOAT)RHGT(rWnd) }; if(!SUCCEEDED(WinErr= pWndRT->CreateCompatibleRenderTarget(SizeF(rImg.right,rImg.bottom),&pImgRT))) { Err= Error(ERR_SYSCREATE,"Image:Resize: Unable to create new image RT %dx%d. [%X]",RWID(rWnd),RHGT(rWnd),WinErr); } else { pImgRT->GetBitmap(&pImgBM); } } } return(Err); }

The DeviceContext has the advantage of being able to retarget from one bitmap to another.

Collision detection using Direct2D.


D2D1 Memory Leaks

If the D2D1 factory was created in debug mode (highly recommended), it will spew out a list of all the unreleased objects when the process shuts down. This is extremely useful, but it requires a bit of memory spelunking in Visual Studio.

An interface [000000D288D5D0F0] was created but not released. Use 'dps 000000D288D5D090' to view its allocation stack. Object type: ID2D1SolidColorBrush Color: r=0.37 g=0.62 b=0.63 a=0.90 Outstanding reference count: 1

The hint "dps 000000D288D5D090" reveals that the D2D1 authors are assuming everyone is using WinDbg to debug DirectX, not Visual Studio. The WinDbg dps command is an abbreviation for Dump Pointer Symbols and will interpret the address as a list of pointers to code that can be cross-referenced in the symbol table. D2D1 is providing a stack trace for the code that allocated the object, which is essentially a roadmap to the source of the problem. Visual Studio requires me to dig through the stack trace manually.

  1. Open a memory window to the address. This is a list of pointers to code addresses (32 or 64bit).
    Visual Studio D2D1 stack trace
  2. Open a disassembly window to each address in the list.
  3. Keep going until I see my code, usually the second address.
    Visual Studio D2D1 stack trace.

Incorrect Fonts

Long ago I wrote a very handy applet that enumerates all the fonts, renders sample text in each font, then prints a LOGFONT struct that can be used to create it. My system has hundreds of fonts and this lets me pick one quickly and easily. So I picked "Arial Rounded MT Bold" and was puzzled when my Direct2D app defaulted to some blocky angular font. I spent some time trying to figure this out and came to the conclusion that IDWriteTextFormat did not support it for some reason. I know the font exists, since my old Win95 GDI-based LogFont applet is able to display it just fine.

And it isn't just that font -- "Cooper Std Black" fails as well.

Update: DWrite doesn't use the same font names as GDI. See Loading DWrite Fonts



WebV7 (C)2018 nlited | Rendered by tikope in 68.094ms | 18.224.30.113