首页 > 解决方案 > DirectX 窗口在多窗口和多线程下快速闪烁

问题描述

我正在尝试创建一个基于 DirectX 的基本 C++ Windows 应用程序,它可以在两个单独的窗口上显示两个图像。我可以轻松地显示单个图像。但是,我必须使用多线程——每个窗口一个线程——因为同时更新窗口的内容至关重要。

然而,目前,我只专注于为两个静态图像实现多线程,每个窗口每个线程一个图像。然而,我遇到的问题是两个窗口都快速/随机地闪烁(它们主要闪烁为黑色,偶尔闪烁绿色)。

我怀疑这可能与我将多线程与 DirectX 结合的方式有关,据我所知,必须使用互斥锁,因此不会同时调用设备或上下文。正因为如此,我怀疑当窗口重新渲染时,它们的内容在一段时间内被另一个线程阻塞。

但是,我不确定是否是这种情况,如果是这样,我不知道如何解决它。

这是主要的游戏循环:

//
// Game.cpp
//

#include "pch.h"
#include "Game.h"
#include <synchapi.h>

extern void ExitGame();

using namespace DirectX;

using Microsoft::WRL::ComPtr;

ComPtr<ID3D11Texture2D> textures[4];

Game::Game() noexcept(false)
{
    m_deviceResources = std::make_unique<DX::DeviceResources>(DXGI_FORMAT_R10G10B10A2_UNORM,
        DXGI_FORMAT_D32_FLOAT, 2, D3D_FEATURE_LEVEL_10_0, DX::DeviceResources::c_EnableHDR);
    m_deviceResources->RegisterDeviceNotify(this);

    m_hdrScene[0] = std::make_unique<DX::RenderTexture>(DXGI_FORMAT_R16G16B16A16_FLOAT);
    m_hdrScene[1] = std::make_unique<DX::RenderTexture>(DXGI_FORMAT_R16G16B16A16_FLOAT);

}

// Initialize the Direct3D resources required to run.
void Game::Initialize(HWND windows[], int width, int height)
{

    m_deviceResources->SetWindow(0, windows[0], width, height);
    m_deviceResources->SetWindow(1, windows[1], width, height);

    m_deviceResources->CreateDeviceResources();
    CreateDeviceDependentResources();

    m_deviceResources->CreateWindowSizeDependentResources(0);
    m_deviceResources->CreateWindowSizeDependentResources(1);


    CreateWindowSizeDependentResources();

    // TODO: Change the timer settings if you want something other than the default variable timestep mode.
    // e.g. for 60 FPS fixed timestep update logic, call:

    m_timer.SetFixedTimeStep(true);
    m_timer.SetTargetElapsedSeconds(1.0 / 60);

}


void Game::CreateThreads()
{
    std::thread one(&Game::Render, this, 0);
    std::thread two(&Game::Render, this, 1);

    one.join();
    two.join();
}

#pragma region Frame Update
// Executes the basic game loop.
void Game::Tick()
{
    m_timer.Tick([&]() { Update(m_timer); });

    CreateThreads();
}


// Updates the world.
void Game::Update(DX::StepTimer const& timer)
{
    float elapsedTime = float(timer.GetElapsedSeconds());

    char buff[128] = {};
    sprintf_s(buff, "%f\n", elapsedTime);
    OutputDebugStringA(buff);



    // TODO: Add your game logic here.

}
#pragma endregion

std::mutex mut;

#pragma region Frame Render
// Draws the scene.
void Game::Render(int i)
{
    // Don't try to render anything before the first Update.
    if (m_timer.GetFrameCount() == 0)
    {
        return;
    }

    mut.lock();

    Clear(i);


    m_deviceResources->PIXBeginEvent(L"Render");
    auto context = m_deviceResources->GetD3DDeviceContext();
    m_spriteBatch = std::make_unique<SpriteBatch>(context);

    mut.unlock();

    // TODO: Add your rendering code here.

    D3D11_SHADER_RESOURCE_VIEW_DESC desc2 = { };
    desc2.Format = DXGI_FORMAT_R16G16B16A16_UNORM;
    desc2.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    desc2.Texture2D.MipLevels = 1;

    ComPtr<ID3D11ShaderResourceView> shaderResourceView;


    mut.lock();

    auto hr = m_deviceResources->m_d3dDevice->CreateShaderResourceView(
        textures[i].Get(),
        &desc2,
        shaderResourceView.GetAddressOf()
    );

    try {
        m_spriteBatch->Begin();
        m_spriteBatch->Draw(shaderResourceView.Get(), XMFLOAT2(0, 0));
        m_spriteBatch->End();
    } catch (std::exception& e)
    {
        auto msg = e.what();
        throw std::exception(msg);
    }

    m_deviceResources->PIXEndEvent();



    auto renderTarget = m_deviceResources->GetRenderTargetView(i);
    context->OMSetRenderTargets(1, &renderTarget, nullptr);

    mut.unlock();

    m_toneMap[i]->SetOperator(ToneMapPostProcess::None);
        m_toneMap[i]->SetTransferFunction(ToneMapPostProcess::ST2084);
        m_toneMap[i]->SetST2084Parameter(10000.f);

    mut.lock();
    m_toneMap[i]->Process(context);

    ID3D11ShaderResourceView* nullsrv[] = { nullptr };
    context->PSSetShaderResources(0, 1, nullsrv);

    mut.unlock();


    // Show the new frame.
    m_deviceResources->Present(i);
}

// Helper method to clear the back buffers.
void Game::Clear(int i)
{
    m_deviceResources->PIXBeginEvent(L"Clear");

    // Clear the views.
    auto context = m_deviceResources->GetD3DDeviceContext();

    auto renderTarget = m_hdrScene[i]->GetRenderTargetView();
    auto depthStencil = m_deviceResources->GetDepthStencilView();

    XMVECTORF32 color;
    auto actual = FXMVECTOR({ {0, 0, 0, 0} });
    color.v = XMColorSRGBToRGB(actual);
    context->ClearRenderTargetView(renderTarget, color);


    context->ClearDepthStencilView(depthStencil, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
    context->OMSetRenderTargets(1, &renderTarget, depthStencil);

    // Set the viewport.
    auto viewport = m_deviceResources->GetScreenViewport();
    context->RSSetViewports(1, &viewport);

    m_deviceResources->PIXEndEvent();
}
#pragma endregion

void Game::OnWindowMoved()
{
    auto r = m_deviceResources->GetOutputSize();
    m_deviceResources->WindowSizeChanged(0, r.right, r.bottom);
    m_deviceResources->WindowSizeChanged(1, r.right, r.bottom);
}

void Game::OnWindowSizeChanged(int index, int width, int height)
{
    if (!m_deviceResources->WindowSizeChanged(index, width, height))
        return;

    CreateWindowSizeDependentResources();

    // TODO: Game window is being resized.
}

// Properties
void Game::GetDefaultSize(int& width, int& height) const
{
    // TODO: Change to desired default window size (note minimum size is 320x200).
    width = 800;
    height = 600;
}

#pragma region Direct3D Resources
// These are the resources that depend on the device.
void Game::CreateDeviceDependentResources()
{
    auto device = m_deviceResources->GetD3DDevice();
    cv::directx::ocl::initializeContextFromD3D11Device(device);


    // TODO: Initialize device dependent objects here (independent of window size).

    for (int i = 0; i < 2; i++)
    {
        m_hdrScene[i]->SetDevice(device);
        m_toneMap[i] = std::make_unique<ToneMapPostProcess>(device);

        m_toneMap[i]->SetOperator(ToneMapPostProcess::None);
        m_toneMap[i]->SetTransferFunction(ToneMapPostProcess::ST2084);

    }

    for (int i = 0; i < 4; i++) {
        textures[i] = this->getImagesAsTextures()[i];
    }
}

// Allocate all memory resources that change on a window SizeChanged event.
void Game::CreateWindowSizeDependentResources()
{
    auto size = m_deviceResources->GetOutputSize();
    for (int i = 0; i < 2; i++) {
        m_hdrScene[i]->SetWindow(size);

        m_toneMap[i]->SetHDRSourceTexture(m_hdrScene[i]->GetShaderResourceView());
    }
}

void Game::OnDeviceLost()
{
    // TODO: Add Direct3D resource cleanup here.


    m_hdrScene[0]->ReleaseDevice();
    m_hdrScene[1]->ReleaseDevice();

    m_toneMap[0].reset();
    m_toneMap[1].reset();
}

void Game::OnDeviceRestored()
{
    CreateDeviceDependentResources();

    CreateWindowSizeDependentResources();
    CreateWindowSizeDependentResources();

}
#pragma endregion

我也收到此警告,但我不确定这意味着什么:D3D11 WARNING: ID3D11DeviceContext::DrawIndexed: The Pixel Shader expects a Render Target View bound to slot 0, but none is bound. This is OK, as writes of an unbound Render Target View are discarded. It is also possible the developer knows the data will not be used anyway. This is only a problem if the developer actually intended to bind a Render Target View here. [ EXECUTION WARNING #3146081: DEVICE_DRAW_RENDERTARGETVIEW_NOT_SET]

此代码主要基于 DirectXTK Win32 模板。任何帮助将不胜感激,谢谢。

标签: c++windowsmultithreadinggraphicsdirectx

解决方案


渲染过程中的多个手动互斥锁/解锁使整个同步毫无意义。不会同时调用设备或上下文,但可以从不同的线程修改渲染管道。例如,在lock OMSetRenderTargets unlock另一个线程可能获得锁并设置不同的渲染目标后,第一个线程会在屏幕上显示丢弃的内容。


推荐阅读