mirror of
https://github.com/doitsujin/dxvk.git
synced 2025-01-08 10:46:09 +01:00
18450f4643
Otherwise, a race condition occurs if a game submits rendering commands at the same time as presenting the swap chain image. Only works if multithreaded protection is enabled, but according to MSDN, it is illegal to use DXGI commands and the immediate context in parallel. Fixes stability issues in Tales of Vesperia.
872 lines
28 KiB
C++
872 lines
28 KiB
C++
#include "d3d11_context_imm.h"
|
|
#include "d3d11_device.h"
|
|
#include "d3d11_swapchain.h"
|
|
|
|
#include <dxgi_presenter_frag.h>
|
|
#include <dxgi_presenter_vert.h>
|
|
|
|
namespace dxvk {
|
|
|
|
static uint16_t MapGammaControlPoint(float x) {
|
|
if (x < 0.0f) x = 0.0f;
|
|
if (x > 1.0f) x = 1.0f;
|
|
return uint16_t(65535.0f * x);
|
|
}
|
|
|
|
|
|
D3D11SwapChain::D3D11SwapChain(
|
|
D3D11DXGIDevice* pContainer,
|
|
D3D11Device* pDevice,
|
|
HWND hWnd,
|
|
const DXGI_SWAP_CHAIN_DESC1* pDesc)
|
|
: m_dxgiDevice(pContainer),
|
|
m_parent (pDevice),
|
|
m_window (hWnd),
|
|
m_desc (*pDesc),
|
|
m_device (pDevice->GetDXVKDevice()),
|
|
m_context (m_device->createContext()),
|
|
m_frameLatencyCap(pDevice->GetOptions()->maxFrameLatency) {
|
|
CreateFrameLatencyEvent();
|
|
|
|
if (!pDevice->GetOptions()->deferSurfaceCreation)
|
|
CreatePresenter();
|
|
|
|
CreateBackBuffer();
|
|
CreateHud();
|
|
|
|
InitRenderState();
|
|
InitSamplers();
|
|
InitShaders();
|
|
}
|
|
|
|
|
|
D3D11SwapChain::~D3D11SwapChain() {
|
|
m_device->waitForSubmission(&m_presentStatus);
|
|
m_device->waitForIdle();
|
|
|
|
if (m_backBuffer)
|
|
m_backBuffer->ReleasePrivate();
|
|
|
|
DestroyFrameLatencyEvent();
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D11SwapChain::QueryInterface(
|
|
REFIID riid,
|
|
void** ppvObject) {
|
|
if (ppvObject == nullptr)
|
|
return E_POINTER;
|
|
|
|
InitReturnPtr(ppvObject);
|
|
|
|
if (riid == __uuidof(IUnknown)
|
|
|| riid == __uuidof(IDXGIVkSwapChain)) {
|
|
*ppvObject = ref(this);
|
|
return S_OK;
|
|
}
|
|
|
|
Logger::warn("D3D11SwapChain::QueryInterface: Unknown interface query");
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D11SwapChain::GetDesc(
|
|
DXGI_SWAP_CHAIN_DESC1* pDesc) {
|
|
*pDesc = m_desc;
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D11SwapChain::GetAdapter(
|
|
REFIID riid,
|
|
void** ppvObject) {
|
|
return m_dxgiDevice->GetParent(riid, ppvObject);
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D11SwapChain::GetDevice(
|
|
REFIID riid,
|
|
void** ppDevice) {
|
|
return m_dxgiDevice->QueryInterface(riid, ppDevice);
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D11SwapChain::GetImage(
|
|
UINT BufferId,
|
|
REFIID riid,
|
|
void** ppBuffer) {
|
|
InitReturnPtr(ppBuffer);
|
|
|
|
if (BufferId > 0) {
|
|
Logger::err("D3D11: GetImage: BufferId > 0 not supported");
|
|
return DXGI_ERROR_UNSUPPORTED;
|
|
}
|
|
|
|
return m_backBuffer->QueryInterface(riid, ppBuffer);
|
|
}
|
|
|
|
|
|
UINT STDMETHODCALLTYPE D3D11SwapChain::GetImageIndex() {
|
|
return 0;
|
|
}
|
|
|
|
|
|
UINT STDMETHODCALLTYPE D3D11SwapChain::GetFrameLatency() {
|
|
return m_frameLatency;
|
|
}
|
|
|
|
|
|
HANDLE STDMETHODCALLTYPE D3D11SwapChain::GetFrameLatencyEvent() {
|
|
return m_frameLatencyEvent;
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D11SwapChain::ChangeProperties(
|
|
const DXGI_SWAP_CHAIN_DESC1* pDesc) {
|
|
|
|
m_dirty |= m_desc.Format != pDesc->Format
|
|
|| m_desc.Width != pDesc->Width
|
|
|| m_desc.Height != pDesc->Height
|
|
|| m_desc.BufferCount != pDesc->BufferCount
|
|
|| m_desc.Flags != pDesc->Flags;
|
|
|
|
m_desc = *pDesc;
|
|
CreateBackBuffer();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D11SwapChain::SetPresentRegion(
|
|
const RECT* pRegion) {
|
|
// TODO implement
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D11SwapChain::SetGammaControl(
|
|
UINT NumControlPoints,
|
|
const DXGI_RGB* pControlPoints) {
|
|
bool isIdentity = true;
|
|
|
|
if (NumControlPoints > 1) {
|
|
std::array<D3D11_VK_GAMMA_CP, 1025> cp;
|
|
|
|
if (NumControlPoints > cp.size())
|
|
return E_INVALIDARG;
|
|
|
|
for (uint32_t i = 0; i < NumControlPoints; i++) {
|
|
uint16_t identity = MapGammaControlPoint(float(i) / float(NumControlPoints - 1));
|
|
|
|
cp[i].R = MapGammaControlPoint(pControlPoints[i].Red);
|
|
cp[i].G = MapGammaControlPoint(pControlPoints[i].Green);
|
|
cp[i].B = MapGammaControlPoint(pControlPoints[i].Blue);
|
|
cp[i].A = 0;
|
|
|
|
isIdentity &= cp[i].R == identity
|
|
&& cp[i].G == identity
|
|
&& cp[i].B == identity;
|
|
}
|
|
|
|
if (!isIdentity)
|
|
CreateGammaTexture(NumControlPoints, cp.data());
|
|
}
|
|
|
|
if (isIdentity)
|
|
DestroyGammaTexture();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D11SwapChain::SetFrameLatency(
|
|
UINT MaxLatency) {
|
|
if (MaxLatency == 0 || MaxLatency > DXGI_MAX_SWAP_CHAIN_BUFFERS)
|
|
return DXGI_ERROR_INVALID_CALL;
|
|
|
|
m_frameLatency = MaxLatency;
|
|
m_frameLatencySignal->wait(m_frameId - GetActualFrameLatency());
|
|
|
|
SignalFrameLatencyEvent();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT STDMETHODCALLTYPE D3D11SwapChain::Present(
|
|
UINT SyncInterval,
|
|
UINT PresentFlags,
|
|
const DXGI_PRESENT_PARAMETERS* pPresentParameters) {
|
|
auto options = m_parent->GetOptions();
|
|
|
|
if (options->syncInterval >= 0)
|
|
SyncInterval = options->syncInterval;
|
|
|
|
if (!(PresentFlags & DXGI_PRESENT_TEST)) {
|
|
bool vsync = SyncInterval != 0;
|
|
|
|
m_dirty |= vsync != m_vsync;
|
|
m_vsync = vsync;
|
|
}
|
|
|
|
if (m_presenter == nullptr)
|
|
CreatePresenter();
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (!m_presenter->hasSwapChain()) {
|
|
RecreateSwapChain(m_vsync);
|
|
m_dirty = false;
|
|
}
|
|
|
|
if (!m_presenter->hasSwapChain())
|
|
hr = DXGI_STATUS_OCCLUDED;
|
|
|
|
if (m_device->getDeviceStatus() != VK_SUCCESS)
|
|
hr = DXGI_ERROR_DEVICE_RESET;
|
|
|
|
if ((PresentFlags & DXGI_PRESENT_TEST) || hr != S_OK)
|
|
return hr;
|
|
|
|
if (std::exchange(m_dirty, false))
|
|
RecreateSwapChain(m_vsync);
|
|
|
|
try {
|
|
PresentImage(SyncInterval);
|
|
} catch (const DxvkError& e) {
|
|
Logger::err(e.message());
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT D3D11SwapChain::PresentImage(UINT SyncInterval) {
|
|
Com<ID3D11DeviceContext> deviceContext = nullptr;
|
|
m_parent->GetImmediateContext(&deviceContext);
|
|
|
|
// Flush pending rendering commands before
|
|
auto immediateContext = static_cast<D3D11ImmediateContext*>(deviceContext.ptr());
|
|
immediateContext->Flush();
|
|
|
|
// Wait for the sync event so that we respect the maximum frame latency
|
|
uint64_t frameId = ++m_frameId;
|
|
m_frameLatencySignal->wait(frameId - GetActualFrameLatency());
|
|
|
|
for (uint32_t i = 0; i < SyncInterval || i < 1; i++) {
|
|
SynchronizePresent();
|
|
|
|
if (!m_presenter->hasSwapChain())
|
|
return DXGI_STATUS_OCCLUDED;
|
|
|
|
// Presentation semaphores and WSI swap chain image
|
|
vk::PresenterInfo info = m_presenter->info();
|
|
vk::PresenterSync sync = m_presenter->getSyncSemaphores();
|
|
|
|
uint32_t imageIndex = 0;
|
|
|
|
VkResult status = m_presenter->acquireNextImage(
|
|
sync.acquire, VK_NULL_HANDLE, imageIndex);
|
|
|
|
while (status != VK_SUCCESS && status != VK_SUBOPTIMAL_KHR) {
|
|
RecreateSwapChain(m_vsync);
|
|
|
|
if (!m_presenter->hasSwapChain())
|
|
return DXGI_STATUS_OCCLUDED;
|
|
|
|
info = m_presenter->info();
|
|
sync = m_presenter->getSyncSemaphores();
|
|
|
|
status = m_presenter->acquireNextImage(
|
|
sync.acquire, VK_NULL_HANDLE, imageIndex);
|
|
}
|
|
|
|
// Resolve back buffer if it is multisampled. We
|
|
// only have to do it only for the first frame.
|
|
m_context->beginRecording(
|
|
m_device->createCommandList());
|
|
|
|
if (m_swapImageResolve != nullptr && i == 0) {
|
|
VkImageSubresourceLayers resolveSubresource;
|
|
resolveSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
resolveSubresource.mipLevel = 0;
|
|
resolveSubresource.baseArrayLayer = 0;
|
|
resolveSubresource.layerCount = 1;
|
|
|
|
VkImageResolve resolveRegion;
|
|
resolveRegion.srcSubresource = resolveSubresource;
|
|
resolveRegion.srcOffset = VkOffset3D { 0, 0, 0 };
|
|
resolveRegion.dstSubresource = resolveSubresource;
|
|
resolveRegion.dstOffset = VkOffset3D { 0, 0, 0 };
|
|
resolveRegion.extent = m_swapImage->info().extent;
|
|
|
|
m_context->resolveImage(
|
|
m_swapImageResolve, m_swapImage,
|
|
resolveRegion, VK_FORMAT_UNDEFINED);
|
|
}
|
|
|
|
// Use an appropriate texture filter depending on whether
|
|
// the back buffer size matches the swap image size
|
|
bool fitSize = m_swapImage->info().extent.width == info.imageExtent.width
|
|
&& m_swapImage->info().extent.height == info.imageExtent.height;
|
|
|
|
m_context->bindShader(VK_SHADER_STAGE_VERTEX_BIT, m_vertShader);
|
|
m_context->bindShader(VK_SHADER_STAGE_FRAGMENT_BIT, m_fragShader);
|
|
|
|
DxvkRenderTargets renderTargets;
|
|
renderTargets.color[0].view = m_imageViews.at(imageIndex);
|
|
renderTargets.color[0].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
m_context->bindRenderTargets(renderTargets);
|
|
|
|
VkViewport viewport;
|
|
viewport.x = 0.0f;
|
|
viewport.y = 0.0f;
|
|
viewport.width = float(info.imageExtent.width);
|
|
viewport.height = float(info.imageExtent.height);
|
|
viewport.minDepth = 0.0f;
|
|
viewport.maxDepth = 1.0f;
|
|
|
|
VkRect2D scissor;
|
|
scissor.offset.x = 0;
|
|
scissor.offset.y = 0;
|
|
scissor.extent.width = info.imageExtent.width;
|
|
scissor.extent.height = info.imageExtent.height;
|
|
|
|
m_context->setViewports(1, &viewport, &scissor);
|
|
|
|
m_context->setRasterizerState(m_rsState);
|
|
m_context->setMultisampleState(m_msState);
|
|
m_context->setDepthStencilState(m_dsState);
|
|
m_context->setLogicOpState(m_loState);
|
|
m_context->setBlendMode(0, m_blendMode);
|
|
|
|
m_context->setInputAssemblyState(m_iaState);
|
|
m_context->setInputLayout(0, nullptr, 0, nullptr);
|
|
|
|
m_context->bindResourceSampler(BindingIds::Image, fitSize ? m_samplerFitting : m_samplerScaling);
|
|
m_context->bindResourceSampler(BindingIds::Gamma, m_gammaSampler);
|
|
|
|
m_context->bindResourceView(BindingIds::Image, m_swapImageView, nullptr);
|
|
m_context->bindResourceView(BindingIds::Gamma, m_gammaTextureView, nullptr);
|
|
|
|
m_context->draw(3, 1, 0, 0);
|
|
|
|
if (m_hud != nullptr)
|
|
m_hud->render(m_context, info.format, info.imageExtent);
|
|
|
|
if (i + 1 >= SyncInterval)
|
|
m_context->signal(m_frameLatencySignal, frameId);
|
|
|
|
SubmitPresent(immediateContext, sync, i);
|
|
}
|
|
|
|
SignalFrameLatencyEvent();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::SubmitPresent(
|
|
D3D11ImmediateContext* pContext,
|
|
const vk::PresenterSync& Sync,
|
|
uint32_t FrameId) {
|
|
auto lock = pContext->LockContext();
|
|
|
|
// Present from CS thread so that we don't
|
|
// have to synchronize with it first.
|
|
m_presentStatus.result = VK_NOT_READY;
|
|
|
|
pContext->EmitCs([this,
|
|
cFrameId = FrameId,
|
|
cSync = Sync,
|
|
cHud = m_hud,
|
|
cCommandList = m_context->endRecording()
|
|
] (DxvkContext* ctx) {
|
|
m_device->submitCommandList(cCommandList,
|
|
cSync.acquire, cSync.present);
|
|
|
|
if (cHud != nullptr && !cFrameId)
|
|
cHud->update();
|
|
|
|
m_device->presentImage(m_presenter,
|
|
cSync.present, &m_presentStatus);
|
|
});
|
|
|
|
pContext->FlushCsChunk();
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::SynchronizePresent() {
|
|
// Recreate swap chain if the previous present call failed
|
|
VkResult status = m_device->waitForSubmission(&m_presentStatus);
|
|
|
|
if (status != VK_SUCCESS)
|
|
RecreateSwapChain(m_vsync);
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::RecreateSwapChain(BOOL Vsync) {
|
|
// Ensure that we can safely destroy the swap chain
|
|
m_device->waitForSubmission(&m_presentStatus);
|
|
m_device->waitForIdle();
|
|
|
|
m_presentStatus.result = VK_SUCCESS;
|
|
|
|
vk::PresenterDesc presenterDesc;
|
|
presenterDesc.imageExtent = { m_desc.Width, m_desc.Height };
|
|
presenterDesc.imageCount = PickImageCount(m_desc.BufferCount + 1);
|
|
presenterDesc.numFormats = PickFormats(m_desc.Format, presenterDesc.formats);
|
|
presenterDesc.numPresentModes = PickPresentModes(Vsync, presenterDesc.presentModes);
|
|
presenterDesc.fullScreenExclusive = PickFullscreenMode();
|
|
|
|
if (m_presenter->recreateSwapChain(presenterDesc) != VK_SUCCESS)
|
|
throw DxvkError("D3D11SwapChain: Failed to recreate swap chain");
|
|
|
|
CreateRenderTargetViews();
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::CreateFrameLatencyEvent() {
|
|
m_frameLatencySignal = new sync::Win32Fence(m_frameId);
|
|
|
|
if (m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT)
|
|
m_frameLatencyEvent = CreateEvent(nullptr, false, true, nullptr);
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::CreatePresenter() {
|
|
DxvkDeviceQueue graphicsQueue = m_device->queues().graphics;
|
|
|
|
vk::PresenterDevice presenterDevice;
|
|
presenterDevice.queueFamily = graphicsQueue.queueFamily;
|
|
presenterDevice.queue = graphicsQueue.queueHandle;
|
|
presenterDevice.adapter = m_device->adapter()->handle();
|
|
presenterDevice.features.fullScreenExclusive = m_device->extensions().extFullScreenExclusive;
|
|
|
|
vk::PresenterDesc presenterDesc;
|
|
presenterDesc.imageExtent = { m_desc.Width, m_desc.Height };
|
|
presenterDesc.imageCount = PickImageCount(m_desc.BufferCount + 1);
|
|
presenterDesc.numFormats = PickFormats(m_desc.Format, presenterDesc.formats);
|
|
presenterDesc.numPresentModes = PickPresentModes(false, presenterDesc.presentModes);
|
|
presenterDesc.fullScreenExclusive = PickFullscreenMode();
|
|
|
|
m_presenter = new vk::Presenter(m_window,
|
|
m_device->adapter()->vki(),
|
|
m_device->vkd(),
|
|
presenterDevice,
|
|
presenterDesc);
|
|
|
|
CreateRenderTargetViews();
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::CreateRenderTargetViews() {
|
|
vk::PresenterInfo info = m_presenter->info();
|
|
|
|
m_imageViews.clear();
|
|
m_imageViews.resize(info.imageCount);
|
|
|
|
DxvkImageCreateInfo imageInfo;
|
|
imageInfo.type = VK_IMAGE_TYPE_2D;
|
|
imageInfo.format = info.format.format;
|
|
imageInfo.flags = 0;
|
|
imageInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
|
imageInfo.extent = { info.imageExtent.width, info.imageExtent.height, 1 };
|
|
imageInfo.numLayers = 1;
|
|
imageInfo.mipLevels = 1;
|
|
imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
imageInfo.stages = 0;
|
|
imageInfo.access = 0;
|
|
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
imageInfo.layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
|
|
DxvkImageViewCreateInfo viewInfo;
|
|
viewInfo.type = VK_IMAGE_VIEW_TYPE_2D;
|
|
viewInfo.format = info.format.format;
|
|
viewInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
viewInfo.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
viewInfo.minLevel = 0;
|
|
viewInfo.numLevels = 1;
|
|
viewInfo.minLayer = 0;
|
|
viewInfo.numLayers = 1;
|
|
|
|
for (uint32_t i = 0; i < info.imageCount; i++) {
|
|
VkImage imageHandle = m_presenter->getImage(i).image;
|
|
|
|
Rc<DxvkImage> image = new DxvkImage(
|
|
m_device->vkd(), imageInfo, imageHandle);
|
|
|
|
m_imageViews[i] = new DxvkImageView(
|
|
m_device->vkd(), image, viewInfo);
|
|
}
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::CreateBackBuffer() {
|
|
// Explicitly destroy current swap image before
|
|
// creating a new one to free up resources
|
|
if (m_backBuffer)
|
|
m_backBuffer->ReleasePrivate();
|
|
|
|
m_swapImage = nullptr;
|
|
m_swapImageResolve = nullptr;
|
|
m_swapImageView = nullptr;
|
|
m_backBuffer = nullptr;
|
|
|
|
// Create new back buffer
|
|
D3D11_COMMON_TEXTURE_DESC desc;
|
|
desc.Width = std::max(m_desc.Width, 1u);
|
|
desc.Height = std::max(m_desc.Height, 1u);
|
|
desc.Depth = 1;
|
|
desc.MipLevels = 1;
|
|
desc.ArraySize = 1;
|
|
desc.Format = m_desc.Format;
|
|
desc.SampleDesc = m_desc.SampleDesc;
|
|
desc.Usage = D3D11_USAGE_DEFAULT;
|
|
desc.BindFlags = D3D11_BIND_RENDER_TARGET
|
|
| D3D11_BIND_SHADER_RESOURCE;
|
|
desc.CPUAccessFlags = 0;
|
|
desc.MiscFlags = 0;
|
|
desc.TextureLayout = D3D11_TEXTURE_LAYOUT_UNDEFINED;
|
|
|
|
if (m_desc.BufferUsage & DXGI_USAGE_UNORDERED_ACCESS)
|
|
desc.BindFlags |= D3D11_BIND_UNORDERED_ACCESS;
|
|
|
|
if (m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE)
|
|
desc.MiscFlags |= D3D11_RESOURCE_MISC_GDI_COMPATIBLE;
|
|
|
|
m_backBuffer = new D3D11Texture2D(m_parent, &desc);
|
|
m_backBuffer->AddRefPrivate();
|
|
|
|
m_swapImage = GetCommonTexture(m_backBuffer)->GetImage();
|
|
|
|
// If the image is multisampled, we need to create
|
|
// another image which we'll use as a resolve target
|
|
if (m_swapImage->info().sampleCount != VK_SAMPLE_COUNT_1_BIT) {
|
|
DxvkImageCreateInfo resolveInfo;
|
|
resolveInfo.type = VK_IMAGE_TYPE_2D;
|
|
resolveInfo.format = m_swapImage->info().format;
|
|
resolveInfo.flags = 0;
|
|
resolveInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
|
resolveInfo.extent = m_swapImage->info().extent;
|
|
resolveInfo.numLayers = 1;
|
|
resolveInfo.mipLevels = 1;
|
|
resolveInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT
|
|
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
|
|
| VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
resolveInfo.stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
|
|
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
|
|
| VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
resolveInfo.access = VK_ACCESS_SHADER_READ_BIT
|
|
| VK_ACCESS_TRANSFER_WRITE_BIT
|
|
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
|
|
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
resolveInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
resolveInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
|
|
m_swapImageResolve = m_device->createImage(
|
|
resolveInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
}
|
|
|
|
// Create an image view that allows the
|
|
// image to be bound as a shader resource.
|
|
DxvkImageViewCreateInfo viewInfo;
|
|
viewInfo.type = VK_IMAGE_VIEW_TYPE_2D;
|
|
viewInfo.format = m_swapImage->info().format;
|
|
viewInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
viewInfo.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
viewInfo.minLevel = 0;
|
|
viewInfo.numLevels = 1;
|
|
viewInfo.minLayer = 0;
|
|
viewInfo.numLayers = 1;
|
|
|
|
m_swapImageView = m_device->createImageView(
|
|
m_swapImageResolve != nullptr
|
|
? m_swapImageResolve
|
|
: m_swapImage,
|
|
viewInfo);
|
|
|
|
// Initialize the image so that we can use it. Clearing
|
|
// to black prevents garbled output for the first frame.
|
|
VkImageSubresourceRange subresources;
|
|
subresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
subresources.baseMipLevel = 0;
|
|
subresources.levelCount = 1;
|
|
subresources.baseArrayLayer = 0;
|
|
subresources.layerCount = 1;
|
|
|
|
VkClearColorValue clearColor;
|
|
clearColor.float32[0] = 0.0f;
|
|
clearColor.float32[1] = 0.0f;
|
|
clearColor.float32[2] = 0.0f;
|
|
clearColor.float32[3] = 0.0f;
|
|
|
|
m_context->beginRecording(
|
|
m_device->createCommandList());
|
|
|
|
m_context->clearColorImage(
|
|
m_swapImage, clearColor, subresources);
|
|
|
|
m_device->submitCommandList(
|
|
m_context->endRecording(),
|
|
VK_NULL_HANDLE,
|
|
VK_NULL_HANDLE);
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::CreateGammaTexture(
|
|
UINT NumControlPoints,
|
|
const D3D11_VK_GAMMA_CP* pControlPoints) {
|
|
if (m_gammaTexture == nullptr
|
|
|| m_gammaTexture->info().extent.width != NumControlPoints) {
|
|
DxvkImageCreateInfo imgInfo;
|
|
imgInfo.type = VK_IMAGE_TYPE_1D;
|
|
imgInfo.format = VK_FORMAT_R16G16B16A16_UNORM;
|
|
imgInfo.flags = 0;
|
|
imgInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
|
imgInfo.extent = { NumControlPoints, 1, 1 };
|
|
imgInfo.numLayers = 1;
|
|
imgInfo.mipLevels = 1;
|
|
imgInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT
|
|
| VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
imgInfo.stages = VK_PIPELINE_STAGE_TRANSFER_BIT
|
|
| VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
imgInfo.access = VK_ACCESS_TRANSFER_WRITE_BIT
|
|
| VK_ACCESS_SHADER_READ_BIT;
|
|
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
imgInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
|
|
m_gammaTexture = m_device->createImage(
|
|
imgInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
|
|
DxvkImageViewCreateInfo viewInfo;
|
|
viewInfo.type = VK_IMAGE_VIEW_TYPE_1D;
|
|
viewInfo.format = VK_FORMAT_R16G16B16A16_UNORM;
|
|
viewInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
viewInfo.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
viewInfo.minLevel = 0;
|
|
viewInfo.numLevels = 1;
|
|
viewInfo.minLayer = 0;
|
|
viewInfo.numLayers = 1;
|
|
|
|
m_gammaTextureView = m_device->createImageView(m_gammaTexture, viewInfo);
|
|
}
|
|
|
|
m_context->beginRecording(
|
|
m_device->createCommandList());
|
|
|
|
m_context->updateImage(m_gammaTexture,
|
|
VkImageSubresourceLayers { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
|
|
VkOffset3D { 0, 0, 0 },
|
|
VkExtent3D { NumControlPoints, 1, 1 },
|
|
pControlPoints, 0, 0);
|
|
|
|
m_device->submitCommandList(
|
|
m_context->endRecording(),
|
|
VK_NULL_HANDLE,
|
|
VK_NULL_HANDLE);
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::DestroyFrameLatencyEvent() {
|
|
CloseHandle(m_frameLatencyEvent);
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::DestroyGammaTexture() {
|
|
m_gammaTexture = nullptr;
|
|
m_gammaTextureView = nullptr;
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::CreateHud() {
|
|
m_hud = hud::Hud::createHud(m_device);
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::InitRenderState() {
|
|
m_iaState.primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
|
m_iaState.primitiveRestart = VK_FALSE;
|
|
m_iaState.patchVertexCount = 0;
|
|
|
|
m_rsState.polygonMode = VK_POLYGON_MODE_FILL;
|
|
m_rsState.cullMode = VK_CULL_MODE_BACK_BIT;
|
|
m_rsState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
|
m_rsState.depthClipEnable = VK_FALSE;
|
|
m_rsState.depthBiasEnable = VK_FALSE;
|
|
m_rsState.sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
m_msState.sampleMask = 0xffffffff;
|
|
m_msState.enableAlphaToCoverage = VK_FALSE;
|
|
|
|
VkStencilOpState stencilOp;
|
|
stencilOp.failOp = VK_STENCIL_OP_KEEP;
|
|
stencilOp.passOp = VK_STENCIL_OP_KEEP;
|
|
stencilOp.depthFailOp = VK_STENCIL_OP_KEEP;
|
|
stencilOp.compareOp = VK_COMPARE_OP_ALWAYS;
|
|
stencilOp.compareMask = 0xFFFFFFFF;
|
|
stencilOp.writeMask = 0xFFFFFFFF;
|
|
stencilOp.reference = 0;
|
|
|
|
m_dsState.enableDepthTest = VK_FALSE;
|
|
m_dsState.enableDepthWrite = VK_FALSE;
|
|
m_dsState.enableStencilTest = VK_FALSE;
|
|
m_dsState.depthCompareOp = VK_COMPARE_OP_ALWAYS;
|
|
m_dsState.stencilOpFront = stencilOp;
|
|
m_dsState.stencilOpBack = stencilOp;
|
|
|
|
m_loState.enableLogicOp = VK_FALSE;
|
|
m_loState.logicOp = VK_LOGIC_OP_NO_OP;
|
|
|
|
m_blendMode.enableBlending = VK_FALSE;
|
|
m_blendMode.colorSrcFactor = VK_BLEND_FACTOR_ONE;
|
|
m_blendMode.colorDstFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
|
m_blendMode.colorBlendOp = VK_BLEND_OP_ADD;
|
|
m_blendMode.alphaSrcFactor = VK_BLEND_FACTOR_ONE;
|
|
m_blendMode.alphaDstFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
|
m_blendMode.alphaBlendOp = VK_BLEND_OP_ADD;
|
|
m_blendMode.writeMask = VK_COLOR_COMPONENT_R_BIT
|
|
| VK_COLOR_COMPONENT_G_BIT
|
|
| VK_COLOR_COMPONENT_B_BIT
|
|
| VK_COLOR_COMPONENT_A_BIT;
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::InitSamplers() {
|
|
DxvkSamplerCreateInfo samplerInfo;
|
|
samplerInfo.magFilter = VK_FILTER_NEAREST;
|
|
samplerInfo.minFilter = VK_FILTER_NEAREST;
|
|
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
|
samplerInfo.mipmapLodBias = 0.0f;
|
|
samplerInfo.mipmapLodMin = 0.0f;
|
|
samplerInfo.mipmapLodMax = 0.0f;
|
|
samplerInfo.useAnisotropy = VK_FALSE;
|
|
samplerInfo.maxAnisotropy = 1.0f;
|
|
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
|
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
|
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
|
samplerInfo.compareToDepth = VK_FALSE;
|
|
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
|
|
samplerInfo.borderColor = VkClearColorValue();
|
|
samplerInfo.usePixelCoord = VK_FALSE;
|
|
m_samplerFitting = m_device->createSampler(samplerInfo);
|
|
|
|
samplerInfo.magFilter = VK_FILTER_LINEAR;
|
|
samplerInfo.minFilter = VK_FILTER_LINEAR;
|
|
m_samplerScaling = m_device->createSampler(samplerInfo);
|
|
|
|
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
|
|
m_gammaSampler = m_device->createSampler(samplerInfo);
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::InitShaders() {
|
|
const SpirvCodeBuffer vsCode(dxgi_presenter_vert);
|
|
const SpirvCodeBuffer fsCode(dxgi_presenter_frag);
|
|
|
|
const std::array<DxvkResourceSlot, 2> fsResourceSlots = {{
|
|
{ BindingIds::Image, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_IMAGE_VIEW_TYPE_2D },
|
|
{ BindingIds::Gamma, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_IMAGE_VIEW_TYPE_1D },
|
|
}};
|
|
|
|
m_vertShader = m_device->createShader(
|
|
VK_SHADER_STAGE_VERTEX_BIT,
|
|
0, nullptr, { 0u, 1u },
|
|
vsCode);
|
|
|
|
m_fragShader = m_device->createShader(
|
|
VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
fsResourceSlots.size(),
|
|
fsResourceSlots.data(),
|
|
{ 1u, 1u }, fsCode);
|
|
}
|
|
|
|
|
|
void D3D11SwapChain::SignalFrameLatencyEvent() {
|
|
if (m_frameLatencyEvent) {
|
|
// Signal event with the same value that we'd wait for during the next present.
|
|
m_frameLatencySignal->setEvent(m_frameLatencyEvent, m_frameId - GetActualFrameLatency() + 1);
|
|
}
|
|
}
|
|
|
|
|
|
uint32_t D3D11SwapChain::GetActualFrameLatency() {
|
|
uint32_t maxFrameLatency = m_frameLatency;
|
|
|
|
if (!(m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT))
|
|
m_dxgiDevice->GetMaximumFrameLatency(&maxFrameLatency);
|
|
|
|
if (m_frameLatencyCap)
|
|
maxFrameLatency = std::min(maxFrameLatency, m_frameLatencyCap);
|
|
|
|
maxFrameLatency = std::min(maxFrameLatency, m_desc.BufferCount + 1);
|
|
return maxFrameLatency;
|
|
}
|
|
|
|
|
|
uint32_t D3D11SwapChain::PickFormats(
|
|
DXGI_FORMAT Format,
|
|
VkSurfaceFormatKHR* pDstFormats) {
|
|
uint32_t n = 0;
|
|
|
|
switch (Format) {
|
|
default:
|
|
Logger::warn(str::format("D3D11SwapChain: Unexpected format: ", m_desc.Format));
|
|
|
|
case DXGI_FORMAT_R8G8B8A8_UNORM:
|
|
case DXGI_FORMAT_B8G8R8A8_UNORM: {
|
|
pDstFormats[n++] = { VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
|
|
pDstFormats[n++] = { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
|
|
} break;
|
|
|
|
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
|
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: {
|
|
pDstFormats[n++] = { VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
|
|
pDstFormats[n++] = { VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
|
|
} break;
|
|
|
|
case DXGI_FORMAT_R10G10B10A2_UNORM: {
|
|
pDstFormats[n++] = { VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
|
|
pDstFormats[n++] = { VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
|
|
} break;
|
|
|
|
case DXGI_FORMAT_R16G16B16A16_FLOAT: {
|
|
pDstFormats[n++] = { VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
|
|
} break;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
uint32_t D3D11SwapChain::PickPresentModes(
|
|
BOOL Vsync,
|
|
VkPresentModeKHR* pDstModes) {
|
|
uint32_t n = 0;
|
|
|
|
if (Vsync) {
|
|
pDstModes[n++] = VK_PRESENT_MODE_FIFO_KHR;
|
|
} else {
|
|
pDstModes[n++] = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
|
pDstModes[n++] = VK_PRESENT_MODE_MAILBOX_KHR;
|
|
pDstModes[n++] = VK_PRESENT_MODE_FIFO_RELAXED_KHR;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
uint32_t D3D11SwapChain::PickImageCount(
|
|
UINT Preferred) {
|
|
int32_t option = m_parent->GetOptions()->numBackBuffers;
|
|
return option > 0 ? uint32_t(option) : uint32_t(Preferred);
|
|
}
|
|
|
|
|
|
VkFullScreenExclusiveEXT D3D11SwapChain::PickFullscreenMode() {
|
|
return m_desc.Flags & DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH
|
|
? VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT
|
|
: VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT;
|
|
}
|
|
|
|
} |