Problem z multisamplingiem w DirectX11


#1

Witam :slight_smile:

Dawno tu nie zaglądałem. Pojawił się pewien problem u mnie przy tworzeniu biblioteki umożliwiającej odtwarzanie video jako tekstura na dowolnym modelu. Używam w tym celu IMFSourceReader. Ale w zasadzie nie o tym chciałem pisać tylko o tym, że wyszedł przy testowaniu tej biblioteki pewien szkopuł.

Problem dotyczy multisamplowania. Gdy ustawiam w DXGI_SWAP_CHAIN_DESC sampleDesc.Count na większe od 1 i quality większe od 0, np. para 4/16 lub 8/32 to D3D11CreateDeviceAndSwapChain wywala błąd _com_error at memory location.

Oczywiście aplikacja mimo to działa dalej i niczego bym nie zauważył, gdyby nie fakt, że przy użyciu mojej biblioteki gdy film wyświetla się jako tekstura na modelu - strasznie laguje środowisko 3D ale co ciekawe film leci płynnie.

Męczyłem się z tym kilka tygodni bo nie wiedziałem w czym rzecz i winę niesłusznie zrzucałem na bibliotekę odtwarzającą video jako teksturę. Okazuje się, że gdy wyłączę multisampling tzn. ustawię sampleDesc.Count=1 i sampleDesc.Quality=0 to problem znika. Wszystko działa płynnie, a wywołanie D3D11CreateDeviceAndSwapChain nie zwraca już żadnego błędu.

Dziś odkryłem kolejną rzecz. Początkowo myślałem, że po prostu włączenie multisamplingu muli aplikację przez video-teksturę - zwyczajnie komputer nie daje rady ale okazuje się że nie. Wyłączyłem multisamling w aplikacji ustawiając sampleDesc.Count=1 i sampleDesc.Quality=0, natomiast włączyłem multisampling w ustawieniach karty graficznej. Na maxa czyli 8/32.

Wchodzę i co widzę?

  • film odtwarza pięknie jako teksturę,
  • krawędzie obiektów są wygładzone, a więc multisampling działa,
  • i w dodatku wszystko chodzi płynnie!

WTF!?!?!

Wiem już, że problem dotyczy ustawienia sampleDesc.Quality i sampleDesc.Count i wywołania D3D11CreateDeviceAndSwapChain czyli włączenia multisamplingu z poziomu aplikacji.

Co może być nie tak? Ktoś miał taki przypadek?
Dlaczego D3D11CreateDeviceAndSwapChain zwraca _com_error przy sampleDesc.Quality i sampleDesc.Count większych od 1?


#2

Cześć jestem nowy na tym forum, miałem podobny problem jakieś dwa dni temu, korzystasz z C++/CLI oraz aplikacji UWP ?

Przede wszystkim jeżeli tak jest to powinieneś sprawdzić jaki poziom Quality obsługuje Tobie karta graficzna przy danym multisamplingu.

np. tak:
UINT m4xMsaaQuality; dev->CheckMultisampleQualityLevels(DXGI_FORMAT_B8G8R8A8_UNORM,8, &m4xMsaaQuality);

następnie musisz ogarnąć.
Jaki format interfejsu obsługiwany jest twoją kartą graficzną:

	for (UINT i = 1; i < DXGI_FORMAT_MAX; i++)
	{
		DXGI_FORMAT inFormat = safe_cast<DXGI_FORMAT>(i);
		UINT formatSupport = 0;
		HRESULT hr = dev->CheckFormatSupport(inFormat, &formatSupport);

		if ((formatSupport & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RESOLVE) &&
			(formatSupport & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET)
			)
		{
			m_supportInfo->SetFormatSupport(i, true);
		}
		else
		{
			m_supportInfo->SetFormatSupport(i, false);
		}
	}

przyda Ci się do tego klasa pomocnicza:

private ref class MultisamplingSupportInfo sealed
{
internal:
	MultisamplingSupportInfo()
	{
		ZeroMemory(&sampleSizeData, sizeof(sampleSizeData));
		ZeroMemory(&qualityFlagData, sizeof(qualityFlagData));
	};

public:
	unsigned int GetSampleSize(int i, int j) { return sampleSizeData[i][j]; };
	void         SetSampleSize(int i, int j, unsigned int value) { sampleSizeData[i][j] = value; };

	unsigned int GetQualityFlagsAt(int i, int j) { return qualityFlagData[i][j]; };
	void         SetQualityFlagsAt(int i, int j, unsigned int value) { qualityFlagData[i][j] = value; };

	bool         GetFormatSupport(int format) { return formatSupport[format]; };
	void         SetFormatSupport(int format, bool value) { formatSupport[format] = value; };

	unsigned int GetFormat() { return m_format; };
	void         SetFormat(unsigned int i) { m_format = (DXGI_FORMAT)i; };

	unsigned int GetLargestSampleSize() { return m_largestSampleSize; };
	unsigned int GetSmallestSampleSize() { return m_smallestSampleSize; };

	void SetLargestSampleSize(unsigned int value) { m_largestSampleSize = value; };
	void SetSmallestSampleSize(unsigned int value) { m_smallestSampleSize = value; };

private:
	unsigned int sampleSizeData[DXGI_FORMAT_MAX][MAX_SAMPLES_CHECK];
	unsigned int qualityFlagData[DXGI_FORMAT_MAX][MAX_SAMPLES_CHECK];

	bool formatSupport[DXGI_FORMAT_MAX];

	unsigned int m_largestSampleSize;
	unsigned int m_smallestSampleSize;

	DXGI_FORMAT m_format;
};

no jak już uda się wypełnić tablice: formatSupport[]

to wypada teraz zobaczyć, jaki sampleSize ustawiony jest dla danego formatu i znowu pętla:

`for (unsigned int i = 0; i < DXGI_FORMAT_MAX; i++){
       for (unsigned int j = 1; j < MAX_SAMPLES_CHECK; j++){
        UINT numQualityFlags;

        HRESULT test = dev->CheckMultisampleQualityLevels(
        (DXGI_FORMAT)i,
        j,
        &numQualityFlags
        );

        if (SUCCEEDED(test) && (numQualityFlags > 0)){
        	m_supportInfo->SetSampleSize(i, j, 1);
        	m_supportInfo->SetQualityFlagsAt(i, j, numQualityFlags);
        }
    }
}`

No i teraz jest najgorszy problem gdyż aby użyć Mx4SAA, powinieneś użyć : DXGI_SWAP_EFFECT_DISCARD jako swapEffect, no ale niestety miedzy UWP (czli WinRT) a Win32 są drobne różnice. Powołując sie na MSDN:

The recommended approach is to manually convert DX11 Discard swap chains to use flip models within UWP, using DXGI_SWAP_EFFECT_FLIP_DISCARD instead of DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL where possible.

no dobra koniec końców jakie jest rozwiązanie? Należy stworzyć swapchain i na podstawie tego backbuffer, który nie obsługuje multisamplingu czyli taki:

`DXGI_SWAP_CHAIN_DESC1 scd = { 0 };
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;    // how the swap chain should be used
scd.BufferCount =2;      
	
scd.Format = DXGI_FORMAT_B8G8R8A8_UNORM;              // the most common swap chain format
scd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;    // the recommended flip mode
scd.SampleDesc.Count = 1;                             // disable anti-aliasing
scd.SampleDesc.Quality = 0;

CoreWindow^ Window = CoreWindow::GetForCurrentThread();    // get the window pointer

swapchain = nullptr;										   // create the swap chain
dxgiFactory->CreateSwapChainForCoreWindow(
dev.Get(),                                  // address of the device
reinterpret_cast<IUnknown*>(Window),        // address of the window
&scd,                                       // address of the swap chain description
nullptr,                                    // advanced
&swapchain);                                // address of the new swap chain pointer
	
// get a pointer directly to the back buffer
ComPtr<ID3D11Texture2D> backbuffer;
swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (&backbuffer));`

tworzysz backbuffer:

`CD3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc(D3D11_RTV_DIMENSION_TEXTURE2DMS);			
// create a render target pointing to the back buffer
dev->CreateRenderTargetView(backbuffer.Get(), &renderTargetViewDesc, &rendertarget);`	

a następnie tworzysz surface , który jednak będzie obsługiwał multisampling

`CoreWindow^ Window = CoreWindow::GetForCurrentThread();
D3D11_TEXTURE2D_DESC offScreenSurfaceDesc;
ZeroMemory(&offScreenSurfaceDesc, sizeof(D3D11_TEXTURE2D_DESC));
offScreenSurfaceDesc.Width = Window->Bounds.Width;
offScreenSurfaceDesc.Height = Window->Bounds.Height;
offScreenSurfaceDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
offScreenSurfaceDesc.BindFlags = D3D11_BIND_RENDER_TARGET;
offScreenSurfaceDesc.MipLevels = 1;
offScreenSurfaceDesc.ArraySize = 1;
offScreenSurfaceDesc.SampleDesc.Count = 8;
offScreenSurfaceDesc.SampleDesc.Quality = m4xMsaaQuality - 1;

dev->CreateTexture2D(
	&offScreenSurfaceDesc,
	nullptr,
	&m_offScreenSurface);`

a następnie podmienić pod backbuffer (renderTarget się nie zmieni)

unsigned int sub = D3D11CalcSubresource(0, 0, 1); devcon->ResolveSubresource( backbuffer.Get(), sub, m_offScreenSurface.Get(), sub, DXGI_FORMAT_B8G8R8A8_UNORM );

To by było tyle, jeżeli chciałbyś zobaczyć kompletny przykład to jest tutaj https://github.com/toch88/UWP_DirectX11_Multisampling_Enable wyświetla “Triangle” z włączonym 8 krotnym samplingiem. W tym przykładzie jest jeszcze jeden problem w momencie gdy pierwszy raz włączysz aplikacje - pojawi ci się w małym okienku, gdy będziesz chciał go powiększyć będziesz widział wyraźne schodki, jak włączysz i wyłączysz włączysz z maksymalizacją to będzie wszystko ok. muszę popracować z Resizem.

Co prawda jest początkujący w temacie, ale jest to jakiś punkt wyjścia, chyba w polskim “community” nie wiele jest ludzi którzy się tym zajmują, wolą gotowe rozwiązania jak Unity.


#3

Witaj
Lipiec 2016, marzec 2017 i teraz czerwiec 2018 :smiley: nie można tego tematu zostawić tak bez odpowiedzi :stuck_out_tongue:

Po dłuższej przerwie wróciłem znów do tworzenia silnika gier. Więc przyjrzałem się Twojej odpowiedzi. Po pierwsze ja piszę w natywnym C++ x64 bez UWP.

Problem który tu opisałem już dawno zażegnałem bowiem o ile pamiętam ustawienia tekstury dla wyświetlania próbek wideo miały złe ustawienia.

Zastanowił mnie jednak Twój kod, który tu podałeś. U mnie w silniku wygląda to następująco:

  • tworzę listę multisamplingu coś w tym sensie:
for (int i = 2; i <= D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT; ++i) {
    m_device->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, i, &numQualityLevels);
    ...
}

Tu gdzie trzy kropki nadaje różne nazwy trybów multisamplingu w zależności od tego jakiego producenta jest karta graficzna wykorzystując:

if (adapterDesc.VendorId == 4318) { // NVIDIA

}

Kiedy mam już listę trybów multisamplingu, odszukuje ten który wczytuje z pliku konfiguracyjnego i stosuję go w łańcuchu wymiany i buforze głębi.

swapChainDesc.SampleDesc.Count = GLOBAL.MULTISAMPLING_COUNT;
swapChainDesc.SampleDesc.Quality = GLOBAL.MULTISAMPLING_QUALITY;

depthBufferDesc.SampleDesc.Count = GLOBAL.MULTISAMPLING_COUNT;
depthBufferDesc.SampleDesc.Quality = GLOBAL.MULTISAMPLING_QUALITY;

Wszystko działa dobrze.

Zauważyłem jednak, że sprawdzanie trybów multisamplingu wykonuje wyłącznie z formatem DXGI_FORMAT_R8G8B8A8_UNORM. Natomiast bufor głębi i szablon widoku głębi używa formatu DXGI_FORMAT_D24_UNORM_S8_UINT.

I zastanawia mnie, czy konieczne jest sprawdzenie poziomów jakości we wszystkich używanych formatach?

Bowiem za pomocą CheckFormatSupport sprawdzamy czy dany format może być użyty jako multisamplingowy cel renderowania (D3D11_FORMAT_SUPPORT_MULTISAMPLE_RENDERTARGET) oraz czy można na danym formacie wykonywać operację rozwiązania przy użyciu metody ResolveSubresource (D3D11_FORMAT_SUPPORT_MULTISAMPLE_RESOLVE).

Powinno się jeszcze sprawdzić pod kątem użycia tekstury danego formatu do odczytania w shaderze z funkcją ładowania HLSL (D3D11_FORMAT_SUPPORT_MULTISAMPLE_LOAD).

Jeżeli chodzi o format DXGI_FORMAT_D24_UNORM_S8_UINT w buforze głębi to powinno się go sprawdzić jako D3D11_FORMAT_SUPPORT_DEPTH_STENCIL.

Na tej stronie https://msdn.microsoft.com/en-us/library/windows/desktop/ff471325(v=vs.85).aspx jest lista obsługiwanych formatów przez DirectX 11. Dalej mamy formaty obsługiwane w MSAA 4x i 8x.

Jeżeli jednak CheckMultisampleQualityLevels na formacie DXGI_FORMAT_R8G8B8A8_UNORM zwraca pewne wartości przy próbkowaniu 2x, 4x, 8x w przypadku mojej karty, to nie sądzę by inne formaty nie były również w tym próbkowaniu obsługiwane.

Znalazłem również zapis w MSDN, że:

FEATURE_LEVEL_11_0 devices are required to support 4x MSAA for all render target formats, and 8x MSAA for all render target formats except R32G32B32A32 formats.

Pytanie czy rzeczywiście sprawdzanie wszystkich używanych formatów pod kątem wsparcia multisamplingu jest konieczne? Czy wystarczy sprawdzenie formatu DXGI_FORMAT_R8G8B8A8_UNORM ?