DirectX 窗口通过多窗口和多线程快速闪烁

DirectX window flickers rapidly with multi-windows and multi-threading

本文关键字:窗口 闪烁 多线程 DirectX      更新时间:2023-10-16

我正在尝试创建一个基本的基于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, "%fn", 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 模板。任何帮助将不胜感激,谢谢。

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