How to fix this multisampling error when creating a swapchain? - directx-11

I'm getting an DXGI ERROR about multisampling when creating a swapchain and need some help after hours of trying to resolve this error.
I'm setting up a simple window for learning Direct3D 11. I have tried changing the SampleDesc.Count and SampleDesc.Quality in the DXGI_SWAP_CHAIN_DESC1 structure, but I still get the error.
// dxgiFactory is using interface IDXGIFactory7
// d3dDevice5 is using interface ID3D11Device5
ComPtr<IDXGISwapChain1> dxgiSwapChain1;
DXGI_SWAP_CHAIN_DESC1 desc;
desc.Width = 800;
desc.Height = 400;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.Stereo = FALSE;
desc.SampleDesc.Count = 0;
desc.SampleDesc.Quality = 0;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = 3;
desc.Scaling = DXGI_SCALING_STRETCH;
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
desc.AlphaMode = DXGI_ALPHA_MODE_STRAIGHT;
desc.Flags = 0;
hr = dxgiFactory->CreateSwapChainForHwnd(d3dDevice5.Get(), hWnd, &desc, nullptr, nullptr, &dxgiSwapChain1);
Debug output:
DXGI ERROR: IDXGIFactory::CreateSwapChain: Flip model swapchains (DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL and DXGI_SWAP_EFFECT_FLIP_DISCARD) do not support multisampling.
How do I resolve this error?

TL;DR Either change your flip model to the older DXGI_SWAP_EFFECT_DISCARD or create an MSAA render target that you explicitly resolve.
Create your swap chain as one sample (i.e. no MSAA).
Create a render target texture that is using one or more samples (i.e. MSAA).
Create your render target view for your MSAA render target
Each frame, render to your MSAA render target, then ResolveSubresource to the swapchain backbuffer--or some other single-sample buffer--, then Present.
For a detailed code example, see GitHub.
You also can't create it as an DXGI_FORMAT_*_SRGB gamma-correcting swapchain with the new DXGI_SWAP_EFFECT_FLIP_* models. You can create a Render Target View that is DXGI_FORMAT_*_SRGB for a swapchain that is not sRGB to get the same effect. There's a little bit of a gotcha doing both MSAA and sRGB together with the new flip models that is fixed in Windows 10 Fall Creators Update (16299) or later.
If you were using DirectX 12, you don't have the option of using the older swap effects, so you have to implement the MSAA render target directly. Again, see GitHub.
In the 'pre-Directx 12 / Vulkan' days, DirectX made it easy to enable MSAA by doing a bunch of stuff behind the scenes for you. It would create a non-MSAA render target for display, give you back an MSAA render target for rendering, and do the resolve for you as part of the Present. It was easy, but it was also a bit wasteful.
With the new 'no magic' approach of DirectX 12, you have to do it explicitly in the application. In real games you want to do this anyhow because you usually do a lot of post-processing and want do the resolve well before Present or even do other kinds of resolve (FXAA, MLAA, SMAA). For example:
Render 3D scene to a floating-point MSAA render target
->
Resolve to a single-sample floating-point render target
->
Perform tone-mapping/color-grading/blur/bloom/etc.
->
Render UI/HUD
->
Perform HDR10 signal generation/gamma-correction/color-space warp
->
Present
As you can see from that flow, it's pretty silly to ever have the swapchain be MSAA except in toy examples or sample code.
To get a sense of just how much of a modern game is doing multiple passes of rendering, see this blog post
See DirectX Tool Kit for DX 11 and DX12
UPDATE: I covered this in detail in a recent blog post

Related

Direct2D tile limitation?

Is there a limitation on how much Direct2D tile size can grow?
In D2D1_RENDERING_CONTROLS I can set the tiling of the Direct2D buffering.
D2D1_RENDERING_CONTROLS r4c = {};
m_d2dContext->GetRenderingControls(&r4c);
if (r4c.tileSize.width < (UINT32)wi || r4c.tileSize.height < (UINT32)he)
{
r4c.tileSize.width = wi;
r4c.tileSize.height = he;
m_d2dContext->SetRenderingControls(r4c);
m_d2dContext->GetRenderingControls(&r4c);
nop();
}
When it's up to 4K resolution, the rendering of an effect gets the whole texture at one pass:
When I go up to 8K, then there's tiling, indicating that Direct2D passes the texture in more than one pass:
This is a killing for my custom HLSL effects that require the whole texture to operate, like this special lighting effect.
Is this an intended feature? A bug? An undetected issue? Would I be forced to go to OpenGL rendering eventually?
This is not related to my GF card - I tested with an RTX 2070, the same error occurs.
Edit: here is a full VS 2019 solution with a simple swirl effect.
// When you try plain 1920x1080 or 3840x2160, ok
int wi = 7680;
int he = 4320;
// With the above, the swirling is tiled x4.
Edit: On EndDraw(), I have this in debug output:
Exception thrown at 0x00007FFBE6324B89 in d2dbug.exe: Microsoft C++ exception: _com_error at memory location 0x000000EBC4D5CE58.
Exception thrown at 0x00007FFBE6324B89 in d2dbug.exe: Microsoft C++ exception: _com_error at memory location 0x000000EBC4D5CE58.
Exception thrown at 0x00007FFBE6324B89 in d2dbug.exe: Microsoft C++ exception: _com_error at memory location 0x000000EBC4D5CE58.
There is an answer from Microsoft.
I would recommend using a bitmap as the input to the effect rather
than the image source. Simply drawing the image source into that
bitmap should work. They could also create the bitmap from a WIC
bitmap source using the D2D API CreateBitmapFromWicBitmap.
This works, although the output of an effect returns a ID2D1Image which then has to be converted to a ID2D1Bitmap by the code here.
I'm still discussing with them the performance issues that might occur when there are too many effects in the chain and each of them has to convert manually to an ID2D1Bitmap.

Trouble Getting Depth Testing To Work With Apple's Metal Graphics API

I'm spending some time in the evenings trying to learn Apple's Metal graphics API. I've run into a frustrating problem and so must be missing something pretty fundamental: I can only get rendered objects to appear on screen when depth testing is disabled, or when the depth function is changed to "Greater". What could possibly be going wrong? Also, what kinds of things can I check in order to debug this problem?
Here's what I'm doing:
1) I'm using SDL to create my window. When setting up Metal, I manually create a CAMetalLayer and insert it into the layer hierarchy. To be clear, I am not using MTKView and I don't want to use MTKView. Staying away from Objective-C and Cocoa as much as possible seems to be the best strategy for writing this application to be cross-platform. The intention is to write in platform-agnostic C++ code with SDL and a rendering engine which can be swapped at run-time. Behind this interface is where all Apple-specific code will live. However, I strongly suspect that part of what's going wrong is something to do with setting up the layer:
SDL_SysWMinfo windowManagerInfo;
SDL_VERSION(&windowManagerInfo.version);
SDL_GetWindowWMInfo(&window, &windowManagerInfo);
// Create a metal layer and add it to the view that SDL created.
NSView *sdlView = windowManagerInfo.info.cocoa.window.contentView;
sdlView.wantsLayer = YES;
CALayer *sdlLayer = sdlView.layer;
CGFloat contentsScale = sdlLayer.contentsScale;
NSSize layerSize = sdlLayer.frame.size;
_metalLayer = [[CAMetalLayer layer] retain];
_metalLayer.contentsScale = contentsScale;
_metalLayer.drawableSize = NSMakeSize(layerSize.width * contentsScale,
layerSize.height * contentsScale);
_metalLayer.device = device;
_metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
_metalLayer.frame = sdlLayer.frame;
_metalLayer.framebufferOnly = true;
[sdlLayer addSublayer:_metalLayer];
2) I create a depth texture to use as a depth buffer. My understanding is that this step is necessary in Metal. Though, in OpenGL, the framework creates a depth buffer for me quite automatically:
CGSize drawableSize = _metalLayer.drawableSize;
MTLTextureDescriptor *descriptor =
[MTLTextureDescriptorr texture2DDescriptorWithPixelFormat:MTLPixelFormatDepth32Float_Stencil8 width:drawableSize.width height:drawableSize.height mipmapped:NO];
descriptor.storageMode = MTLStorageModePrivate;
descriptor.usage = MTLTextureUsageRenderTarget;
_depthTexture = [_metalLayer.device newTextureWithDescriptor:descriptor];
_depthTexture.label = #"DepthStencil";
3) I create a depth-stencil state object which will be set at render time:
MTLDepthStencilDescriptor *depthDescriptor = [[MTLDepthStencilDescriptor alloc] init];
depthDescriptor.depthWriteEnabled = YES;
depthDescriptor.depthCompareFunction = MTLCompareFunctionLess;
_depthState = [device newDepthStencilStateWithDescriptor:depthDescriptor];
4) When creating my render pass object, I explicitly attach the depth texture:
_metalRenderPassDesc = [[MTLRenderPassDescriptor renderPassDescriptor] retain];
MTLRenderPassColorAttachmentDescriptor *colorAttachment = _metalRenderPassDesc.colorAttachments[0];
colorAttachment.texture = _drawable.texture;
colorAttachment.clearColor = MTLClearColorMake(0.2, 0.4, 0.5, 1.0);
colorAttachment.storeAction = MTLStoreActionStore;
colorAttachment.loadAction = desc.clear ? MTLLoadActionClear : MTLLoadActionLoad;
MTLRenderPassDepthAttachmentDescriptor *depthAttachment = _metalRenderPassDesc.depthAttachment;
depthAttachment.texture = depthTexture;
depthAttachment.clearDepth = 1.0;
depthAttachment.storeAction = MTLStoreActionDontCare;
depthAttachment.loadAction = desc.clear ? MTLLoadActionClear : MTLLoadActionLoad;
MTLRenderPassStencilAttachmentDescriptor *stencilAttachment = _metalRenderPassDesc.stencilAttachment;
stencilAttachment.texture = depthAttachment.texture;
stencilAttachment.storeAction = MTLStoreActionDontCare;
stencilAttachment.loadAction = desc.clear ? MTLLoadActionClear : MTLLoadActionLoad;
5) Finally, at render time, I set the depth-stencil object before drawing my object:
[_encoder setDepthStencilState:_depthState];
Note that if I go into step 3 and change depthCompareFunction to MTLCompareFunctionAlways or MTLCompareFunctionGreater then I see polygons on the screen, but ordering is (expectedly) incorrect. If I leave depthCompareFunction set to MTLCompareFunctionLess then I see nothing but the background color. It acts AS IF all fragments fail the depth test at all times.
The Metal API validator reports no errors and has no warnings...
I've tried a variety of combinations of settings for things like the depth-stencil texture format and have not made any forward progress. Honestly, I'm not sure what to try next.
EDIT: GPU Frame Capture in Xcode displays a green outline of my polygons, but none of those fragments are actually drawn.
EDIT 2: I've learned that the Metal API validator has an "Extended" mode. When this is enabled, I get these two warnings:
warning: Texture Usage Should not be Flagged as MTLTextureUsageRenderTarget: This texture is not a render target. Clear the MTLTextureUsageRenderTarget bit flag in the texture usage options. Texture = DepthStencil. Texture is used in the Depth attachment.
warning: Resource Storage Mode Should be MTLStorageModePrivate and it Should be Initialized with a Blit: This resource is rarely accessed by the CPU. Changing the storage mode to MTLStorageModePrivate and initializing it with a blit from a shared buffer may improve performance. Texture = 0x102095000.
When I head these two warnings, I get these two errors. (The warnings and errors seem to contradict one another.)]
error 'MTLTextureDescriptor: Depth, Stencil, DepthStencil, and Multisample textures must be allocated with the MTLResourceStorageModePrivate resource option.'
failed assertion `MTLTextureDescriptor: Depth, Stencil, DepthStencil, and Multisample textures must be allocated with the MTLResourceStorageModePrivate resource option.'
EDIT 3: When I run a sample Metal app and use the GPU frame capture tool then I see a gray scale representation of the depth buffer and the rendered object is clearly visible. This doesn't happen for my app. There, the GPU frame capture tool always shows my depth buffer as a plain white image.
Okay, I figured this out. I'm going to post the answer here to help the next guy. There was no problem writing to the depth buffer. This explains why spending time mucking with depth texture and depth-stencil-state settings was getting me nowhere.
The problem is differences in the coordinate systems used for Normalized Device Coordinates in Metal versus OpenGL. In Metal, NDC are in the space [-1,+1]x[-1,+1]x[0,1]. In OpenGL, NDC are [-1,+1]x[-1,+1]x[-1,+1]. If I simply take the projection matrix produced by glm::perspective and shove it through Metal then results will not be as expected. In order to compensate for the NDC space differences when rendering with Metal, that projection matrix must be left-multiplied by a scaling matrix with (1, 1, 0.5, 1) on the diagonal.
I found these links to be helpful:
1. http://blog.athenstean.com/post/135771439196/from-opengl-to-metal-the-projection-matrix
2. http://www.songho.ca/opengl/gl_projectionmatrix.html
EDIT: Replaced explanation with a more complete and accurate explanation. Replace solution with a better solution.

DirectX to OpenGL hot-swap, doesn't display on Win32 window

During the developement of my engine, I'm trying to implement a feature, that enables hot-swapping between OpenGL and DirectX. Currently I'm testing on Win32 platform, and came across the following problem:
I implemented both renderer (OpenGL 3.0, and Direct3D11), both work fine alone. The swapping mechanism is the following:
Destroy the current rendering context, and build up the new one. For example: Release all DirectX objects, and then create OpenGL context, via WGL. I'm trying to implement this, using only one window (HWND).
Swapping from OpenGL 3.0 to DirectX11 works. (After destroying OpenGL, DirectX renders fine)
Destroying OpenGL and then recreating OpenGL again, works. Same with DirectX.
When I'm trying to swap from DirectX to OpenGL, the window will stop displaying the newly draw content, only the lastly drawn DirectX frame.
To construct the OpenGL context I'm using WGL. The class for the window was created with the CS_OWNDC style. I'm using SwapBuffers to flip the window buffers. Before setting up the context, I use SetPixelFormat with the previously returned value from ChoosePixelFormat. The created context is version 3.0, ensured via wglCreateContextAttribsARB.
Additional information:
All of the DirectX references are released, this was checked by calling ReportLiveDeviceObjects and checking the return value of ID3D11Device1::Release (0). ID3D11DeviceContext1::ClearState and Flush were called to ensure object destruction.
None of the OpenGL methods report error via glGetError, this is checked after every call. This is same for all OS, and WGL calls.
The OpenGL rendering calls are executing as expected, for example:
OpenGL rendering with 150 fps
Swap to DirectX, render with 60 fps (VSYNC)
Swap back to OpenGL, rendering again with 150 fps (not more)
There are other scenarios where OpenGL renders with more than 150 fps, so the rendering calls are executing properly.
My guess is that the flipping of the buffers doesn't work somehow, however SwapBuffers returns TRUE anyway.
I tried using SaveDC and RestoreDC before and after using DirectX, this resulted in now solution.
Using wglSwapLayerBuffers instead of SwapBuffers gives no change.
Can I somehow restore the HWND, or HDC to the original state, or do you guys have any idea why this might happen?
Guess I posted my question to soon, but however, this is how I solved it.
I dug around the documnentation for DirectX, and for the function CreateSwapChainForHwnd, I found the following:
Because you can associate only one flip presentation model swap chain at a time with an HWND, the Microsoft Direct3D 11 policy of deferring the destruction of objects can cause problems if you attempt to destroy a flip presentation model swap chain and replace it with another swap chain.
I was using DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL in my swap chain descriptor, and this could mean, that DirectX sets up a flip swap chain for the window, but when I try to use it with OpenGL, it will fail swapping buffers somehow.
The solution for this, is to not use FLIP mode for creating the swap chain:
DXGI_SWAP_CHAIN_DESC1 scd;
scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
scd.Scaling = DXGI_SCALING_ASPECT_RATIO_STRETCH;
You have to set the Scaling to something else than DXGI_SCALING_NONE, or the creation will fail.
The interesting part is, that the DirectX still does not properly destroy the flip model on the window, altough I did everything it suggested in the documentation (ClearState and Flush calls).
CreateSwapChainForHwnd see Remarks
Edit: I found this question after some time. If anybody still has some idea, how to revert back to using GDI again instead of the DWM backbuffer, it is greatly appreciated.

Setting up a Win32 OpenGL Window with a GL_RGBA Color Buffer

I am trying to set up an OpenGL window with an alpha channel in its color buffer. Unfortunately, my current setup is creating a GL_RGB back and front buffer (as reported by gDEBugger, and as shown by my experiments).
I set up the window like so:
PIXELFORMATDESCRIPTOR pfd;
ZeroMemory(&pfd,sizeof(pfd));
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24; //note by docs that alpha doesn't go here (though putting 32 changes nothing)
pfd.cDepthBits = 24;
pfd.iLayerType = PFD_MAIN_PLANE;
I have also tried more specifically:
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
24,
8,0, 8,8, 8,16, 8,24, //A guess for lil endian; usually these are 0 (making them 0 doesn't help)
0, 0,0,0,0,
24, //depth
8, //stencil
0, //aux
PFD_MAIN_PLANE,
0,
0, 0, 0
};
My understanding is that when I call ChoosePixelFormat later, it's returning a format without an alpha channel (though why, I don't know).
I should clarify that when I say alpha buffer, I mean just a simple alpha buffer for rendering purposes (each color has an alpha value that fragments can be tested against, and so on). I do NOT mean a semi-transparent window or some other effect. [EDIT: So no, I am not at this time interested in making the window itself transparent. I just want an alpha channel for the default framebuffer.]
[EDIT: This is part of a cross-platform windowing backend I'm writing. My code is always portable. I am not using a library that provides this functionality since I need more control.]
There are two key points to consider here:
You can do this with the default framebuffer. However, you need to request it properly. Windows's default selection mechanism doesn't seem to weight having RGBA very highly. The best course seems to be to enumerate all possible pixelmodes and then select the one you want "manually" as it were. By doing this, I was also able to specify that I wanted a 24 bit depth buffer, an accumulation buffer, and an 8-bit stencil buffer.
The OpenGL context must be fully valid for alpha blending, depth testing, and any even remotely advanced techniques to work. Curiously, I was able to get rendered triangles without having a fully valid OpenGL context, leading to my confusion! Maybe it was being emulated in software? I figured it out when glewInit (not to mention making a few VBOs) failed miserably. The key point is that the OpenGL context must be valid. In my case, I wasn't setting it up properly.
This problem was in the context of my currently writing a lightweight cross-platform windowing toolkit. Currently, it supports Windows through the Win32 API, Linux through X11, and I just started porting to Mac OS through X11.
At the request of some in the comments, I hereby present my humble effort thereof that others may benefit. Mac support doesn't work yet, menus aren't implemented on Linux, and user input on Linux is only half-there. As of 1/18/2013, it may temporarily be found here (people of the future, I may have put new versions on my website (look for "Portcullis")).

GL_INVALID_VALUE from glFramebufferTexture2DEXT only after delete/realloc texture

I have some C# (SharpGL-esque) code which abstracts OpenGL frame buffer handling away to simple "set this texture as a 'render target'" calls. When a texture is first set as a render target, I create an FBO with matching depth buffer for that size of texture; that FBO/depth-buffer combo will then be reused for all same-sized textures.
I have a curious error as follows.
Initially the app runs and renders fine. But if I increase my window size, this can cause some code to need to resize its 'render target' texture, which it does via glDeleteTextures() and glGenTextures() (then bind, glTexImage2D, and texparams so MIN_FILTER and MAG_FILTER are both GL_NEAREST). I've observed I tend to get the same name (ID) back when doing so (as GL reuses the just-freed name).
We then hit the following code (with apologies for the slightly bastardised GL-like syntax):
void SetRenderTarget(Texture texture)
{
if (texture != null)
{
var size = (texture.Width << 16) | texture.Height;
FrameBufferInfo info;
if (!_mapSizeToFrameBufferInfo.TryGetValue(size, out info))
{
info = new FrameBufferInfo();
info.Width = texture.Width;
info.Height = texture.Height;
GL.GenFramebuffersEXT(1, _buffer);
info.FrameBuffer = _buffer[0];
GL.BindFramebufferEXT(GL.FRAMEBUFFER_EXT, info.FrameBuffer);
GL.FramebufferTexture2DEXT(GL.FRAMEBUFFER_EXT, GL.COLOR_ATTACHMENT0_EXT, GL.TEXTURE_2D, texture.InternalID, 0);
GL.GenRenderbuffersEXT(1, _buffer);
info.DepthBuffer = _buffer[0];
GL.BindRenderBufferEXT(GL.RENDERBUFFER_EXT, info.DepthBuffer);
GL.RenderbufferStorageEXT(GL.RENDERBUFFER_EXT, GL.DEPTH_COMPONENT16, texture.Width, texture.Height);
GL.BindRenderBufferEXT(GL.RENDERBUFFER_EXT, 0);
GL.FramebufferRenderbufferEXT(GL.FRAMEBUFFER_EXT, GL.DEPTH_ATTACHMENT_EXT, GL.RENDERBUFFER_EXT, info.DepthBuffer);
_mapSizeToFrameBufferInfo.Add(size, info);
}
else
{
GL.BindFramebufferEXT(GL.FRAMEBUFFER_EXT, info.FrameBuffer);
GL.FramebufferTexture2DEXT(GL.FRAMEBUFFER_EXT, GL.COLOR_ATTACHMENT0_EXT, GL.TEXTURE_2D, texture.InternalID, 0);
}
GL.CheckFrameBufferStatus(GL.FRAMEBUFFER_EXT);
}
else
{
GL.FramebufferTexture2DEXT(GL.FRAMEBUFFER_EXT, GL.COLOR_ATTACHMENT0_EXT, GL.TEXTURE_2D, 0, 0);
GL.BindFramebufferEXT(GL.FRAMEBUFFER_EXT, 0);
}
ProjectStandardOrthographic();
}
After said window resize, GL returns a GL_INVALID_VALUE error from the glFramebufferTexture2DEXT() call (identified with glGetError() and gDEBugger). If I ignore this, glCheckFrameBufferStatus() later fails with GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT. If I ignore this too, I can see the expected "framebuffer to dubious to do anything" errors if I check for them and a black screen if I don't.
I'm running on an NVidia GeForce GTX 550 Ti, Vista 64 (32 bit app), 306.97 drivers. I'm using GL 3.3 with the Core profile.
Workaround and curiosity: If when rellocating textures I glGenTextures() before glDeleteTextures() - to avoid getting the same ID back - the problem goes away. I don't want to do this as it's a stupid kluge and increases my chances of out of memory errors. I'm theorising it's because GL was/is using the texture in a recent FBO and now has decided that texture ID is in use or is no longer valid in some way and so isn't acceptable? Maybe?
After the problem gDEBugger shows that both FBOs (the original one with the smaller depth buffer and previous texture, and the new one with the larger combination) have the same texture ID attached.
I've tried detaching the texture from the frame buffer (via glFramebufferTexture2DEXT again) before deallocation, but to no avail (gDEBuffer reflects the change but the problem still occurs). I've tried taking out the depth buffer entirely. I've tried checking the texture sizes via glGetTexLevelParameter() before I use it; it does indeed exist.
This sounds like a bug in NVIDIA's OpenGL implementation. Once you delete an object name, that object name becomes invalid, and thus should be a legitimate candidate for glGen* to return.
You should file a bug report, with a minimal case that reproduces the issue.
I don't want to do this as it's a stupid kluge and increases my chances of out of memory errors.
No, it doesn't. glGenTextures doesn't allocate storage for textures (which is where any real OOM errors might come from). It only creates the texture name. It's unfortunate that you have to use a workaround, but it's not any real concern.

Resources