Search code examples
c++directxdirectx-11vertex-buffer

DirectX 11 Texture Flicker


enter image description here

If you look at the attached gif, specifically the circle (might have to zoom it to see the issue), there's a strange effect happening. It's like the pixels are being changed slightly as the texture is being translated. I'm not sure why. As with the blue line in the centre of the square, it seems to be moving back and forth slightly. I'm very new to DirectX and so have no idea what could be causing this.

DirectX setup code:

// Create a DirectX graphics interface factory.
result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
Error::ErrorCheck(result, TEXT("CreateDXGIFactory()"));

// Use the factory to create an adapter for the primary graphics interface (video card).
result = factory->EnumAdapters(0, &adapter);
Error::ErrorCheck(result, TEXT("factory->EnumAdapters()"));

// Enumerate the primary adapter output (monitor).
result = adapter->EnumOutputs(0, &adapterOutput);
Error::ErrorCheck(result, TEXT("adapter->EnumOutputs()"));

// Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor).
result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
Error::ErrorCheck(result, TEXT("adapterOutput->GetDisplayModeList()"));

// Create a list to hold all the possible display modes for this monitor/video card combination.
displayModeList = new DXGI_MODE_DESC[numModes];

// Now fill the display mode list structures.
result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
Error::ErrorCheck(result, TEXT("adapterOutput->GetDisplayModeList()"));

// Now go through all the display modes and find the one that matches the screen width and height.
// When a match is found store the numerator and denominator of the refresh rate for that monitor.
for(i=0; i<numModes; i++)
{
    if(displayModeList[i].Width == (unsigned int)screenWidth)
    {
        if(displayModeList[i].Height == (unsigned int)screenHeight)
        {
            numerator = displayModeList[i].RefreshRate.Numerator;
            denominator = displayModeList[i].RefreshRate.Denominator;
        }
    }
}

// Get the adapter (video card) description.
result = adapter->GetDesc(&adapterDesc);
Error::ErrorCheck(result, TEXT("adapter->GetDesc()"));

// Store the dedicated video card memory in megabytes.
m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024);

// Convert the name of the video card to a character array and store it.
error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128);

// Release the display mode list.
delete [] displayModeList;
displayModeList = 0;

// Release the adapter output.
adapterOutput->Release();
adapterOutput = 0;

// Release the adapter.
adapter->Release();
adapter = 0;

// Release the factory.
factory->Release();
factory = 0;

// Initialize the swap chain description.
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

// Set to a single back buffer.
swapChainDesc.BufferCount = 1;

// Set the width and height of the back buffer.
swapChainDesc.BufferDesc.Width = screenWidth;
swapChainDesc.BufferDesc.Height = screenHeight;

// Set regular 32-bit surface for the back buffer.
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

// Set the refresh rate of the back buffer.
if(m_vsync_enabled)
{
    swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
    swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator;
}
else
{
    swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
    swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
}

// Set the usage of the back buffer.
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

// Set the handle for the window to render to.
swapChainDesc.OutputWindow = hwnd;

// Turn multisampling off.
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;

// Set to full screen or windowed mode.
if(fullscreen)
{
    swapChainDesc.Windowed = false;
}
else
{
    swapChainDesc.Windowed = true;
}

// Set the scan line ordering and scaling to unspecified.
swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

// Discard the back buffer contents after presenting.
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

// Don't set the advanced flags.
swapChainDesc.Flags = 0;

// Set the feature level to DirectX 11.
featureLevel = D3D_FEATURE_LEVEL_11_0;

// Create the swap chain, Direct3D device, and Direct3D device context.
result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1, 
                       D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext);
Error::ErrorCheck(result, TEXT("D3D11CreateDeviceAndSwapChain()"));

// Get the pointer to the back buffer.
result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr);
Error::ErrorCheck(result, TEXT("m_swapChain->GetBuffer()"));

// Create the render target view with the back buffer pointer.
result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView);
Error::ErrorCheck(result, TEXT("m_swapChain->GetBuffer()"));

// Release pointer to the back buffer as we no longer need it.
backBufferPtr->Release();
backBufferPtr = 0;

// Initialize the description of the depth buffer.
ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));

// Set up the description of the depth buffer.
depthBufferDesc.Width = screenWidth;
depthBufferDesc.Height = screenHeight;
depthBufferDesc.MipLevels = 1;
depthBufferDesc.ArraySize = 1;
depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthBufferDesc.SampleDesc.Count = 1;
depthBufferDesc.SampleDesc.Quality = 0;
depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthBufferDesc.CPUAccessFlags = 0;
depthBufferDesc.MiscFlags = 0;

// Create the texture for the depth buffer using the filled out description.
result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer);
Error::ErrorCheck(result, TEXT("m_device->CreateTexture2D()"));

// Initialize the description of the stencil state.
ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));

// Set up the description of the stencil state.
depthStencilDesc.DepthEnable = true;
depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;

depthStencilDesc.StencilEnable = true;
depthStencilDesc.StencilReadMask = 0xFF;
depthStencilDesc.StencilWriteMask = 0xFF;

// Stencil operations if pixel is front-facing.
depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

// Stencil operations if pixel is back-facing.
depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

// Create the depth stencil state.
result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState);
Error::ErrorCheck(result, TEXT("m_device->CreateDepthStencilState()"));

// Set the depth stencil state.
m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);

// Initailze the depth stencil view.
ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));

// Set up the depth stencil view description.
depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
depthStencilViewDesc.Texture2D.MipSlice = 0;

// Create the depth stencil view.
result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView);
Error::ErrorCheck(result, TEXT("m_device->CreateDepthStencilView()"));

// Bind the render target view and depth stencil buffer to the output render pipeline.
m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);

// Setup the raster description which will determine how and what polygons will be drawn.
rasterDesc.AntialiasedLineEnable = false;
rasterDesc.CullMode = D3D11_CULL_NONE;
rasterDesc.DepthBias = 0;
rasterDesc.DepthBiasClamp = 0.0f;
rasterDesc.DepthClipEnable = true;
rasterDesc.FillMode = D3D11_FILL_SOLID;
rasterDesc.FrontCounterClockwise = false;
rasterDesc.MultisampleEnable = false;
rasterDesc.ScissorEnable = false;
rasterDesc.SlopeScaledDepthBias = 0.0f;

// Create the rasterizer state from the description we just filled out.
result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState);
Error::ErrorCheck(result, TEXT("m_device->CreateRasterizerState()"));

// Now set the rasterizer state.
m_deviceContext->RSSetState(m_rasterState);

// Setup the viewport for rendering.
viewport.Width = (float)screenWidth;
viewport.Height = (float)screenHeight;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;

// Create the viewport.
m_deviceContext->RSSetViewports(1, &viewport);

Texture Vertex Setup/Update code

void Bitmap::InitializeBuffers(ID3D11Device* device) {
VertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;
int i;

// Set the number of vertices in the vertex array.
m_vertexCount = 6;

// Set the number of indices in the index array.
m_indexCount = m_vertexCount;

// Create the vertex array.
vertices = new VertexType[m_vertexCount];

// Create the index array.
indices = new unsigned long[m_indexCount];

// Initialize vertex array to zeros at first.
memset(vertices, 0, (sizeof(VertexType) * m_vertexCount));

// Load the index array with data.
for(i=0; i<m_indexCount; i++)
{
    indices[i] = i;
}

// Set up the description of the static vertex buffer.
vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;

// Give the subresource structure a pointer to the vertex data.
vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;

// Now create the vertex buffer.
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
Error::ErrorCheck(result, TEXT("CreateBuffer()"));

// Set up the description of the static index buffer.
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;

// Give the subresource structure a pointer to the index data.
indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;

// Create the index buffer.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
Error::ErrorCheck(result, TEXT("CreateBuffer()"));

// Release the arrays now that the vertex and index buffers have been created and loaded.
delete [] vertices;
vertices = 0;

delete [] indices;
indices = 0;
}

void Bitmap::Render(ID3D11DeviceContext* deviceContext, int positionX, int positionY, bool flipped) {
// Re-build the dynamic vertex buffer for rendering to possibly a different location on the screen.
UpdateBuffers(deviceContext, positionX, positionY, flipped);

// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
RenderBuffers(deviceContext);
}

void Bitmap::UpdateBuffers(ID3D11DeviceContext* deviceContext, int positionX, int positionY, bool flipped) {
int left, right, top, bottom;
VertexType* vertices;
D3D11_MAPPED_SUBRESOURCE mappedResource;
VertexType* verticesPtr;
HRESULT result;

// If the position we are rendering this bitmap to has not changed then don't update the vertex buffer since it
// currently has the correct parameters.
if((positionX == m_previousPosX) && (positionY == m_previousPosY)) {
    if(m_flipped == flipped) {
        return;
    }
}

// If it has changed then update the position it is being rendered to.
m_previousPosX = positionX;
m_previousPosY = positionY;

// Calculate the screen coordinates of the left side of the bitmap.
left = ((m_screenWidth / 2) * -1) + positionX;

// Calculate the screen coordinates of the right side of the bitmap.
right = left + m_bitmapWidth;

// Calculate the screen coordinates of the top of the bitmap.
top = (m_screenHeight / 2) - positionY;

// Calculate the screen coordinates of the bottom of the bitmap.
bottom = top - m_bitmapHeight;

// Create the vertex array.
vertices = new VertexType[m_vertexCount];

// Load the vertex array with data.
if(!flipped) {
    // First triangle.
    vertices[0].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    vertices[0].texture = D3DXVECTOR2(0.0f, 0.0f);

    vertices[1].position = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
    vertices[1].texture = D3DXVECTOR2(1.0f, 1.0f);

    vertices[2].position = D3DXVECTOR3(left, bottom, 0.0f);  // Bottom left.
    vertices[2].texture = D3DXVECTOR2(0.0f, 1.0f);

    // Second triangle.
    vertices[3].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    vertices[3].texture = D3DXVECTOR2(0.0f, 0.0f);

    vertices[4].position = D3DXVECTOR3(right, top, 0.0f);  // Top right.
    vertices[4].texture = D3DXVECTOR2(1.0f, 0.0f);

    vertices[5].position = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
    vertices[5].texture = D3DXVECTOR2(1.0f, 1.0f);
} else {
    // First triangle.
    vertices[0].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    vertices[0].texture = D3DXVECTOR2(1.0f, 0.0f);

    vertices[1].position = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
    vertices[1].texture = D3DXVECTOR2(0.0f, 1.0f);

    vertices[2].position = D3DXVECTOR3(left, bottom, 0.0f);  // Bottom left.
    vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f);

    // Second triangle.
    vertices[3].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    vertices[3].texture = D3DXVECTOR2(1.0f, 0.0f);

    vertices[4].position = D3DXVECTOR3(right, top, 0.0f);  // Top right.
    vertices[4].texture = D3DXVECTOR2(0.0f, 0.0f);

    vertices[5].position = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
    vertices[5].texture = D3DXVECTOR2(0.0f, 1.0f);
}

m_flipped = flipped;

// Lock the vertex buffer so it can be written to.
result = deviceContext->Map(m_vertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
Error::ErrorCheck(result, TEXT("deviceContext->Map()"));

// Get a pointer to the data in the vertex buffer.
verticesPtr = (VertexType*)mappedResource.pData;

// Copy the data into the vertex buffer.
memcpy(verticesPtr, (void*)vertices, (sizeof(VertexType) * m_vertexCount));

// Unlock the vertex buffer.
deviceContext->Unmap(m_vertexBuffer, 0);

// Release the vertex array as it is no longer needed.
delete [] vertices;
vertices = 0;
}

void Bitmap::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
unsigned int stride;
unsigned int offset;

// Set vertex buffer stride and offset.
stride = sizeof(VertexType); 
offset = 0;

// Set the vertex buffer to active in the input assembler so it can be rendered.
deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

// Set the index buffer to active in the input assembler so it can be rendered.
deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}

Solution

  • Your problem is that you are not considering the real center of a texel. It is not at the corner but at his center.

    Let's say you have a 256x256 texture, to read the top left pixel, the texture coordinate to use is not float2(0,0) but float2(0.5,0.5)/256.f and the bottom right is float2(255.5,255.5)/256.f.

    Now, in the chain geometry > vertex shader with projection > viewport > pixel shader. You can apply the offset in different ways.

    We could add the half texel offset in the pixel shader but it would means send the value in a constant or use GetDimensions that is quite bad. You can do it directly in the geometry, but it put the problem too far ahead to my taste ( what if you do implicit UVs or positions ?).

    The simplest and less intrusive solution for your case is to apply the offset at the end of the vertex shader, by baking it in orthographic the projection or by adjusting the projected position directly.

    The projected space map a square of dimension ]-1..1[ on the X and Y axis that is later transform by the viewport in real screen coordinate in pixels. If we assume the geometry will have texture coordinate in the range [0..1] then this code will fix your problem :

    float4 projPos; // this is your current vertex shader output sv_position 
    projPos.xy -= projPos.w * 1.f / backBufferDim.xy;
    

    Because the GPU will divide by W, we need to cancel that by multiplying by W, and because the projected space is of a len of 2, a half pixel offset in the projected space is the double of that offset, so you use reciprocal of the backbuffer dimension.

    With that line, the interpolation of the texture coordinate will work for you, and your pixel shader will receive a properly offseted texture coordinate.