Files
ARMSX2/pcsx2/GS/Renderers/DX11/GSDevice11.cpp
T
TJnotJTandTellowKrinkle d1cd5a62ed GS/HW: ROV revisions.
Use GSTexture::FeedbackTarget RTs by default (not GSTexture::ShaderWriteTarget).
Do ROV type conversion for both color and depth (if needed).
Make sure the formats for ROV actually support UAV/storage image.
Change 'PSSetUnorderedAccess' to 'PSSetROVs' for clarity.
DX12: Use enum names instead of raw constants in PSSetShaderResource().
Some string format cleanup to GL_*() macros.
VK: Do not enable ROV if FB fetch is available.
Other misc cosmetic changes such as whitespace, etc.

Co-authored-by: TellowKrinkle
2026-06-28 12:13:38 -04:00

3318 lines
103 KiB
C++

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "GS.h"
#include "GS/GSGL.h"
#include "GSDevice11.h"
#include "GS/Renderers/DX11/D3D.h"
#include "GS/GSExtra.h"
#include "GS/GSPerfMon.h"
#include "GS/GSUtil.h"
#include "Host.h"
#include "common/BitUtils.h"
#include "common/Error.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "common/ScopedGuard.h"
#include "imgui.h"
#include "IconsFontAwesome.h"
#include <bit>
#include <fstream>
#include <sstream>
#include <VersionHelpers.h>
#include <d3dcompiler.h>
#include <dxgidebug.h>
// #define REPORT_LEAKED_OBJECTS 1
static constexpr std::array<float, 4> s_present_clear_color = {};
static bool SupportsTextureFormat(ID3D11Device* dev, DXGI_FORMAT format)
{
UINT support;
if (FAILED(dev->CheckFormatSupport(format, &support)))
return false;
return (support & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0;
}
static bool IsTextureFormatHWBlendable(ID3D11Device* dev, DXGI_FORMAT format)
{
UINT support;
if (FAILED(dev->CheckFormatSupport(format, &support)))
return false;
return (support & D3D11_FORMAT_SUPPORT_RENDER_TARGET) && (support & D3D11_FORMAT_SUPPORT_BLENDABLE);
}
static bool IsTextureFormatUAVCapable(ID3D11Device* dev, DXGI_FORMAT format)
{
UINT support;
if (FAILED(dev->CheckFormatSupport(format, &support)))
return false;
return (support & D3D11_FORMAT_SUPPORT_TYPED_UNORDERED_ACCESS_VIEW) != 0;
}
GSDevice11::GSDevice11()
{
memset(&m_state, 0, sizeof(m_state));
m_state.topology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
m_state.bf = -1;
m_features.primitive_id = true;
m_features.texture_barrier = false;
m_features.multidraw_fb_copy = false;
m_features.provoking_vertex_last = false;
m_features.point_expand = false;
m_features.line_expand = false;
m_features.prefer_new_textures = false;
m_features.dxt_textures = false;
m_features.bptc_textures = false;
m_features.framebuffer_fetch = false;
m_features.stencil_buffer = true;
m_features.cas_sharpening = true;
m_features.test_and_sample_depth = false;
m_features.depth_feedback = false;
m_features.aa1 = false;
}
GSDevice11::~GSDevice11() = default;
void GSDevice11::SetD3DDebugObjectName(ID3D11DeviceChild* obj, std::string_view name)
{
#ifdef PCSX2_DEVBUILD
// WKPDID_D3DDebugObjectName
static constexpr GUID guid = {0x429b8c22, 0x9188, 0x4b0c, {0x87, 0x42, 0xac, 0xb0, 0xbf, 0x85, 0xc2, 0x00}};
UINT existing_data_size;
HRESULT hr = obj->GetPrivateData(guid, &existing_data_size, nullptr);
if (SUCCEEDED(hr) && existing_data_size > 0)
return;
obj->SetPrivateData(guid, static_cast<UINT>(name.length()), name.data());
#endif
}
RenderAPI GSDevice11::GetRenderAPI() const
{
return RenderAPI::D3D11;
}
bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
{
if (!GSDevice::Create(vsync_mode, allow_present_throttle))
return false;
UINT create_flags = 0;
if (GSConfig.UseDebugDevice)
create_flags |= D3D11_CREATE_DEVICE_DEBUG;
m_dxgi_factory = D3D::CreateFactory(GSConfig.UseDebugDevice);
if (!m_dxgi_factory)
return false;
wil::com_ptr_nothrow<IDXGIAdapter1> dxgi_adapter = D3D::GetAdapterByName(m_dxgi_factory.get(), GSConfig.Adapter);
static constexpr std::array<D3D_FEATURE_LEVEL, 3> requested_feature_levels = {{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_0,
}};
wil::com_ptr_nothrow<ID3D11Device> temp_dev;
wil::com_ptr_nothrow<ID3D11DeviceContext> temp_ctx;
HRESULT hr =
D3D11CreateDevice(dxgi_adapter.get(), dxgi_adapter ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE,
nullptr, create_flags, requested_feature_levels.data(), static_cast<UINT>(requested_feature_levels.size()),
D3D11_SDK_VERSION, temp_dev.put(), &m_feature_level, temp_ctx.put());
if (FAILED(hr) || !temp_dev.try_query_to(&m_dev) || !temp_ctx.try_query_to(&m_ctx))
{
Host::ReportErrorAsync("GS",
fmt::format(
TRANSLATE_FS("GS", "Failed to create D3D11 device: 0x{:08X}. A GPU which supports Direct3D Feature Level 10.0 is required."),
hr));
return false;
}
// we re-grab these later, see below
dxgi_adapter.reset();
temp_dev.reset();
temp_ctx.reset();
if (GSConfig.UseDebugDevice && IsDebuggerPresent())
{
wil::com_ptr_nothrow<ID3D11InfoQueue> info;
if (m_dev.try_query_to(&info))
{
info->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, TRUE);
info->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_WARNING, TRUE);
// Silence some annoying harmless warnings.
D3D11_MESSAGE_ID hide[] = {
D3D11_MESSAGE_ID_DEVICE_OMSETRENDERTARGETS_HAZARD,
D3D11_MESSAGE_ID_DEVICE_PSSETSHADERRESOURCES_HAZARD,
D3D11_MESSAGE_ID_DEVICE_DRAW_RENDERTARGETVIEW_NOT_SET,
D3D11_MESSAGE_ID_QUERY_END_ABANDONING_PREVIOUS_RESULTS,
};
D3D11_INFO_QUEUE_FILTER filter = {};
filter.DenyList.NumIDs = std::size(hide);
filter.DenyList.pIDList = hide;
info->AddStorageFilterEntries(&filter);
}
}
wil::com_ptr_nothrow<IDXGIDevice> dxgi_device;
if (m_dev.try_query_to(&dxgi_device) && SUCCEEDED(dxgi_device->GetParent(IID_PPV_ARGS(dxgi_adapter.put()))))
{
m_name = D3D::GetAdapterName(dxgi_adapter.get());
Console.WriteLn(fmt::format("D3D11: Adapter: {}", m_name));
}
else
Console.Error("D3D11: Failed to obtain adapter name.");
BOOL allow_tearing_supported = false;
hr = m_dxgi_factory->CheckFeatureSupport(
DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, sizeof(allow_tearing_supported));
m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE);
if (!AcquireWindow(true) || (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()))
return false;
D3D11_BUFFER_DESC bd;
D3D11_SAMPLER_DESC sd;
D3D11_DEPTH_STENCIL_DESC dsd;
D3D11_RASTERIZER_DESC rd;
D3D11_BLEND_DESC bsd;
if (GSConfig.UseDebugDevice)
m_annotation = m_ctx.try_query<ID3DUserDefinedAnnotation>();
if (!m_shader_cache.Open(m_feature_level, GSConfig.UseDebugDevice))
Console.Warning("D3D11: Shader cache failed to open.");
{
// HACK: check AMD
// Broken point sampler should be enabled only on AMD.
wil::com_ptr_nothrow<IDXGIDevice> dxgi_device;
wil::com_ptr_nothrow<IDXGIAdapter1> dxgi_adapter;
if (SUCCEEDED(m_dev->QueryInterface(dxgi_device.put())) &&
SUCCEEDED(dxgi_device->GetParent(IID_PPV_ARGS(dxgi_adapter.put()))))
{
m_features.broken_point_sampler = (D3D::GetVendorID(dxgi_adapter.get()) == D3D::VendorID::AMD);
}
}
SetFeatures(dxgi_adapter.get());
std::optional<std::string> shader = ReadShaderSource("shaders/dx11/tfx.fx");
if (!shader.has_value())
return false;
m_tfx_source = std::move(*shader);
// convert
D3D11_INPUT_ELEMENT_DESC il_convert[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 16, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 28, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
const std::optional<std::string> convert_hlsl = ReadShaderSource("shaders/dx11/convert.fx");
if (!convert_hlsl.has_value())
return false;
ShaderMacro sm_vs;
sm_vs.AddMacro("VERTEX_SHADER", 1);
if (!m_shader_cache.GetVertexShaderAndInputLayout(m_dev.get(), m_convert.vs.put(), m_convert.il.put(),
il_convert, std::size(il_convert), *convert_hlsl, sm_vs.GetPtr(), "vs_main"))
{
return false;
}
const auto WrapEntryPointMacro = [](const std::string& s) { return fmt::format("__{}__", s); };
m_convert.ps.resize(ShaderConvertSelector::NUM_TOTAL_SHADERS);
for (u32 i = 0; i < ShaderConvertSelector::NUM_TOTAL_SHADERS; i++)
{
const ShaderConvertSelector shader = ShaderConvertSelector::Get(i);
const char* entry_point = shader.EntryPoint();
std::string entry_point_macro = WrapEntryPointMacro(entry_point);
ShaderMacro sm_ps;
sm_ps.AddMacro("PIXEL_SHADER", 1);
sm_ps.AddMacro("HAS_BILN", static_cast<int>(shader.Biln()));
sm_ps.AddMacro("HAS_STENCIL_OUTPUT", static_cast<int>(shader.StencilOutput()));
sm_ps.AddMacro("HAS_INTEGER_OUTPUT", static_cast<int>(shader.IntegerOutputBpp() != 0));
sm_ps.AddMacro("HAS_DEPTH_OUTPUT", static_cast<int>(shader.DepthOutput()));
sm_ps.AddMacro("HAS_FLOAT32_INPUT", static_cast<int>(shader.Float32Input()));
sm_ps.AddMacro("HAS_FLOAT32_OUTPUT", static_cast<int>(shader.Float32Output()));
sm_ps.AddMacro(entry_point_macro.c_str(), 1);
m_convert.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *convert_hlsl, sm_ps.GetPtr(), entry_point);
if (!m_convert.ps[i])
return false;
}
shader = ReadShaderSource("shaders/dx11/present.fx");
if (!shader.has_value())
return false;
if (!m_shader_cache.GetVertexShaderAndInputLayout(m_dev.get(), m_present.vs.put(), m_present.il.put(),
il_convert, std::size(il_convert), *shader, nullptr, "vs_main"))
{
return false;
}
for (size_t i = 0; i < std::size(m_present.ps); i++)
{
m_present.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *shader, nullptr, ShaderEntryPoint(static_cast<PresentShader>(i)));
if (!m_present.ps[i])
return false;
}
memset(&bd, 0, sizeof(bd));
bd.ByteWidth = sizeof(DisplayConstantBuffer);
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
m_dev->CreateBuffer(&bd, nullptr, m_present.ps_cb.put());
memset(&dsd, 0, sizeof(dsd));
m_dev->CreateDepthStencilState(&dsd, m_convert.dss.put());
dsd.DepthEnable = true;
dsd.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
dsd.DepthFunc = D3D11_COMPARISON_ALWAYS;
m_dev->CreateDepthStencilState(&dsd, m_convert.dss_write.put());
memset(&bsd, 0, sizeof(bsd));
for (u32 i = 0; i < static_cast<u32>(m_convert.bs.size()); i++)
{
bsd.RenderTarget[0].RenderTargetWriteMask = static_cast<u8>(i);
m_dev->CreateBlendState(&bsd, m_convert.bs[i].put());
}
// merge
memset(&bd, 0, sizeof(bd));
bd.ByteWidth = sizeof(MergeConstantBuffer);
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
m_dev->CreateBuffer(&bd, nullptr, m_merge.cb.put());
shader = ReadShaderSource("shaders/dx11/merge.fx");
if (!shader.has_value())
return false;
for (size_t i = 0; i < std::size(m_merge.ps); i++)
{
const std::string entry_point(StringUtil::StdStringFromFormat("ps_main%zu", i));
m_merge.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *shader, nullptr, entry_point.c_str());
if (!m_merge.ps[i])
return false;
}
memset(&bsd, 0, sizeof(bsd));
bsd.RenderTarget[0].BlendEnable = true;
bsd.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
bsd.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
bsd.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
bsd.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
bsd.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
bsd.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
bsd.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
m_dev->CreateBlendState(&bsd, m_merge.bs.put());
// interlace
memset(&bd, 0, sizeof(bd));
bd.ByteWidth = sizeof(InterlaceConstantBuffer);
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
m_dev->CreateBuffer(&bd, nullptr, m_interlace.cb.put());
shader = ReadShaderSource("shaders/dx11/interlace.fx");
if (!shader.has_value())
return false;
for (size_t i = 0; i < std::size(m_interlace.ps); i++)
{
const std::string entry_point(StringUtil::StdStringFromFormat("ps_main%zu", i));
m_interlace.ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *shader, nullptr, entry_point.c_str());
if (!m_interlace.ps[i])
return false;
}
// Shade Boost
memset(&bd, 0, sizeof(bd));
bd.ByteWidth = sizeof(float) * 4;
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
m_dev->CreateBuffer(&bd, nullptr, m_shadeboost.cb.put());
shader = ReadShaderSource("shaders/dx11/shadeboost.fx");
if (!shader.has_value())
return false;
m_shadeboost.ps = m_shader_cache.GetPixelShader(m_dev.get(), *shader, nullptr, "ps_main");
if (!m_shadeboost.ps)
return false;
// Vertex/Index Buffer
bd = {};
bd.ByteWidth = VERTEX_BUFFER_SIZE;
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_vb.put())))
{
Console.Error("D3D11: Failed to create vertex buffer.");
return false;
}
bd.ByteWidth = INDEX_BUFFER_SIZE;
bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_ib.put())))
{
Console.Error("D3D11: Failed to create index buffer.");
return false;
}
IASetIndexBuffer(m_ib.get());
if (m_features.vs_expand)
{
bd.ByteWidth = VERTEX_BUFFER_SIZE;
bd.BindFlags = D3D11_BIND_SHADER_RESOURCE;
bd.StructureByteStride = sizeof(GSVertex);
bd.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_expand_vb.put())))
{
Console.Error("D3D11: Failed to create expand vertex buffer.");
return false;
}
const CD3D11_SHADER_RESOURCE_VIEW_DESC vb_srv_desc(
D3D11_SRV_DIMENSION_BUFFER, DXGI_FORMAT_UNKNOWN, 0, VERTEX_BUFFER_SIZE / sizeof(GSVertex));
if (FAILED(m_dev->CreateShaderResourceView(m_expand_vb.get(), &vb_srv_desc, m_expand_vb_srv.put())))
{
Console.Error("D3D11: Failed to create expand vertex buffer SRV.");
return false;
}
m_ctx->VSSetShaderResources(0, 1, m_expand_vb_srv.addressof());
bd.ByteWidth = EXPAND_BUFFER_SIZE;
bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
bd.StructureByteStride = 0;
bd.MiscFlags = 0;
std::unique_ptr<u8[]> expand_data = std::make_unique<u8[]>(EXPAND_BUFFER_SIZE);
GenerateExpansionIndexBuffer(expand_data.get());
const D3D11_SUBRESOURCE_DATA srd = {expand_data.get()};
if (FAILED(m_dev->CreateBuffer(&bd, &srd, m_expand_ib.put())))
{
Console.Error("D3D11: Failed to create expand index buffer.");
return false;
}
}
if (m_features.aa1)
{
bd.ByteWidth = INDEX_BUFFER_SIZE;
bd.BindFlags = D3D11_BIND_SHADER_RESOURCE;
bd.StructureByteStride = sizeof(u32);
bd.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_expand_ib_vs.put())))
{
Console.Error("D3D11: Failed to create vertex shader index buffer.");
return false;
}
const CD3D11_SHADER_RESOURCE_VIEW_DESC expand_ib_vs_srv_desc(
D3D11_SRV_DIMENSION_BUFFER, DXGI_FORMAT_UNKNOWN, 0, INDEX_BUFFER_SIZE / sizeof(u32));
if (FAILED(m_dev->CreateShaderResourceView(m_expand_ib_vs.get(), &expand_ib_vs_srv_desc, m_expand_ib_vs_srv.put())))
{
Console.Error("D3D11: Failed to create vertex shader index buffer SRV.");
return false;
}
m_ctx->VSSetShaderResources(5, 1, m_expand_ib_vs_srv.addressof());
}
// rasterizer
memset(&rd, 0, sizeof(rd));
rd.FillMode = D3D11_FILL_SOLID;
rd.CullMode = D3D11_CULL_NONE;
rd.FrontCounterClockwise = false;
rd.DepthBias = false;
rd.DepthBiasClamp = 0;
rd.SlopeScaledDepthBias = 0;
rd.DepthClipEnable = false; // ???
rd.ScissorEnable = true;
rd.MultisampleEnable = false;
rd.AntialiasedLineEnable = false;
m_dev->CreateRasterizerState(&rd, m_rs.put());
m_ctx->RSSetState(m_rs.get());
// sampler
memset(&sd, 0, sizeof(sd));
sd.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
sd.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
sd.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
sd.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
sd.MinLOD = -FLT_MAX;
sd.MaxLOD = FLT_MAX;
sd.MaxAnisotropy = 1;
sd.ComparisonFunc = D3D11_COMPARISON_NEVER;
m_dev->CreateSamplerState(&sd, m_convert.ln.put());
sd.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
m_dev->CreateSamplerState(&sd, m_convert.pt.put());
// constant buffer
memset(&bd, 0, sizeof(bd));
bd.ByteWidth = sizeof(GSHWDrawConfig::VSConstantBuffer);
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_vs_cb.put())))
{
Console.Error("D3D11: Failed to create vertex shader constant buffer.");
return false;
}
memset(&bd, 0, sizeof(bd));
bd.ByteWidth = sizeof(GSHWDrawConfig::VSConstantBuffer);
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_vs_pc.put())))
{
Console.Error("D3D11: Failed to create vertex shader push constant buffer.");
return false;
}
memset(&bd, 0, sizeof(bd));
bd.ByteWidth = sizeof(GSHWDrawConfig::PSConstantBuffer);
bd.Usage = D3D11_USAGE_DEFAULT;
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_ps_cb.put())))
{
Console.Error("D3D11: Failed to create pixel shader constant buffer.");
return false;
}
VSSetPushConstants(0, 0, true); // Avoid undefined data.
// create layout
{
const VSSelector sel;
const GSHWDrawConfig::VSConstantBuffer cb;
SetupVS(sel, &cb);
}
// depth stencil
memset(&dsd, 0, sizeof(dsd));
dsd.DepthEnable = false;
dsd.StencilEnable = true;
dsd.StencilReadMask = 1;
dsd.StencilWriteMask = 1;
dsd.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
dsd.FrontFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE;
dsd.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsd.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
dsd.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
dsd.BackFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE;
dsd.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsd.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
m_dev->CreateDepthStencilState(&dsd, m_date.dss.put());
// blend
{
D3D11_BLEND_DESC blend;
memset(&blend, 0, sizeof(blend));
m_dev->CreateBlendState(&blend, m_date.bs.put());
}
for (size_t i = 0; i < std::size(m_date.primid_init_ps); i++)
{
const std::string entry_point(StringUtil::StdStringFromFormat("ps_primid_image_init_%zu", i));
const std::string entry_point_macro = WrapEntryPointMacro(entry_point);
ShaderMacro sm_ps;
sm_ps.AddMacro("PIXEL_SHADER", 1);
sm_ps.AddMacro(entry_point_macro.c_str(), 1);
m_date.primid_init_ps[i] = m_shader_cache.GetPixelShader(m_dev.get(), *convert_hlsl, sm_ps.GetPtr(), entry_point.c_str());
if (!m_date.primid_init_ps[i])
return false;
}
if (m_features.cas_sharpening && !CreateCASShaders())
return false;
if (!CreateImGuiResources())
return false;
if (m_feature_level < D3D_FEATURE_LEVEL_11_0)
{
Host::AddIconOSDMessage("d3d11_feature_level_warning", ICON_FA_TRIANGLE_EXCLAMATION,
TRANSLATE_SV("GS", "The Direct3D 11 renderer is running at feature level 10.0. This is an UNSUPPORTED configuration.\n"
"Do not request support, please upgrade your hardware/drivers first."),
Host::OSD_WARNING_DURATION);
}
// 1x1 dummy texture.
const GSTexture::Usage null_usage = m_features.rov ? GSTexture::ShaderWriteTarget : GSTexture::Feedback;
m_null_texture = CreateSurface(null_usage, 1, 1, 1, GSTexture::Format::Color);
if (!m_null_texture)
return false;
return true;
}
void GSDevice11::Destroy()
{
delete m_null_texture;
GSDevice::Destroy();
DestroySwapChain();
DestroyTimestampQueries();
m_convert = {};
m_present = {};
m_merge = {};
m_interlace = {};
m_shadeboost = {};
m_date = {};
m_cas = {};
m_imgui = {};
m_vb.reset();
m_ib.reset();
m_expand_vb_srv.reset();
m_expand_vb.reset();
m_expand_ib.reset();
m_vs.clear();
m_vs_cb.reset();
m_vs_pc.reset();
m_gs.clear();
m_ps.clear();
m_ps_cb.reset();
m_ps_ss.clear();
m_om_dss.clear();
m_om_bs.clear();
m_rs.reset();
if (m_state.rtv)
{
m_state.rtv->Release();
m_state.rtv = nullptr;
}
m_state.current_rt = nullptr;
if (m_state.dsv_as_rtv)
{
m_state.dsv_as_rtv->Release();
m_state.dsv_as_rtv = nullptr;
}
m_state.current_ds_as_rt = nullptr;
if (m_state.dsv)
{
m_state.dsv->Release();
m_state.dsv = nullptr;
}
m_state.current_ds = nullptr;
if (m_state.rt_uav)
{
m_state.rt_uav->Release();
m_state.rt_uav = nullptr;
}
m_state.current_rt_uav = nullptr;
if (m_state.ds_uav)
{
m_state.ds_uav->Release();
m_state.ds_uav = nullptr;
}
m_state.current_ds_uav = nullptr;
m_shader_cache.Close();
#ifdef REPORT_LEAKED_OBJECTS
wil::com_ptr_nothrow<ID3D11Debug> debug;
m_dev.try_query_to(&debug);
#endif
m_annotation.reset();
m_ctx.reset();
m_dev.reset();
m_dxgi_factory.reset();
#ifdef REPORT_LEAKED_OBJECTS
if (debug)
debug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL | D3D11_RLDO_IGNORE_INTERNAL);
#endif
}
void GSDevice11::SetFeatures(IDXGIAdapter1* adapter)
{
// Check these first as others depend on them.
m_features.multidraw_fb_copy = GSConfig.OverrideTextureBarriers != 0;
m_features.vs_expand = (!GSConfig.DisableVertexShaderExpand && m_feature_level >= D3D_FEATURE_LEVEL_11_0);
// Check all three formats, since the feature means any can be used.
m_features.dxt_textures = SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC1_UNORM) &&
SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC2_UNORM) &&
SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC3_UNORM);
m_features.bptc_textures = SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC7_UNORM);
m_features.cas_sharpening = (m_feature_level >= D3D_FEATURE_LEVEL_11_0);
m_features.test_and_sample_depth = (m_feature_level >= D3D_FEATURE_LEVEL_11_0);
m_features.depth_feedback = m_features.multidraw_fb_copy && GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Depth;
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand && m_features.feedback_loops();
m_max_texture_size = (m_feature_level >= D3D_FEATURE_LEVEL_11_0) ?
D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION :
D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION;
m_conservative_depth = (m_feature_level >= D3D_FEATURE_LEVEL_11_0);
m_rgba16_unorm_hw_blend = IsTextureFormatHWBlendable(m_dev.get(), DXGI_FORMAT_R16G16B16A16_UNORM);
m_uav_texture = (m_feature_level >= D3D_FEATURE_LEVEL_11_0);
D3D11_FEATURE_DATA_D3D11_OPTIONS2 options2{};
m_dev->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS2, &options2, sizeof(options2));
m_features.rov = m_uav_texture && options2.ROVsSupported;
for (u32 fmt = static_cast<u32>(GSTexture::Format::Color); fmt <= static_cast<u32>(GSTexture::Format::PrimID); fmt++)
{
if (GSTexture::IsShaderWriteFormat(static_cast<GSTexture::Format>(fmt)))
m_features.rov &= IsTextureFormatUAVCapable(m_dev.get(), GSTexture11::GetDXGIFormat(static_cast<GSTexture::Format>(fmt)));
}
// Let the user know if said features are available.
Console.WriteLnFmt("D3D11: DXTn Texture Compression: {}", m_features.dxt_textures ? "Supported" : "Not Supported");
Console.WriteLnFmt("D3D11: BC6/7 Texture Compression: {}", m_features.bptc_textures ? "Supported" : "Not Supported");
Console.WriteLnFmt("D3D11: Conservative Depth: {}", m_conservative_depth ? "Supported" : "Not Supported");
Console.WriteLnFmt("D3D11: Depth Testing and Sampling: {}", m_features.test_and_sample_depth ? "Supported" : "Not Supported");
Console.WriteLnFmt("D3D11: RGBA16 UNORM Hardware Blending: {}", m_rgba16_unorm_hw_blend ? "Supported" : "Not Supported");
Console.WriteLnFmt("D3D11: Rasterizer Ordered Views: {}", m_features.rov ? "Supported" : "Not Supported");
}
bool GSDevice11::HasSurface() const
{
return static_cast<bool>(m_swap_chain);
}
void GSDevice11::SetVSyncMode(GSVSyncMode mode, bool allow_present_throttle)
{
m_allow_present_throttle = allow_present_throttle;
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
if (mode == GSVSyncMode::Mailbox && m_is_exclusive_fullscreen)
{
WARNING_LOG("D3D11: Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
mode = GSVSyncMode::FIFO;
}
if (m_vsync_mode == mode)
return;
const u32 old_buffer_count = GetSwapChainBufferCount();
m_vsync_mode = mode;
if (!m_swap_chain)
return;
if (GetSwapChainBufferCount() != old_buffer_count)
{
DestroySwapChain();
if (!CreateSwapChain())
pxFailRel("D3D11: Failed to recreate swap chain after vsync change.");
}
}
u32 GSDevice11::GetSwapChainBufferCount() const
{
// With vsync off, we only need two buffers. Same for blocking vsync.
// With triple buffering, we need three.
return (m_vsync_mode == GSVSyncMode::Mailbox) ? 3 : 2;
}
bool GSDevice11::CreateSwapChain()
{
constexpr DXGI_FORMAT swap_chain_format = DXGI_FORMAT_R8G8B8A8_UNORM;
if (m_window_info.type != WindowInfo::Type::Win32)
return false;
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
RECT client_rc{};
GetClientRect(window_hwnd, &client_rc);
DXGI_MODE_DESC fullscreen_mode;
wil::com_ptr_nothrow<IDXGIOutput> fullscreen_output;
if (Host::IsFullscreen())
{
u32 fullscreen_width, fullscreen_height;
float fullscreen_refresh_rate;
m_is_exclusive_fullscreen =
GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) &&
D3D::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.get(), window_hwnd, fullscreen_width,
fullscreen_height, fullscreen_refresh_rate, swap_chain_format, &fullscreen_mode,
fullscreen_output.put());
// Using mailbox-style no-allow-tearing causes tearing in exclusive fullscreen.
if (m_vsync_mode == GSVSyncMode::Mailbox && m_is_exclusive_fullscreen)
{
WARNING_LOG("D3D11: Using FIFO instead of Mailbox vsync due to exclusive fullscreen.");
m_vsync_mode = GSVSyncMode::FIFO;
}
}
else
{
m_is_exclusive_fullscreen = false;
}
m_using_flip_model_swap_chain = !GSConfig.UseBlitSwapChain || m_is_exclusive_fullscreen;
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left);
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
swap_chain_desc.Format = swap_chain_format;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.BufferCount = GetSwapChainBufferCount();
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SwapEffect =
m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD;
m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen);
if (m_using_allow_tearing)
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
HRESULT hr = S_OK;
if (m_is_exclusive_fullscreen)
{
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
fs_sd_desc.Width = fullscreen_mode.Width;
fs_sd_desc.Height = fullscreen_mode.Height;
fs_desc.RefreshRate = fullscreen_mode.RefreshRate;
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering;
fs_desc.Scaling = fullscreen_mode.Scaling;
fs_desc.Windowed = FALSE;
Console.WriteLn("D3D11: Creating a %dx%d exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
hr = m_dxgi_factory->CreateSwapChainForHwnd(
m_dev.get(), window_hwnd, &fs_sd_desc, &fs_desc, fullscreen_output.get(), m_swap_chain.put());
if (FAILED(hr))
{
Console.Warning("D3D11: Failed to create fullscreen swap chain, trying windowed.");
m_is_exclusive_fullscreen = false;
m_using_allow_tearing = m_allow_tearing_supported && m_using_flip_model_swap_chain;
}
}
if (!m_is_exclusive_fullscreen)
{
Console.WriteLn("D3D11: Creating a %dx%d %s windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
m_using_flip_model_swap_chain ? "flip-discard" : "discard");
hr = m_dxgi_factory->CreateSwapChainForHwnd(
m_dev.get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, m_swap_chain.put());
}
if (FAILED(hr) && m_using_flip_model_swap_chain)
{
Console.Warning("D3D11: Failed to create a flip-discard swap chain, trying discard.");
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
swap_chain_desc.Flags = 0;
m_using_flip_model_swap_chain = false;
m_using_allow_tearing = false;
hr = m_dxgi_factory->CreateSwapChainForHwnd(
m_dev.get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, m_swap_chain.put());
if (FAILED(hr))
{
Console.Error("D3D11: CreateSwapChainForHwnd failed: 0x%08X", hr);
return false;
}
}
// MWA needs to be called on the correct factory.
wil::com_ptr_nothrow<IDXGIFactory> swap_chain_factory;
hr = m_swap_chain->GetParent(IID_PPV_ARGS(swap_chain_factory.put()));
if (SUCCEEDED(hr))
{
hr = swap_chain_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
if (FAILED(hr))
Console.ErrorFmt("D3D11: MakeWindowAssociation() to disable ALT+ENTER failed: {}", Error::CreateHResult(hr).GetDescription());
}
else
{
Console.ErrorFmt("D3D11: GetParent() on swap chain to get factory failed: {}", Error::CreateHResult(hr).GetDescription());
}
if (!CreateSwapChainRTV())
{
DestroySwapChain();
return false;
}
// Render a frame as soon as possible to clear out whatever was previously being displayed.
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), s_present_clear_color.data());
m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
return true;
}
bool GSDevice11::CreateSwapChainRTV()
{
wil::com_ptr_nothrow<ID3D11Texture2D> backbuffer;
HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.put()));
if (FAILED(hr))
{
Console.Error("D3D11: GetBuffer for RTV failed: 0x%08X", hr);
return false;
}
D3D11_TEXTURE2D_DESC backbuffer_desc;
backbuffer->GetDesc(&backbuffer_desc);
CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(
D3D11_RTV_DIMENSION_TEXTURE2D, backbuffer_desc.Format, 0, 0, backbuffer_desc.ArraySize);
hr = m_dev->CreateRenderTargetView(backbuffer.get(), &rtv_desc, m_swap_chain_rtv.put());
if (FAILED(hr))
{
Console.Error("D3D11: CreateRenderTargetView for swap chain failed: 0x%08X", hr);
m_swap_chain_rtv.reset();
return false;
}
m_window_info.surface_width = backbuffer_desc.Width;
m_window_info.surface_height = backbuffer_desc.Height;
DevCon.WriteLn("D3D11: Swap chain buffer size: %ux%u", m_window_info.surface_width, m_window_info.surface_height);
if (m_window_info.type == WindowInfo::Type::Win32)
{
BOOL fullscreen = FALSE;
DXGI_SWAP_CHAIN_DESC desc;
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&fullscreen, nullptr)) && fullscreen &&
SUCCEEDED(m_swap_chain->GetDesc(&desc)))
{
m_window_info.surface_refresh_rate = static_cast<float>(desc.BufferDesc.RefreshRate.Numerator) /
static_cast<float>(desc.BufferDesc.RefreshRate.Denominator);
}
}
return true;
}
void GSDevice11::DestroySwapChain()
{
if (!m_swap_chain)
return;
m_swap_chain_rtv.reset();
// switch out of fullscreen before destroying
BOOL is_fullscreen;
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
m_swap_chain->SetFullscreenState(FALSE, nullptr);
m_swap_chain.reset();
m_is_exclusive_fullscreen = false;
}
bool GSDevice11::UpdateWindow()
{
DestroySwapChain();
if (!AcquireWindow(false))
return false;
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
{
Console.WriteLn("D3D11: Failed to create swap chain on updated window");
return false;
}
return true;
}
void GSDevice11::DestroySurface()
{
DestroySwapChain();
}
std::string GSDevice11::GetDriverInfo() const
{
std::string ret = "Unknown Feature Level";
static constexpr std::array<std::tuple<D3D_FEATURE_LEVEL, const char*>, 2> feature_level_names = {{
{D3D_FEATURE_LEVEL_10_0, "D3D_FEATURE_LEVEL_10_0"},
{D3D_FEATURE_LEVEL_11_0, "D3D_FEATURE_LEVEL_11_0"},
}};
for (size_t i = 0; i < std::size(feature_level_names); i++)
{
if (m_feature_level == std::get<0>(feature_level_names[i]))
{
ret = std::get<1>(feature_level_names[i]);
break;
}
}
ret += "\n";
wil::com_ptr_nothrow<IDXGIDevice> dxgi_dev;
if (m_dev.try_query_to(&dxgi_dev))
{
wil::com_ptr_nothrow<IDXGIAdapter> dxgi_adapter;
if (SUCCEEDED(dxgi_dev->GetAdapter(dxgi_adapter.put())))
{
DXGI_ADAPTER_DESC desc;
if (SUCCEEDED(dxgi_adapter->GetDesc(&desc)))
{
ret += StringUtil::StdStringFromFormat("VID: 0x%04X PID: 0x%04X\n", desc.VendorId, desc.DeviceId);
ret += StringUtil::WideStringToUTF8String(desc.Description);
ret += "\n";
const std::string driver_version(D3D::GetDriverVersionFromLUID(desc.AdapterLuid));
if (!driver_version.empty())
{
ret += "Driver Version: ";
ret += driver_version;
}
}
}
}
return ret;
}
void GSDevice11::ResizeWindow(u32 new_window_width, u32 new_window_height, float new_window_scale)
{
if (!m_swap_chain || m_is_exclusive_fullscreen)
return;
m_window_info.surface_scale = new_window_scale;
if (m_window_info.surface_width == new_window_width && m_window_info.surface_height == new_window_height)
return;
m_swap_chain_rtv.reset();
HRESULT hr = m_swap_chain->ResizeBuffers(
0, 0, 0, DXGI_FORMAT_UNKNOWN, m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0);
if (FAILED(hr))
Console.Error("D3D11: ResizeBuffers() failed: 0x%08X", hr);
if (!CreateSwapChainRTV())
pxFailRel("Failed to recreate swap chain RTV after resize");
}
bool GSDevice11::SupportsExclusiveFullscreen() const
{
return true;
}
GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip)
{
if (frame_skip || !m_swap_chain)
return PresentResult::FrameSkipped;
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
// This might get called repeatedly if it takes a while to switch back, that's the host's problem.
BOOL is_fullscreen;
if (m_is_exclusive_fullscreen &&
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
{
Host::RunOnCPUThread([]() { Host::SetFullscreen(false); });
return PresentResult::FrameSkipped;
}
// When using vsync, the time here seems to include the time for the buffer to become available.
// This blows our our GPU usage number considerably, so read the timestamp before the final blit
// in this configuration. It does reduce accuracy a little, but better than seeing 100% all of
// the time, when it's more like a couple of percent.
if (m_vsync_mode == GSVSyncMode::FIFO && m_gpu_timing_enabled)
PopTimestampQuery();
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), s_present_clear_color.data());
m_ctx->OMSetRenderTargets(1, m_swap_chain_rtv.addressof(), nullptr);
if (m_state.rtv)
m_state.rtv->Release();
m_state.rtv = m_swap_chain_rtv.get();
m_state.rtv->AddRef();
m_state.current_rt = nullptr;
if (m_state.dsv)
{
m_state.dsv->Release();
m_state.dsv = nullptr;
}
m_state.current_ds = nullptr;
g_perfmon.Put(GSPerfMon::RenderPasses, 1);
const GSVector2i size = GetWindowSize();
SetViewport(size);
SetScissor(GSVector4i::loadh(size));
return PresentResult::OK;
}
void GSDevice11::EndPresent()
{
RenderImGui();
// See note in BeginPresent() for why it's conditional on vsync-off.
if (m_vsync_mode != GSVSyncMode::FIFO && m_gpu_timing_enabled)
PopTimestampQuery();
// clear out the swap chain view, it might get resized..
OMSetRenderTargets(nullptr, nullptr, nullptr);
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GSVSyncMode::FIFO);
const UINT flags = (m_vsync_mode == GSVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;
m_swap_chain->Present(sync_interval, flags);
if (m_gpu_timing_enabled)
KickTimestampQuery();
}
bool GSDevice11::CreateTimestampQueries()
{
for (u32 i = 0; i < NUM_TIMESTAMP_QUERIES; i++)
{
for (u32 j = 0; j < 3; j++)
{
const CD3D11_QUERY_DESC qdesc((j == 0) ? D3D11_QUERY_TIMESTAMP_DISJOINT : D3D11_QUERY_TIMESTAMP);
const HRESULT hr = m_dev->CreateQuery(&qdesc, m_timestamp_queries[i][j].put());
if (FAILED(hr))
{
m_timestamp_queries = {};
return false;
}
}
}
KickTimestampQuery();
return true;
}
void GSDevice11::DestroyTimestampQueries()
{
if (!m_timestamp_queries[0][0])
return;
if (m_timestamp_query_started)
m_ctx->End(m_timestamp_queries[m_write_timestamp_query][1].get());
m_timestamp_queries = {};
m_read_timestamp_query = 0;
m_write_timestamp_query = 0;
m_waiting_timestamp_queries = 0;
m_timestamp_query_started = 0;
}
void GSDevice11::PopTimestampQuery()
{
while (m_waiting_timestamp_queries > 0)
{
D3D11_QUERY_DATA_TIMESTAMP_DISJOINT disjoint;
const HRESULT disjoint_hr = m_ctx->GetData(m_timestamp_queries[m_read_timestamp_query][0].get(), &disjoint,
sizeof(disjoint), D3D11_ASYNC_GETDATA_DONOTFLUSH);
if (disjoint_hr != S_OK)
break;
if (disjoint.Disjoint)
{
DevCon.WriteLn("D3D11: GPU timing disjoint, resetting.");
m_read_timestamp_query = 0;
m_write_timestamp_query = 0;
m_waiting_timestamp_queries = 0;
m_timestamp_query_started = 0;
}
else
{
u64 start = 0, end = 0;
const HRESULT start_hr = m_ctx->GetData(m_timestamp_queries[m_read_timestamp_query][1].get(), &start,
sizeof(start), D3D11_ASYNC_GETDATA_DONOTFLUSH);
const HRESULT end_hr = m_ctx->GetData(m_timestamp_queries[m_read_timestamp_query][2].get(), &end,
sizeof(end), D3D11_ASYNC_GETDATA_DONOTFLUSH);
if (start_hr == S_OK && end_hr == S_OK)
{
m_accumulated_gpu_time += static_cast<float>(
static_cast<double>(end - start) / (static_cast<double>(disjoint.Frequency) / 1000.0));
m_read_timestamp_query = (m_read_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES;
m_waiting_timestamp_queries--;
}
else
{
break;
}
}
}
if (m_timestamp_query_started)
{
m_ctx->End(m_timestamp_queries[m_write_timestamp_query][2].get());
m_ctx->End(m_timestamp_queries[m_write_timestamp_query][0].get());
m_write_timestamp_query = (m_write_timestamp_query + 1) % NUM_TIMESTAMP_QUERIES;
m_timestamp_query_started = false;
m_waiting_timestamp_queries++;
}
}
void GSDevice11::KickTimestampQuery()
{
if (m_timestamp_query_started || !m_timestamp_queries[0][0] || m_waiting_timestamp_queries == NUM_TIMESTAMP_QUERIES)
return;
m_ctx->Begin(m_timestamp_queries[m_write_timestamp_query][0].get());
m_ctx->End(m_timestamp_queries[m_write_timestamp_query][1].get());
m_timestamp_query_started = true;
}
bool GSDevice11::SetGPUTimingEnabled(bool enabled)
{
if (m_gpu_timing_enabled == enabled)
return true;
m_gpu_timing_enabled = enabled;
if (m_gpu_timing_enabled)
{
return CreateTimestampQueries();
}
else
{
DestroyTimestampQueries();
return true;
}
}
float GSDevice11::GetAndResetAccumulatedGPUTime()
{
const float value = m_accumulated_gpu_time;
m_accumulated_gpu_time = 0.0f;
return value;
}
void GSDevice11::DrawPrimitive()
{
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
PSUpdateShaderState(true, true);
m_ctx->Draw(m_vertex.count, m_vertex.start);
}
void GSDevice11::DrawIndexedPrimitive()
{
DrawIndexedPrimitive(0, m_index.count);
}
void GSDevice11::DrawIndexedPrimitive(int offset, int count)
{
pxAssert(offset + count <= (int)m_index.count);
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
PSUpdateShaderState(true, true);
m_ctx->DrawIndexed(count, m_index.start + offset, m_vertex.start);
}
void GSDevice11::DrawIndexedPrimitiveVSExpand(int offset, int count, bool vs_indexing, int vs_indexing_expansion)
{
pxAssert(offset + count <= (int)m_index.count);
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
PSUpdateShaderState(true, true);
if (vs_indexing)
{
VSSetPushConstants(m_vertex.start, m_index.start + offset);
m_ctx->Draw(count * vs_indexing_expansion, 0);
}
else
{
VSSetPushConstants(m_vertex.start);
m_ctx->DrawIndexed(count, m_index.start + offset, 0);
}
}
void GSDevice11::Draw(const GSHWDrawConfig& config, int offset, int count)
{
if (config.vs.expand != GSHWDrawConfig::VSExpand::None)
{
const bool vs_indexing = config.vs.UseVSExpandIndexBuffer();
const u32 vs_indexing_expansion = GetExpansionFactor(config.vs.expand);
DrawIndexedPrimitiveVSExpand(offset, count, vs_indexing, vs_indexing_expansion);
}
else
{
DrawIndexedPrimitive(offset, count);
}
}
void GSDevice11::Draw(const GSHWDrawConfig& config)
{
Draw(config, 0, m_index.count);
}
void GSDevice11::CommitClear(GSTexture* t)
{
GSTexture11* T = static_cast<GSTexture11*>(t);
if (!T->IsRenderTargetOrDepthStencil() || T->GetState() == GSTexture::State::Dirty)
return;
if (T->IsDepthStencil())
{
if (T->GetState() == GSTexture::State::Invalidated)
m_ctx->DiscardView(static_cast<ID3D11DepthStencilView*>(*T));
else
m_ctx->ClearDepthStencilView(*T, D3D11_CLEAR_DEPTH, T->GetClearDepth(), 0);
}
else
{
if (T->GetState() == GSTexture::State::Invalidated)
m_ctx->DiscardView(static_cast<ID3D11RenderTargetView*>(*T));
else
m_ctx->ClearRenderTargetView(*T, T->GetClearForFormat().F32);
}
T->SetState(GSTexture::State::Dirty);
}
void GSDevice11::PushDebugGroup(const char* fmt, ...)
{
if (!m_annotation)
return;
std::va_list ap;
va_start(ap, fmt);
std::string str(StringUtil::StdStringFromFormatV(fmt, ap));
va_end(ap);
m_annotation->BeginEvent(StringUtil::UTF8StringToWideString(str).c_str());
}
void GSDevice11::PopDebugGroup()
{
if (!m_annotation)
return;
m_annotation->EndEvent();
}
void GSDevice11::InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...)
{
if (!m_annotation)
return;
std::va_list ap;
va_start(ap, fmt);
std::string str(StringUtil::StdStringFromFormatV(fmt, ap));
va_end(ap);
m_annotation->SetMarker(StringUtil::UTF8StringToWideString(str).c_str());
}
GSTexture* GSDevice11::CreateSurface(GSTexture::Usage usage, int width, int height, int levels, GSTexture::Format format)
{
pxAssert(GSTexture::ValidateUsageAndFormat(usage, format));
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = width;
desc.Height = height;
desc.Format = GSTexture11::GetDXGIFormat(format);
desc.MipLevels = levels;
desc.ArraySize = 1;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
if (GSTexture::IsTexture(usage))
{
desc.BindFlags |= (levels > 1 && !GSTexture::IsCompressedFormat(format)) ? D3D11_BIND_RENDER_TARGET : 0;
desc.MiscFlags |= (levels > 1 && !GSTexture::IsCompressedFormat(format)) ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0;
}
if (GSTexture::IsRenderTarget(usage))
{
desc.BindFlags |= D3D11_BIND_RENDER_TARGET;
}
if (GSTexture::IsDepthStencil(usage))
{
desc.BindFlags |= D3D11_BIND_DEPTH_STENCIL;
}
if (GSTexture::IsShaderWrite(usage))
{
desc.BindFlags |= D3D11_BIND_UNORDERED_ACCESS;
}
wil::com_ptr_nothrow<ID3D11Texture2D> texture;
HRESULT hr = m_dev->CreateTexture2D(&desc, nullptr, texture.put());
if (FAILED(hr))
{
Console.Error("D3D11: Failed to allocate %dx%d surface", width, height);
return nullptr;
}
return new GSTexture11(std::move(texture), desc, usage, format);
}
std::unique_ptr<GSDownloadTexture> GSDevice11::CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format)
{
return GSDownloadTexture11::Create(width, height, format);
}
void GSDevice11::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY)
{
// Empty rect, abort copy.
if (r.rempty())
{
GL_INS("D3D11: CopyRect rect empty.");
return;
}
const GSVector4i src_rect(0, 0, sTex->GetWidth(), sTex->GetHeight());
const GSVector4i dst_rect(0, 0, dTex->GetWidth(), dTex->GetHeight());
const bool src_dst_rect_match = src_rect.eq(dst_rect);
// Sizes must match for full depth copies when no partial copies are supported.
if (sTex->IsDepthStencil() && !src_dst_rect_match)
{
GL_INS("D3D11: CopyRect rect mismatch for full depth copy.");
return;
}
const bool full_draw_copy = sTex->IsDepthStencil() || dst_rect.eq(r);
// Source is cleared, if destination is a render target, we can carry the clear forward.
if (sTex->GetState() == GSTexture::State::Cleared)
{
if (dTex->IsRenderTargetOrDepthStencil() && ProcessClearsBeforeCopy(sTex, dTex, full_draw_copy))
return;
// Commit clear for the source texture.
CommitClear(sTex);
}
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
// Commit destination clear if partially overwritten (color only).
if (dTex->GetState() == GSTexture::State::Cleared && !full_draw_copy)
CommitClear(dTex);
// DX11 doesn't support partial depth copy so we need to
// either pass a nullptr D3D11_BOX for a full depth copy or use CopyResource instead.
// Optimization: Use CopyResource for depth copies or full rect color copies, it's faster than CopySubresourceRegion.
const bool full_rt_copy = src_dst_rect_match && (sTex->IsDepthStencil() || (destX == 0 && destY == 0 && r.eq(src_rect)));
if (full_rt_copy)
{
m_ctx->CopyResource(*static_cast<GSTexture11*>(dTex), *static_cast<GSTexture11*>(sTex));
}
else
{
const D3D11_BOX box = {static_cast<UINT>(r.left), static_cast<UINT>(r.top), 0U, static_cast<UINT>(r.right), static_cast<UINT>(r.bottom), 1U};
m_ctx->CopySubresourceRegion(*static_cast<GSTexture11*>(dTex), 0, destX, destY, 0, *static_cast<GSTexture11*>(sTex), 0, &box);
}
dTex->SetState(GSTexture::State::Dirty);
}
void GSDevice11::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
ShaderConvertSelector shader, Filter filter)
{
const u8 mask = shader.Mask();
shader = shader.SetMask(); // Mask is handled separately from program.
filter = shader.SupportsBilinear() ? Nearest : filter; // Don't allow HW bilinear if SW bilinear is needed.
DoStretchRect(sTex, sRect, dTex, dRect, GetConvertShader(shader), nullptr, m_convert.bs[mask].get(), filter);
}
void GSDevice11::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, Filter filter)
{
DoStretchRect(sTex, sRect, dTex, dRect, ps, ps_cb, m_convert.bs[D3D11_COLOR_WRITE_ENABLE_ALL].get(), filter);
}
void GSDevice11::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, ID3D11BlendState* bs, Filter filter)
{
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
CommitClear(sTex);
const bool draw_in_depth = dTex && dTex->IsDepthStencil();
GSVector2i ds;
if (dTex)
{
ds = dTex->GetSize();
if (draw_in_depth)
OMSetRenderTargets(nullptr, nullptr, dTex);
else
OMSetRenderTargets(dTex, nullptr, nullptr);
}
else
{
ds = GSVector2i(m_window_info.surface_width, m_window_info.surface_height);
}
// om
if (draw_in_depth)
OMSetDepthStencilState(m_convert.dss_write.get(), 0);
else
OMSetDepthStencilState(m_convert.dss.get(), 0);
OMSetBlendState(bs, 0);
// ia
const float inv_x = 2.0f / ds.x;
const float inv_y = 2.0f / ds.y;
const float left = dRect.x * inv_x - 1.0f;
const float right = dRect.z * inv_x - 1.0f;
const float top = 1.0f - dRect.y * inv_y;
const float bottom = 1.0f - dRect.w * inv_y;
const GSVertexPT1 vertices[] =
{
{GSVector4(left, top, 0.5f, 1.0f), GSVector2(sRect.x, sRect.y)},
{GSVector4(right, top, 0.5f, 1.0f), GSVector2(sRect.z, sRect.y)},
{GSVector4(left, bottom, 0.5f, 1.0f), GSVector2(sRect.x, sRect.w)},
{GSVector4(right, bottom, 0.5f, 1.0f), GSVector2(sRect.z, sRect.w)},
};
IASetVertexBuffer(vertices, sizeof(vertices[0]), std::size(vertices));
IASetInputLayout(m_convert.il.get());
IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
// vs
VSSetShader(m_convert.vs.get(), nullptr);
// ps
PSSetShaderResource(TEXTURE_TEXTURE, sTex);
PSSetSamplerState(filter == Biln ? m_convert.ln.get() : m_convert.pt.get());
PSSetShader(ps, ps_cb);
// draw
DrawPrimitive();
}
void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, Filter filter)
{
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
CommitClear(sTex);
GSVector2i ds;
if (dTex)
{
ds = dTex->GetSize();
OMSetRenderTargets(dTex, nullptr, nullptr);
}
else
{
ds = GSVector2i(m_window_info.surface_width, m_window_info.surface_height);
}
DisplayConstantBuffer cb;
cb.SetSource(sRect, sTex->GetSize());
cb.SetTarget(dRect, ds);
cb.SetTime(shaderTime);
UpdateSubresource(m_present.ps_cb.get(), &cb, &m_present.cb_uniforms, sizeof(cb));
// om
OMSetDepthStencilState(m_convert.dss.get(), 0);
OMSetBlendState(m_convert.bs[D3D11_COLOR_WRITE_ENABLE_ALL].get(), 0);
// ia
const float inv_x = 2.0f / ds.x;
const float inv_y = 2.0f / ds.y;
const float left = dRect.x * inv_x - 1.0f;
const float right = dRect.z * inv_x - 1.0f;
const float top = 1.0f - dRect.y * inv_y;
const float bottom = 1.0f - dRect.w * inv_y;
const GSVertexPT1 vertices[] =
{
{GSVector4(left, top, 0.5f, 1.0f), GSVector2(sRect.x, sRect.y)},
{GSVector4(right, top, 0.5f, 1.0f), GSVector2(sRect.z, sRect.y)},
{GSVector4(left, bottom, 0.5f, 1.0f), GSVector2(sRect.x, sRect.w)},
{GSVector4(right, bottom, 0.5f, 1.0f), GSVector2(sRect.z, sRect.w)},
};
IASetVertexBuffer(vertices, sizeof(vertices[0]), std::size(vertices));
IASetInputLayout(m_present.il.get());
IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
// vs
VSSetShader(m_present.vs.get(), nullptr);
// ps
PSSetShaderResource(TEXTURE_TEXTURE, sTex);
PSSetSamplerState(filter == Biln ? m_convert.ln.get() : m_convert.pt.get());
PSSetShader(m_present.ps[static_cast<u32>(shader)].get(), m_present.ps_cb.get());
// draw
DrawPrimitive();
}
void GSDevice11::UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize)
{
// match merge cb
struct alignas(16) Uniforms
{
float scale;
float pad1[3];
u32 offsetX, offsetY, dOffset;
u32 pad2;
};
const Uniforms cb = {sScale, {}, offsetX, offsetY, dOffset, 0};
UpdateSubresource(m_merge.cb.get(), &cb, &m_merge.cb_uniforms, sizeof(cb));
const GSVector4 dRect(0, 0, dSize, 1);
const ShaderConvert shader = (dSize == 16) ? ShaderConvert::CLUT_4 : ShaderConvert::CLUT_8;
DoStretchRect(sTex, GSVector4::zero(), dTex, dRect, GetConvertShader(shader), m_merge.cb.get(), nullptr, Nearest);
}
void GSDevice11::ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM)
{
// match merge cb
struct alignas(16) Uniforms
{
float scale;
float pad1[3];
u32 SBW, DBW, SPSM;
u32 pad2;
};
const Uniforms cb = {sScale, {}, SBW, DBW, SPSM, 0};
UpdateSubresource(m_merge.cb.get(), &cb, &m_merge.cb_uniforms, sizeof(cb));
const GSVector4 dRect(0, 0, dTex->GetWidth(), dTex->GetHeight());
const ShaderConvert shader = ((SPSM & 0xE) == 0) ? ShaderConvert::RGBA_TO_8I : ShaderConvert::RGB5A1_TO_8I;
DoStretchRect(sTex, GSVector4::zero(), dTex, dRect, GetConvertShader(shader), m_merge.cb.get(), nullptr, Nearest);
}
void GSDevice11::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32 downsample_factor, const GSVector2i& clamp_min, const GSVector4& dRect)
{
struct alignas(16) Uniforms
{
float weight;
float step_multiplier;
float pad0[2];
GSVector2i clamp_min;
int downsample_factor;
int pad1;
};
const Uniforms cb = {
static_cast<float>(downsample_factor * downsample_factor), (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f, {}, clamp_min, static_cast<int>(downsample_factor), 0};
UpdateSubresource(m_merge.cb.get(), &cb, &m_merge.cb_uniforms, sizeof(cb));
const ShaderConvert shader = ShaderConvert::DOWNSAMPLE_COPY;
DoStretchRect(sTex, GSVector4::zero(), dTex, dRect, GetConvertShader(shader), m_merge.cb.get(), nullptr, Nearest);
}
void GSDevice11::DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, GSTexture* dTex, ShaderConvertSelector shader)
{
shader = shader.SetMask(); // Mask is handled separately from program.
IASetInputLayout(m_convert.il.get());
IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
VSSetShader(m_convert.vs.get(), nullptr);
PSSetShader(GetConvertShader(shader), nullptr);
OMSetDepthStencilState(dTex->IsRenderTarget() ? m_convert.dss.get() : m_convert.dss_write.get(), 0);
OMSetRenderTargets(dTex->IsRenderTarget() ? dTex : nullptr, nullptr, dTex->IsDepthStencil() ? dTex : nullptr);
const GSVector2 ds(static_cast<float>(dTex->GetWidth()), static_cast<float>(dTex->GetHeight()));
GSTexture* last_tex = rects[0].src;
Filter last_filter = rects[0].filter;
u8 last_wmask = rects[0].wmask.wrgba;
u32 first = 0;
u32 count = 1;
for (u32 i = 1; i < num_rects; i++)
{
if (rects[i].src == last_tex && rects[i].filter == last_filter && rects[i].wmask.wrgba == last_wmask)
{
count++;
continue;
}
DoMultiStretchRects(rects + first, count, ds);
last_tex = rects[i].src;
last_filter = rects[i].filter;
last_wmask = rects[i].wmask.wrgba;
first += count;
count = 1;
}
DoMultiStretchRects(rects + first, count, ds);
}
void GSDevice11::DoMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, const GSVector2& ds)
{
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
// Don't use primitive restart here, it ends up slower on some drivers.
const u32 vertex_reserve_size = num_rects * 4;
const u32 index_reserve_size = num_rects * 6;
GSVertexPT1* verts = static_cast<GSVertexPT1*>(IAMapVertexBuffer(sizeof(GSVertexPT1), vertex_reserve_size));
u16* idx = IAMapIndexBuffer(index_reserve_size);
u32 icount = 0;
u32 vcount = 0;
for (u32 i = 0; i < num_rects; i++)
{
const GSVector4& sRect = rects[i].src_rect;
const GSVector4& dRect = rects[i].dst_rect;
const float inv_x = 2.0f / ds.x;
const float inv_y = 2.0f / ds.y;
const float left = dRect.x * inv_x - 1.0f;
const float right = dRect.z * inv_x - 1.0f;
const float top = 1.0f - dRect.y * inv_y;
const float bottom = 1.0f - dRect.w * inv_y;
const u32 vstart = vcount;
verts[vcount++] = {GSVector4(left, top, 0.5f, 1.0f), GSVector2(sRect.x, sRect.y)};
verts[vcount++] = {GSVector4(right, top, 0.5f, 1.0f), GSVector2(sRect.z, sRect.y)};
verts[vcount++] = {GSVector4(left, bottom, 0.5f, 1.0f), GSVector2(sRect.x, sRect.w)};
verts[vcount++] = {GSVector4(right, bottom, 0.5f, 1.0f), GSVector2(sRect.z, sRect.w)};
if (i > 0)
idx[icount++] = vstart;
idx[icount++] = vstart;
idx[icount++] = vstart + 1;
idx[icount++] = vstart + 2;
idx[icount++] = vstart + 3;
idx[icount++] = vstart + 3;
};
IAUnmapVertexBuffer(sizeof(GSVertexPT1), vcount);
IAUnmapIndexBuffer(icount);
IASetIndexBuffer(m_ib.get());
CommitClear(rects[0].src);
PSSetShaderResource(TEXTURE_TEXTURE, rects[0].src);
PSSetSamplerState(rects[0].filter == Biln ? m_convert.ln.get() : m_convert.pt.get());
OMSetBlendState(m_convert.bs[rects[0].wmask.wrgba].get(), 0.0f);
DrawIndexedPrimitive();
}
void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, u32 c, const Filter filter)
{
const GSVector4 full_r(0.0f, 0.0f, 1.0f, 1.0f);
const bool feedback_write_2 = PMODE.EN2 && sTex[2] != nullptr && EXTBUF.FBIN == 1;
const bool feedback_write_1 = PMODE.EN1 && sTex[2] != nullptr && EXTBUF.FBIN == 0;
const bool feedback_write_2_but_blend_bg = feedback_write_2 && PMODE.SLBG == 1;
// Merge the 2 source textures (sTex[0],sTex[1]). Final results go to dTex. Feedback write will go to sTex[2].
// If either 2nd output is disabled or SLBG is 1, a background color will be used.
// Note: background color is also used when outside of the unit rectangle area
ClearRenderTarget(dTex, c);
// Upload constant to select YUV algo, but skip constant buffer update if we don't need it
if (feedback_write_2 || feedback_write_1 || sTex[0])
{
const MergeConstantBuffer cb = {GSVector4::unorm8(c), EXTBUF.EMODA, EXTBUF.EMODC};
UpdateSubresource(m_merge.cb.get(), &cb, &m_merge.cb_uniforms, sizeof(cb));
}
if (sTex[1] && (PMODE.SLBG == 0 || feedback_write_2_but_blend_bg))
{
// 2nd output is enabled and selected. Copy it to destination so we can blend it with 1st output
// Note: value outside of dRect must contains the background color (c)
StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY, filter);
}
// Save 2nd output
if (feedback_write_2)
{
DoStretchRect(dTex, full_r, sTex[2], dRect[2], GetConvertShader(ShaderConvert::YUV),
m_merge.cb.get(), nullptr, filter);
}
// Restore background color to process the normal merge
if (feedback_write_2_but_blend_bg)
ClearRenderTarget(dTex, c);
if (sTex[0])
{
// 1st output is enabled. It must be blended
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge.ps[PMODE.MMOD].get(), m_merge.cb.get(), m_merge.bs.get(), filter);
}
if (feedback_write_1)
{
DoStretchRect(dTex, full_r, sTex[2], dRect[2], GetConvertShader(ShaderConvert::YUV),
m_merge.cb.get(), nullptr, filter);
}
}
void GSDevice11::DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderInterlace shader, Filter filter, const InterlaceConstantBuffer& cb)
{
UpdateSubresource(m_interlace.cb.get(), &cb, &m_interlace.cb_uniforms, sizeof(cb));
DoStretchRect(sTex, sRect, dTex, dRect, m_interlace.ps[static_cast<int>(shader)].get(), m_interlace.cb.get(), filter);
}
void GSDevice11::DoFXAA(GSTexture* sTex, GSTexture* dTex)
{
const GSVector2i s = dTex->GetSize();
const GSVector4 sRect(0, 0, 1, 1);
const GSVector4 dRect(0, 0, s.x, s.y);
if (!m_fxaa_ps)
{
const std::optional<std::string> shader = ReadShaderSource("shaders/common/fxaa.fx");
if (!shader.has_value())
{
Console.Error("D3D11: FXAA shader is missing");
return;
}
ShaderMacro sm;
sm.AddMacro("FXAA_HLSL", "1");
m_fxaa_ps = m_shader_cache.GetPixelShader(m_dev.get(), *shader, sm.GetPtr(), "main");
if (!m_fxaa_ps)
return;
}
DoStretchRect(sTex, sRect, dTex, dRect, m_fxaa_ps.get(), nullptr, Biln);
}
void GSDevice11::DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4])
{
const GSVector2i s = dTex->GetSize();
const GSVector4 sRect(0, 0, 1, 1);
const GSVector4 dRect(0, 0, s.x, s.y);
UpdateSubresource(m_shadeboost.cb.get(), params, &m_shadeboost.cb_uniforms, sizeof(float) * 4);
DoStretchRect(sTex, sRect, dTex, dRect, m_shadeboost.ps.get(), m_shadeboost.cb.get(), Nearest);
}
void GSDevice11::SetupVS(VSSelector sel, const GSHWDrawConfig::VSConstantBuffer* cb)
{
auto i = std::as_const(m_vs).find(sel.key);
if (i == m_vs.end())
{
ShaderMacro sm;
sm.AddMacro("VERTEX_SHADER", 1);
sm.AddMacro("VS_TME", sel.tme);
sm.AddMacro("VS_FST", sel.fst);
sm.AddMacro("VS_IIP", sel.iip);
sm.AddMacro("VS_EXPAND", static_cast<int>(sel.expand));
static constexpr const D3D11_INPUT_ELEMENT_DESC layout[] =
{
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R8G8B8A8_UINT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 1, DXGI_FORMAT_R32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"POSITION", 0, DXGI_FORMAT_R16G16_UINT, 0, 16, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"POSITION", 1, DXGI_FORMAT_R32_UINT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 2, DXGI_FORMAT_R16G16_UINT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 1, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 28, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
GSVertexShader11 vs;
if (sel.expand == GSHWDrawConfig::VSExpand::None)
{
m_shader_cache.GetVertexShaderAndInputLayout(m_dev.get(), vs.vs.put(), vs.il.put(), layout,
std::size(layout), m_tfx_source, sm.GetPtr(), "vs_main");
}
else
{
vs.vs = m_shader_cache.GetVertexShader(m_dev.get(), m_tfx_source, sm.GetPtr(), "vs_main_expand");
}
i = m_vs.try_emplace(sel.key, std::move(vs)).first;
}
if (m_vs_cb_cache.Update(*cb))
{
m_ctx->UpdateSubresource(m_vs_cb.get(), 0, NULL, cb, 0, 0);
}
VSSetShader(i->second.vs.get(), m_vs_cb.get());
IASetInputLayout(i->second.il.get());
}
void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstantBuffer* cb, PSSamplerSelector ssel)
{
auto i = std::as_const(m_ps).find(sel);
if (i == m_ps.end())
{
ShaderMacro sm;
sm.AddMacro("PIXEL_SHADER", 1);
sm.AddMacro("PS_HAS_CONSERVATIVE_DEPTH", m_conservative_depth);
sm.AddMacro("PS_DEPTH_FEEDBACK_SUPPORT", m_features.depth_feedback ? 1 : 2);
sm.AddMacro("PS_FST", sel.fst);
sm.AddMacro("PS_WMS", sel.wms);
sm.AddMacro("PS_WMT", sel.wmt);
sm.AddMacro("PS_ADJS", sel.adjs);
sm.AddMacro("PS_ADJT", sel.adjt);
sm.AddMacro("PS_AEM_FMT", sel.aem_fmt);
sm.AddMacro("PS_AEM", sel.aem);
sm.AddMacro("PS_TFX", sel.tfx);
sm.AddMacro("PS_TCC", sel.tcc);
sm.AddMacro("PS_DATE", sel.date);
sm.AddMacro("PS_ATST", static_cast<u32>(sel.atst));
sm.AddMacro("PS_AFAIL", static_cast<u32>(sel.afail));
sm.AddMacro("PS_FOG", sel.fog);
sm.AddMacro("PS_IIP", sel.iip);
sm.AddMacro("PS_BLEND_HW", sel.blend_hw);
sm.AddMacro("PS_A_MASKED", sel.a_masked);
sm.AddMacro("PS_FBA", sel.fba);
sm.AddMacro("PS_FBMASK", sel.fbmask);
sm.AddMacro("PS_LTF", sel.ltf);
sm.AddMacro("PS_TCOFFSETHACK", sel.tcoffsethack);
sm.AddMacro("PS_POINT_SAMPLER", sel.point_sampler);
sm.AddMacro("PS_REGION_RECT", sel.region_rect);
sm.AddMacro("PS_SHUFFLE", sel.shuffle);
sm.AddMacro("PS_SHUFFLE_SAME", sel.shuffle_same);
sm.AddMacro("PS_PROCESS_BA", sel.process_ba);
sm.AddMacro("PS_PROCESS_RG", sel.process_rg);
sm.AddMacro("PS_SHUFFLE_ACROSS", sel.shuffle_across);
sm.AddMacro("PS_READ16_SRC", sel.real16src);
sm.AddMacro("PS_WRITE_RG", sel.write_rg);
sm.AddMacro("PS_CHANNEL_FETCH", sel.channel);
sm.AddMacro("PS_TALES_OF_ABYSS_HLE", sel.tales_of_abyss_hle);
sm.AddMacro("PS_URBAN_CHAOS_HLE", sel.urban_chaos_hle);
sm.AddMacro("PS_DST_FMT", sel.dst_fmt);
sm.AddMacro("PS_DEPTH_FMT", sel.depth_fmt);
sm.AddMacro("PS_PAL_FMT", sel.pal_fmt);
sm.AddMacro("PS_COLCLIP_HW", sel.colclip_hw);
sm.AddMacro("PS_RTA_CORRECTION", sel.rta_correction);
sm.AddMacro("PS_RTA_SRC_CORRECTION", sel.rta_source_correction);
sm.AddMacro("PS_COLCLIP", sel.colclip);
sm.AddMacro("PS_BLEND_A", sel.blend_a);
sm.AddMacro("PS_BLEND_B", sel.blend_b);
sm.AddMacro("PS_BLEND_C", sel.blend_c);
sm.AddMacro("PS_BLEND_D", sel.blend_d);
sm.AddMacro("PS_BLEND_MIX", sel.blend_mix);
sm.AddMacro("PS_ROUND_INV", sel.round_inv);
sm.AddMacro("PS_FIXED_ONE_A", sel.fixed_one_a);
sm.AddMacro("PS_PABE", sel.pabe);
sm.AddMacro("PS_DITHER", sel.dither);
sm.AddMacro("PS_DITHER_ADJUST", sel.dither_adjust);
sm.AddMacro("PS_ZCLAMP", sel.zclamp);
sm.AddMacro("PS_ZFLOOR", sel.zfloor);
sm.AddMacro("PS_SCANMSK", sel.scanmsk);
sm.AddMacro("PS_AUTOMATIC_LOD", sel.automatic_lod);
sm.AddMacro("PS_MANUAL_LOD", sel.manual_lod);
sm.AddMacro("PS_TEX_IS_FB", sel.tex_is_fb);
sm.AddMacro("PS_NO_COLOR", sel.no_color);
sm.AddMacro("PS_NO_COLOR1", sel.no_color1);
sm.AddMacro("PS_ZTST", sel.ztst);
sm.AddMacro("PS_AA1", static_cast<u32>(sel.aa1));
sm.AddMacro("PS_ABE", sel.abe);
sm.AddMacro("PS_ANISOTROPIC_FILTERING", sel.sw_aniso);
sm.AddMacro("PS_ROV_COLOR", sel.rov_color);
sm.AddMacro("PS_ROV_DEPTH", static_cast<u32>(sel.rov_depth));
wil::com_ptr_nothrow<ID3D11PixelShader> ps = m_shader_cache.GetPixelShader(m_dev.get(), m_tfx_source, sm.GetPtr(), "ps_main");
i = m_ps.try_emplace(sel, std::move(ps)).first;
}
if (cb && m_ps_cb_cache.Update(*cb))
{
m_ctx->UpdateSubresource(m_ps_cb.get(), 0, NULL, cb, 0, 0);
}
wil::com_ptr_nothrow<ID3D11SamplerState> ss0;
if (sel.tfx != 4)
{
if (sel.pal_fmt || sel.wms >= 3 || sel.wmt >= 3)
{
pxAssert(ssel.biln == 0);
}
auto i = std::as_const(m_ps_ss).find(ssel.key);
if (i != m_ps_ss.end())
{
ss0 = i->second;
}
else
{
static constexpr std::array<D3D11_FILTER, 8> filters = {{
D3D11_FILTER_MIN_MAG_MIP_POINT, // 000 / min=point,mag=point,mip=point
D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT, // 001 / min=linear,mag=point,mip=point
D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, // 010 / min=point,mag=linear,mip=point
D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT, // 011 / min=linear,mag=linear,mip=point
D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR, // 100 / min=point,mag=point,mip=linear
D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, // 101 / min=linear,mag=point,mip=linear
D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR, // 110 / min=point,mag=linear,mip=linear
D3D11_FILTER_MIN_MAG_MIP_LINEAR, // 111 / min=linear,mag=linear,mip=linear
}};
const u8 index = (static_cast<u8>(ssel.IsMipFilterLinear()) << 2) |
(static_cast<u8>(ssel.IsMagFilterLinear()) << 1) |
static_cast<u8>(ssel.IsMinFilterLinear());
const D3D11_SAMPLER_DESC sd = {
filters[index], // Filter
ssel.tau ? D3D11_TEXTURE_ADDRESS_WRAP : D3D11_TEXTURE_ADDRESS_CLAMP, // Address u
ssel.tav ? D3D11_TEXTURE_ADDRESS_WRAP : D3D11_TEXTURE_ADDRESS_CLAMP, // Address v
D3D11_TEXTURE_ADDRESS_CLAMP, // Address w
0.0f, // Lod bias
1, // Anisotropy
D3D11_COMPARISON_NEVER, // comparison function
{}, // Border colour
0.0f, // Min lod
(ssel.lodclamp || !ssel.UseMipmapFiltering()) ? 0.25f : FLT_MAX, // Max lod
};
m_dev->CreateSamplerState(&sd, &ss0);
m_ps_ss[ssel.key] = ss0;
}
}
PSSetSamplerState(ss0.get());
PSSetShader(i->second.get(), m_ps_cb.get());
}
void GSDevice11::SetupOM(OMDepthStencilSelector dssel, OMBlendSelector bsel, u8 afix)
{
auto i = std::as_const(m_om_dss).find(dssel.key);
if (i == m_om_dss.end())
{
D3D11_DEPTH_STENCIL_DESC dsd;
memset(&dsd, 0, sizeof(dsd));
if (dssel.date)
{
dsd.StencilEnable = true;
dsd.StencilReadMask = 1;
dsd.StencilWriteMask = 1;
dsd.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL;
dsd.FrontFace.StencilPassOp = dssel.date_one ? D3D11_STENCIL_OP_ZERO : D3D11_STENCIL_OP_KEEP;
dsd.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsd.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
dsd.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;
dsd.BackFace.StencilPassOp = dssel.date_one ? D3D11_STENCIL_OP_ZERO : D3D11_STENCIL_OP_KEEP;
dsd.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
dsd.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
}
if (dssel.ztst != ZTST_ALWAYS || dssel.zwe)
{
static const D3D11_COMPARISON_FUNC ztst[] =
{
D3D11_COMPARISON_NEVER,
D3D11_COMPARISON_ALWAYS,
D3D11_COMPARISON_GREATER_EQUAL,
D3D11_COMPARISON_GREATER
};
dsd.DepthEnable = true;
dsd.DepthWriteMask = dssel.zwe ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
dsd.DepthFunc = ztst[dssel.ztst];
}
wil::com_ptr_nothrow<ID3D11DepthStencilState> dss;
m_dev->CreateDepthStencilState(&dsd, dss.put());
i = m_om_dss.try_emplace(dssel.key, std::move(dss)).first;
}
OMSetDepthStencilState(i->second.get(), 1);
auto j = std::as_const(m_om_bs).find(bsel.key);
if (j == m_om_bs.end())
{
D3D11_BLEND_DESC bd;
memset(&bd, 0, sizeof(bd));
if (bsel.blend.IsEffective(bsel.colormask))
{
// clang-format off
static constexpr std::array<D3D11_BLEND, 16> s_d3d11_blend_factors = { {
D3D11_BLEND_SRC_COLOR, D3D11_BLEND_INV_SRC_COLOR, D3D11_BLEND_DEST_COLOR, D3D11_BLEND_INV_DEST_COLOR,
D3D11_BLEND_SRC1_COLOR, D3D11_BLEND_INV_SRC1_COLOR, D3D11_BLEND_SRC_ALPHA, D3D11_BLEND_INV_SRC_ALPHA,
D3D11_BLEND_DEST_ALPHA, D3D11_BLEND_INV_DEST_ALPHA, D3D11_BLEND_SRC1_ALPHA, D3D11_BLEND_INV_SRC1_ALPHA,
D3D11_BLEND_BLEND_FACTOR, D3D11_BLEND_INV_BLEND_FACTOR, D3D11_BLEND_ONE, D3D11_BLEND_ZERO
} };
static constexpr std::array<D3D11_BLEND_OP, 4> s_d3d11_blend_ops = { {
D3D11_BLEND_OP_ADD, D3D11_BLEND_OP_SUBTRACT, D3D11_BLEND_OP_REV_SUBTRACT, D3D11_BLEND_OP_MIN
} };
// clang-format on
bd.RenderTarget[0].BlendEnable = bsel.blend.enable;
bd.RenderTarget[0].BlendOp = s_d3d11_blend_ops[bsel.blend.op];
bd.RenderTarget[0].SrcBlend = s_d3d11_blend_factors[bsel.blend.src_factor];
bd.RenderTarget[0].DestBlend = s_d3d11_blend_factors[bsel.blend.dst_factor];
bd.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
bd.RenderTarget[0].SrcBlendAlpha = s_d3d11_blend_factors[bsel.blend.src_factor_alpha];
bd.RenderTarget[0].DestBlendAlpha = s_d3d11_blend_factors[bsel.blend.dst_factor_alpha];
}
if (bsel.colormask.wr)
bd.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_RED;
if (bsel.colormask.wg)
bd.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_GREEN;
if (bsel.colormask.wb)
bd.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_BLUE;
if (bsel.colormask.wa)
bd.RenderTarget[0].RenderTargetWriteMask |= D3D11_COLOR_WRITE_ENABLE_ALPHA;
wil::com_ptr_nothrow<ID3D11BlendState> bs;
m_dev->CreateBlendState(&bd, bs.put());
j = m_om_bs.try_emplace(bsel.key, std::move(bs)).first;
}
OMSetBlendState(j->second.get(), afix);
}
bool GSDevice11::CreateCASShaders()
{
CD3D11_BUFFER_DESC desc(NUM_CAS_CONSTANTS * sizeof(u32), D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DEFAULT);
HRESULT hr = m_dev->CreateBuffer(&desc, nullptr, m_cas.cb.put());
if (FAILED(hr))
return false;
std::optional<std::string> cas_source = ReadShaderSource("shaders/dx11/cas.hlsl");
if (!cas_source.has_value() || !GetCASShaderSource(&cas_source.value()))
return false;
static constexpr D3D_SHADER_MACRO sharpen_only_macros[] = {
{"CAS_SHARPEN_ONLY", "1"},
{nullptr, nullptr}};
m_cas.cs_sharpen = m_shader_cache.GetComputeShader(m_dev.get(), cas_source.value(), sharpen_only_macros, "main");
m_cas.cs_upscale = m_shader_cache.GetComputeShader(m_dev.get(), cas_source.value(), nullptr, "main");
if (!m_cas.cs_sharpen || !m_cas.cs_upscale)
{
Console.Error("D3D11: Failed to create CAS compute shaders.");
return false;
}
return true;
}
bool GSDevice11::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants)
{
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
static constexpr int threadGroupWorkRegionDim = 16;
const int dispatchX = (dTex->GetWidth() + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
const int dispatchY = (dTex->GetHeight() + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
ID3D11ShaderResourceView* srvs[1] = {*static_cast<GSTexture11*>(sTex)};
ID3D11UnorderedAccessView* uavs[1] = {*static_cast<GSTexture11*>(dTex)};
OMSetRenderTargets(nullptr, nullptr, nullptr);
m_ctx->UpdateSubresource(m_cas.cb.get(), 0, nullptr, constants.data(), 0, 0);
m_ctx->CSSetConstantBuffers(0, 1, m_cas.cb.addressof());
m_ctx->CSSetShader(sharpen_only ? m_cas.cs_sharpen.get() : m_cas.cs_upscale.get(), nullptr, 0);
m_ctx->CSSetShaderResources(0, std::size(srvs), srvs);
m_ctx->CSSetUnorderedAccessViews(0, std::size(uavs), uavs, nullptr);
m_ctx->Dispatch(dispatchX, dispatchY, 1);
// clear bindings out to prevent hazards
uavs[0] = nullptr;
srvs[0] = nullptr;
m_ctx->CSSetShaderResources(0, std::size(srvs), srvs);
m_ctx->CSSetUnorderedAccessViews(0, std::size(uavs), uavs, nullptr);
return true;
}
bool GSDevice11::CreateImGuiResources()
{
HRESULT hr;
const std::optional<std::string> hlsl = ReadShaderSource("shaders/dx11/imgui.fx");
if (!hlsl.has_value())
{
Console.Error("D3D11: Failed to read imgui.fx");
return false;
}
// clang-format off
static constexpr D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)offsetof(ImDrawVert, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
// clang-format on
if (!m_shader_cache.GetVertexShaderAndInputLayout(m_dev.get(), m_imgui.vs.put(), m_imgui.il.put(), layout,
std::size(layout), hlsl.value(), nullptr, "vs_main") ||
!(m_imgui.ps = m_shader_cache.GetPixelShader(m_dev.get(), hlsl.value(), nullptr, "ps_main")))
{
Console.Error("D3D11: Failed to compile ImGui shaders");
return false;
}
D3D11_BLEND_DESC blend_desc = {};
blend_desc.RenderTarget[0].BlendEnable = true;
blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
hr = m_dev->CreateBlendState(&blend_desc, m_imgui.bs.put());
if (FAILED(hr))
{
Console.Error("D3D11: CreateImGuiResources(): CreateBlendState() failed: %08X", hr);
return false;
}
D3D11_BUFFER_DESC buffer_desc = {};
buffer_desc.Usage = D3D11_USAGE_DEFAULT;
buffer_desc.ByteWidth = sizeof(float) * 4 * 4;
buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
hr = m_dev->CreateBuffer(&buffer_desc, nullptr, m_imgui.vs_cb.put());
if (FAILED(hr))
{
Console.Error("D3D11: CreateImGuiResources(): CreateBlendState() failed: %08X", hr);
return false;
}
return true;
}
void GSDevice11::RenderImGui()
{
ImGui::Render();
const ImDrawData* draw_data = ImGui::GetDrawData();
if (draw_data->CmdListsCount == 0)
return;
UpdateImGuiTextures();
constexpr float L = 0.0f;
const float R = static_cast<float>(m_window_info.surface_width);
constexpr float T = 0.0f;
const float B = static_cast<float>(m_window_info.surface_height);
// clang-format off
const GSVector4 ortho_projection[4] =
{
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
{ 0.0f, 0.0f, 0.5f, 0.0f },
{ (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f },
};
// clang-format on
UpdateSubresource(m_imgui.vs_cb.get(), ortho_projection, &m_imgui.vs_cb_uniforms, sizeof(ortho_projection));
const UINT vb_stride = sizeof(ImDrawVert);
const UINT vb_offset = 0;
m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &vb_stride, &vb_offset);
IASetInputLayout(m_imgui.il.get());
IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
VSSetShader(m_imgui.vs.get(), m_imgui.vs_cb.get());
PSSetShader(m_imgui.ps.get(), nullptr);
OMSetBlendState(m_imgui.bs.get(), 0.0f);
OMSetDepthStencilState(m_convert.dss.get(), 0);
PSSetSamplerState(m_convert.ln.get());
// Render command lists
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
// This mess is because the vertex size isn't the same...
u32 vertex_offset;
{
static_assert(std::has_single_bit(sizeof(GSVertexPT1)));
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
const u32 unaligned_size = cmd_list->VtxBuffer.Size * sizeof(ImDrawVert);
u32 start_pos = Common::AlignUp(m_vb_pos, sizeof(ImDrawVert));
u32 end_pos = Common::AlignUpPow2(start_pos + unaligned_size, sizeof(GSVertexPT1));
if (end_pos > VERTEX_BUFFER_SIZE)
{
type = D3D11_MAP_WRITE_DISCARD;
m_vb_pos = 0;
start_pos = 0;
end_pos = Common::AlignUpPow2(unaligned_size, sizeof(GSVertexPT1));
}
m_vb_pos = end_pos;
vertex_offset = start_pos / sizeof(ImDrawVert);
D3D11_MAPPED_SUBRESOURCE sr;
const HRESULT hr = m_ctx->Map(m_vb.get(), 0, type, 0, &sr);
if (FAILED(hr))
continue;
std::memcpy(static_cast<u8*>(sr.pData) + start_pos, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
m_ctx->Unmap(m_vb.get(), 0);
}
static_assert(sizeof(ImDrawIdx) == sizeof(u16));
IASetIndexBuffer(cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size);
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
pxAssert(!pcmd->UserCallback);
const GSVector4 clip = GSVector4::load<false>(&pcmd->ClipRect);
if ((clip.zwzw() <= clip.xyxy()).mask() != 0)
continue;
const GSVector4i iclip = GSVector4i(clip);
if (!m_state.scissor.eq(iclip))
{
m_state.scissor = iclip;
m_ctx->RSSetScissorRects(1, reinterpret_cast<const D3D11_RECT*>(&iclip));
}
// Since we don't have the GSTexture...
m_state.ps_pending_srv[0] = reinterpret_cast<ID3D11ShaderResourceView*>(pcmd->GetTexID());
PSUpdateShaderState(true, true);
m_ctx->DrawIndexed(pcmd->ElemCount, m_index.start + pcmd->IdxOffset, vertex_offset + pcmd->VtxOffset);
}
g_perfmon.Put(GSPerfMon::DrawCalls, cmd_list->CmdBuffer.Size);
}
m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &m_state.vb_stride, &vb_offset);
}
void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSVector4i& bbox)
{
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
// sfex3 (after the capcom logo), vf4 (first menu fading in), ffxii shadows, rumble roses shadows, persona4 shadows
CommitClear(rt);
CommitClear(ds);
m_ctx->ClearDepthStencilView(*static_cast<GSTexture11*>(ds), D3D11_CLEAR_STENCIL, 0.0f, 0);
// om
OMSetDepthStencilState(m_date.dss.get(), 1);
OMSetBlendState(m_date.bs.get(), 0);
OMSetRenderTargets(nullptr, nullptr, ds);
// ia
const GSVector4 src = GSVector4(bbox) / GSVector4(ds->GetSize()).xyxy();
const GSVector4 dst = src * 2.0f - 1.0f;
const GSVertexPT1 vertices[] =
{
{GSVector4(dst.x, -dst.y, 0.5f, 1.0f), GSVector2(src.x, src.y)},
{GSVector4(dst.z, -dst.y, 0.5f, 1.0f), GSVector2(src.z, src.y)},
{GSVector4(dst.x, -dst.w, 0.5f, 1.0f), GSVector2(src.x, src.w)},
{GSVector4(dst.z, -dst.w, 0.5f, 1.0f), GSVector2(src.z, src.w)},
};
IASetVertexBuffer(vertices, sizeof(vertices[0]), 4);
IASetInputLayout(m_convert.il.get());
IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
// vs
VSSetShader(m_convert.vs.get(), nullptr);
// ps
PSSetShaderResource(TEXTURE_TEXTURE, rt);
PSSetSamplerState(m_convert.pt.get());
PSSetShader(GetConvertShader(SetDATMShader(datm)), nullptr);
// draw
DrawPrimitive();
}
void* GSDevice11::IAMapVertexBuffer(u32 stride, u32 count)
{
const u32 size = stride * count;
if (size > VERTEX_BUFFER_SIZE)
return nullptr;
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
m_vertex.start = (m_vb_pos + (stride - 1)) / stride;
m_vb_pos = (m_vertex.start * stride) + size;
if (m_vb_pos > VERTEX_BUFFER_SIZE)
{
m_vertex.start = 0;
m_vb_pos = size;
type = D3D11_MAP_WRITE_DISCARD;
}
D3D11_MAPPED_SUBRESOURCE m;
if (FAILED(m_ctx->Map(m_vb.get(), 0, type, 0, &m)))
return nullptr;
return static_cast<u8*>(m.pData) + (m_vertex.start * stride);
}
void GSDevice11::IAUnmapVertexBuffer(u32 stride, u32 count)
{
m_ctx->Unmap(m_vb.get(), 0);
IASetVertexBuffer(m_vb.get(), stride);
m_vertex.count = count;
}
bool GSDevice11::IASetVertexBuffer(const void* vertex, u32 stride, u32 count)
{
void* map = IAMapVertexBuffer(stride, count);
if (!map)
return false;
GSVector4i::storent(map, vertex, count * stride);
IAUnmapVertexBuffer(stride, count);
return true;
}
void GSDevice11::IASetVertexBuffer(ID3D11Buffer* buffer, u32 stride)
{
if (m_state.vb != buffer || stride != m_state.vb_stride)
{
const UINT zero = 0;
m_ctx->IASetVertexBuffers(0, 1, &buffer, buffer ? &stride : &zero, &zero);
m_state.vb = buffer;
}
}
bool GSDevice11::IASetExpandVertexBuffer(const void* vertex, u32 stride, u32 count)
{
const u32 size = stride * count;
if (size > VERTEX_BUFFER_SIZE)
return false;
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
m_vertex.start = (m_structured_vb_pos + (stride - 1)) / stride;
m_structured_vb_pos = (m_vertex.start * stride) + size;
if (m_structured_vb_pos > VERTEX_BUFFER_SIZE)
{
m_vertex.start = 0;
m_structured_vb_pos = size;
type = D3D11_MAP_WRITE_DISCARD;
}
D3D11_MAPPED_SUBRESOURCE m;
if (FAILED(m_ctx->Map(m_expand_vb.get(), 0, type, 0, &m)))
return false;
void* map = static_cast<u8*>(m.pData) + (m_vertex.start * stride);
GSVector4i::storent(map, vertex, count * stride);
m_ctx->Unmap(m_expand_vb.get(), 0);
m_vertex.count = count;
return true;
}
u16* GSDevice11::IAMapIndexBuffer(u32 count)
{
if (count > (INDEX_BUFFER_SIZE / sizeof(u16)))
return nullptr;
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
m_index.start = m_ib_pos;
m_ib_pos += count;
if (m_ib_pos > (INDEX_BUFFER_SIZE / sizeof(u16)))
{
m_index.start = 0;
m_ib_pos = count;
type = D3D11_MAP_WRITE_DISCARD;
}
D3D11_MAPPED_SUBRESOURCE m;
if (FAILED(m_ctx->Map(m_ib.get(), 0, type, 0, &m)))
return nullptr;
return static_cast<u16*>(m.pData) + m_index.start;
}
void GSDevice11::IAUnmapIndexBuffer(u32 count)
{
m_ctx->Unmap(m_ib.get(), 0);
m_index.count = count;
}
bool GSDevice11::IASetIndexBuffer(const void* index, u32 count)
{
u16* map = IAMapIndexBuffer(count);
if (!map)
return false;
std::memcpy(map, index, count * sizeof(u16));
IAUnmapIndexBuffer(count);
IASetIndexBuffer(m_ib.get());
return true;
}
void GSDevice11::IASetIndexBuffer(ID3D11Buffer* buffer)
{
if (m_state.index_buffer != buffer)
{
m_ctx->IASetIndexBuffer(buffer, DXGI_FORMAT_R16_UINT, 0);
m_state.index_buffer = buffer;
}
}
void GSDevice11::IASetInputLayout(ID3D11InputLayout* layout)
{
if (m_state.layout != layout)
{
m_state.layout = layout;
m_ctx->IASetInputLayout(layout);
}
}
void GSDevice11::IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY topology)
{
if (m_state.topology != topology)
{
m_state.topology = topology;
m_ctx->IASetPrimitiveTopology(topology);
}
}
void GSDevice11::VSSetShader(ID3D11VertexShader* vs, ID3D11Buffer* vs_cb)
{
if (m_state.vs != vs)
{
m_state.vs = vs;
m_ctx->VSSetShader(vs, nullptr, 0);
}
if (m_state.vs_cb != vs_cb)
{
m_state.vs_cb = vs_cb;
m_ctx->VSSetConstantBuffers(0, 1, &vs_cb);
}
}
void GSDevice11::VSSetPushConstants(u32 base_vertex, u32 base_index, bool force_update)
{
GSHWDrawConfig::VSPushConstants vs_pc;
vs_pc.base_vertex = base_vertex;
vs_pc.base_index = base_index;
if (m_vs_pc_cache.Update(vs_pc) || force_update)
{
m_ctx->UpdateSubresource(m_vs_pc.get(), 0, NULL, &vs_pc, 0, 0);
}
if (m_state.vs_pc != m_vs_pc.get())
{
m_state.vs_pc = m_vs_pc.get();
m_ctx->VSSetConstantBuffers(1, 1, &m_state.vs_pc);
}
}
u16* GSDevice11::VSMapIndexBuffer(u32 count)
{
if (count > (INDEX_BUFFER_SIZE / sizeof(u16)))
return nullptr;
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
m_index.start = m_expand_ib_vs_pos;
m_expand_ib_vs_pos += count;
if (m_expand_ib_vs_pos > (INDEX_BUFFER_SIZE / sizeof(u16)))
{
m_index.start = 0;
m_expand_ib_vs_pos = count;
type = D3D11_MAP_WRITE_DISCARD;
}
D3D11_MAPPED_SUBRESOURCE m;
if (FAILED(m_ctx->Map(m_expand_ib_vs.get(), 0, type, 0, &m)))
return nullptr;
return static_cast<u16*>(m.pData) + m_index.start;
}
void GSDevice11::VSUnmapIndexBuffer(u32 count)
{
m_ctx->Unmap(m_expand_ib_vs.get(), 0);
m_index.count = count;
}
bool GSDevice11::VSSetIndexBuffer(const void* index, u32 count)
{
u16* map = VSMapIndexBuffer(count);
if (!map)
return false;
std::memcpy(map, index, count * sizeof(u16));
VSUnmapIndexBuffer(count);
return true;
}
void GSDevice11::PSSetShaderResource(int i, GSTexture* sr)
{
// Update local state only, PSUpdateShaderState updates gpu state.
if (sr)
m_state.ps_pending_srv[i] = *static_cast<GSTexture11*>(sr);
else
m_state.ps_pending_srv[i] = nullptr;
}
void GSDevice11::PSSetSamplerState(ID3D11SamplerState* ss0)
{
m_state.ps_pending_ss[0] = ss0;
}
void GSDevice11::ClearSamplerCache()
{
m_ps_ss.clear();
}
void GSDevice11::PSSetShader(ID3D11PixelShader* ps, ID3D11Buffer* ps_cb)
{
if (m_state.ps != ps)
{
m_state.ps = ps;
m_ctx->PSSetShader(ps, nullptr, 0);
}
if (m_state.ps_cb != ps_cb)
{
m_state.ps_cb = ps_cb;
m_ctx->PSSetConstantBuffers(0, 1, &ps_cb);
}
}
void GSDevice11::PSUpdateShaderState(const bool sr_update, const bool ss_update)
{
// Shader resource caching requires srv/rtv hazards to be resolved, ensure PSUnbindConflictingSRVs handle.
if (sr_update)
{
bool sr_changed = false;
for (size_t i = 0; i < MAX_TEXTURES; ++i)
{
if (m_state.ps_current_srv[i] != m_state.ps_pending_srv[i])
{
sr_changed = true;
break;
}
}
if (sr_changed)
{
m_ctx->PSSetShaderResources(0, MAX_TEXTURES, m_state.ps_pending_srv.data());
m_state.ps_current_srv = m_state.ps_pending_srv;
}
}
if (ss_update)
{
bool ss_changed = false;
for (size_t i = 0; i < MAX_SAMPLERS; ++i)
{
if (m_state.ps_current_ss[i] != m_state.ps_pending_ss[i])
{
ss_changed = true;
break;
}
}
if (ss_changed)
{
m_ctx->PSSetSamplers(0, MAX_SAMPLERS, m_state.ps_pending_ss.data());
m_state.ps_current_ss = m_state.ps_pending_ss;
}
}
}
void GSDevice11::PSUnbindConflictingSRVs(GSTexture* tex1, GSTexture* tex2)
{
// Make sure no SRVs are bound using the same texture before binding it to a RTV.
bool changed = false;
for (size_t i = 0; i < MAX_TEXTURES; i++)
{
// We chech against what's currently bound, then update pending state which calls PSUpdateShaderState to update gpu state.
if ((tex1 && m_state.ps_current_srv[i] == *static_cast<GSTexture11*>(tex1)) || (tex2 && m_state.ps_current_srv[i] == *static_cast<GSTexture11*>(tex2)))
{
// Local and gpu cached state can differ, if it does check if it conflicts and if it doesn't then we can bind that instead of unbinding.
const bool unbind_needed = (tex1 && m_state.ps_pending_srv[i] == *static_cast<GSTexture11*>(tex1)) || (tex2 && m_state.ps_pending_srv[i] == *static_cast<GSTexture11*>(tex2));
if (unbind_needed)
m_state.ps_pending_srv[i] = nullptr;
changed = true;
}
}
if (changed)
PSUpdateShaderState(true, false);
}
void GSDevice11::OMSetDepthStencilState(ID3D11DepthStencilState* dss, u8 sref)
{
if (m_state.dss != dss || (dss && m_state.sref != sref))
{
m_state.dss = dss;
m_state.sref = sref;
m_ctx->OMSetDepthStencilState(dss, sref);
}
}
void GSDevice11::OMSetBlendState(ID3D11BlendState* bs, u8 bf)
{
if (m_state.bs != bs || (bs && m_state.bf != bf))
{
m_state.bs = bs;
m_state.bf = bf;
const GSVector4 col(static_cast<float>(bf) / 128.0f);
m_ctx->OMSetBlendState(bs, col.v, 0xffffffff);
}
}
void GSDevice11::OMSetRenderTargets(GSTexture* rt, GSTexture* ds_as_rt, GSTexture* ds, GSTexture* rt_uav_tex, GSTexture* ds_uav_tex,
const GSVector4i* scissor, ID3D11DepthStencilView* read_only_dsv)
{
if (!(rt || rt_uav_tex) && ds_uav_tex)
rt_uav_tex = m_null_texture; // Fill in the first UAV slot with the null texture.
ID3D11RenderTargetView* rtv = nullptr;
ID3D11RenderTargetView* dsv_as_rtv = nullptr;
ID3D11DepthStencilView* dsv = nullptr;
ID3D11UnorderedAccessView* rt_uav = nullptr;
ID3D11UnorderedAccessView* ds_uav = nullptr;
if (rt)
{
CommitClear(rt);
rtv = *static_cast<GSTexture11*>(rt);
}
if (ds_as_rt)
{
CommitClear(ds_as_rt);
dsv_as_rtv = *static_cast<GSTexture11*>(ds_as_rt);
}
if (ds)
{
CommitClear(ds);
dsv = read_only_dsv ? read_only_dsv : *static_cast<GSTexture11*>(ds);
}
if (rt_uav_tex)
{
CommitClear(rt_uav_tex);
rt_uav = *static_cast<GSTexture11*>(rt_uav_tex);
}
if (ds_uav_tex)
{
CommitClear(ds_uav_tex);
ds_uav = *static_cast<GSTexture11*>(ds_uav_tex);
}
const bool changed = (m_state.rtv != rtv || m_state.dsv_as_rtv != dsv_as_rtv || m_state.dsv != dsv || m_state.rt_uav != rt_uav || m_state.ds_uav != ds_uav);
const bool no_uavs = m_state.rt_uav == nullptr && rt_uav == nullptr && m_state.ds_uav == nullptr && ds_uav == nullptr;
if (changed)
g_perfmon.Put(GSPerfMon::RenderPasses, 1.0);
if (m_state.rtv != rtv)
{
if (m_state.rtv)
m_state.rtv->Release();
if (rtv)
rtv->AddRef();
m_state.rtv = rtv;
m_state.current_rt = rt;
}
if (m_state.dsv_as_rtv != dsv_as_rtv)
{
if (m_state.dsv_as_rtv)
m_state.dsv_as_rtv->Release();
if (dsv_as_rtv)
dsv_as_rtv->AddRef();
m_state.rtv = dsv_as_rtv;
m_state.current_ds_as_rt = ds_as_rt;
}
if (m_state.dsv != dsv)
{
if (m_state.dsv)
m_state.dsv->Release();
if (dsv)
dsv->AddRef();
m_state.dsv = dsv;
m_state.current_ds = ds;
}
if (m_state.rt_uav != rt_uav)
{
if (m_state.rt_uav)
m_state.rt_uav->Release();
if (rt_uav)
rt_uav->AddRef();
m_state.rt_uav = rt_uav;
m_state.current_rt_uav = rt_uav_tex;
}
if (m_state.ds_uav != ds_uav)
{
if (m_state.ds_uav)
m_state.ds_uav->Release();
if (ds_uav)
ds_uav->AddRef();
m_state.ds_uav = ds_uav;
m_state.current_ds_uav = ds_uav_tex;
}
PSUnbindConflictingSRVs(m_state.current_rt, read_only_dsv ? nullptr : m_state.current_ds);
PSUnbindConflictingSRVs(m_state.current_ds_as_rt);
PSUnbindConflictingSRVs(m_state.current_rt_uav, m_state.current_ds_uav);
if (changed)
{
// OM targets and UAVs share the same namespace in DX11, so we need to pack them into contiguous slots.
const u32 num_rtvs = ds_as_rt ? 2 : rt ? 1 : 0;
ID3D11RenderTargetView* rtvs[] = {rtv, dsv_as_rtv};
if (no_uavs)
m_ctx->OMSetRenderTargets(num_rtvs, rtvs, dsv);
else
{
u32 num_uavs = 0;
ID3D11UnorderedAccessView* uavs[2];
if (rt_uav)
uavs[num_uavs++] = rt_uav;
if (ds_uav)
uavs[num_uavs++] = ds_uav;
m_ctx->OMSetRenderTargetsAndUnorderedAccessViews(num_rtvs, rtvs, dsv, num_rtvs, num_uavs, uavs, nullptr);
}
}
if (rt || ds_as_rt || ds || rt_uav_tex || ds_uav_tex)
{
const GSVector2i size =
rt ? rt->GetSize() :
ds ? ds->GetSize() :
(rt_uav_tex && rt_uav_tex != m_null_texture) ? rt_uav_tex->GetSize() :
ds_uav_tex->GetSize();
SetViewport(size);
SetScissor(scissor ? *scissor : GSVector4i::loadh(size));
}
}
void GSDevice11::SetViewport(const GSVector2i& viewport)
{
if (m_state.viewport != viewport)
{
m_state.viewport = viewport;
const D3D11_VIEWPORT vp = {
0.0f, 0.0f, static_cast<float>(viewport.x), static_cast<float>(viewport.y), 0.0f, 1.0f};
m_ctx->RSSetViewports(1, &vp);
}
}
void GSDevice11::SetScissor(const GSVector4i& scissor)
{
static_assert(sizeof(D3D11_RECT) == sizeof(GSVector4i));
if (!m_state.scissor.eq(scissor))
{
m_state.scissor = scissor;
m_ctx->RSSetScissorRects(1, reinterpret_cast<const D3D11_RECT*>(&scissor));
}
}
void GSDevice11::UpdateSubresource(ID3D11Buffer* buffer, const void* cb_uniforms, void* cached_cb_uniforms, size_t cb_uniforms_size)
{
if (memcmp(cb_uniforms, cached_cb_uniforms, cb_uniforms_size) != 0)
{
memcpy(cached_cb_uniforms, cb_uniforms, cb_uniforms_size);
m_ctx->UpdateSubresource(buffer, 0, nullptr, cached_cb_uniforms, 0, 0);
}
}
void GSDevice11::ShaderMacro::AddMacro(const char* n, int d)
{
AddMacro(n, std::to_string(d));
}
void GSDevice11::ShaderMacro::AddMacro(const char* n, std::string d)
{
mlist.emplace_back(n, std::move(d));
}
D3D_SHADER_MACRO* GSDevice11::ShaderMacro::GetPtr()
{
mout.clear();
for (auto& i : mlist)
mout.emplace_back(i.name.c_str(), i.def.c_str());
mout.emplace_back(nullptr, nullptr);
return (D3D_SHADER_MACRO*)mout.data();
}
void GSDevice11::RenderHW(GSHWDrawConfig& config)
{
const GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize();
GSTexture* colclip_rt = g_gs_device->GetColorClipTexture();
GSTexture* draw_rt = config.ps.HasColorROV() ? nullptr : config.rt;
GSTexture* draw_ds_as_rt = m_ds_as_rt;
GSTexture* draw_ds = config.ps.HasDepthROV() ? nullptr : config.ds;
GSTexture* draw_rt_rov = config.ps.HasColorROV() ? config.rt : nullptr;
GSTexture* draw_ds_rov = config.ps.HasDepthROV() ? config.ds : nullptr;
GSTexture* draw_rt_clone = nullptr;
GSTexture* draw_ds_clone = nullptr;
GSTexture* primid_texture = nullptr;
ScopedGuard recycle_temp_textures([&]() {
if (draw_rt_clone)
Recycle(draw_rt_clone);
if (draw_ds_clone)
Recycle(draw_ds_clone);
if (primid_texture)
Recycle(primid_texture);
});
if (colclip_rt)
{
if (config.colclip_mode == GSHWDrawConfig::ColClipMode::EarlyResolve)
{
const GSVector2i size = config.rt->GetSize();
const GSVector4 dRect(config.colclip_update_area);
const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy();
StretchRect(colclip_rt, sRect, config.rt, dRect, ShaderConvert::COLCLIP_RESOLVE, Nearest);
Recycle(colclip_rt);
g_gs_device->SetColorClipTexture(nullptr);
colclip_rt = nullptr;
}
else
config.ps.colclip_hw = 1;
}
if (config.ps.colclip_hw)
{
if (!colclip_rt)
{
config.colclip_update_area = config.drawarea;
colclip_rt = CreateFeedbackTarget(rtsize.x, rtsize.y, m_rgba16_unorm_hw_blend ? GSTexture::Format::ColorClip : GSTexture::Format::ColorHDR, false);
if (!colclip_rt)
{
Console.Warning("D3D11: Failed to allocate ColorClip render target, aborting draw.");
return;
}
g_gs_device->SetColorClipTexture(colclip_rt);
const GSVector4 dRect = GSVector4((config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea);
const GSVector4 sRect = dRect / GSVector4(rtsize.x, rtsize.y).xyxy();
StretchRect(config.rt, sRect, colclip_rt, dRect, ShaderConvert::COLCLIP_INIT, Nearest);
}
draw_rt = colclip_rt ? colclip_rt : draw_rt;
}
// Destination Alpha Setup
const bool need_barrier = config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy);
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking)
{
primid_texture = CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::PrimID, false);
if (!primid_texture)
{
Console.Warning("D3D11: Failed to allocate DATE image, aborting draw.");
return;
}
DoStretchRect(colclip_rt ? colclip_rt : config.rt, GSVector4(config.drawarea) / GSVector4(rtsize).xyxy(),
primid_texture, GSVector4(config.drawarea), m_date.primid_init_ps[static_cast<u8>(config.datm)].get(), nullptr, Nearest);
}
else if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::Stencil ||
(config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && !need_barrier))
SetupDATE(colclip_rt ? colclip_rt : config.rt, config.ds, config.datm, config.drawarea);
if (config.vs.expand != GSHWDrawConfig::VSExpand::None)
{
if (!IASetExpandVertexBuffer(config.verts, sizeof(*config.verts), config.nverts))
{
Console.Error("D3D11: Failed to upload structured vertices (%u)", config.nverts);
return;
}
}
else
{
if (!IASetVertexBuffer(config.verts, sizeof(*config.verts), config.nverts))
{
Console.Error("D3D11: Failed to upload vertices (%u)", config.nverts);
return;
}
}
if (config.vs.UseFixedExpandIndexBuffer())
{
IASetIndexBuffer(m_expand_ib.get());
m_index.start = 0;
m_index.count = config.nindices;
}
else if (config.vs.UseVSExpandIndexBuffer())
{
VSSetIndexBuffer(config.indices, config.nindices);
IASetVertexBuffer(nullptr, 0); // Unbind the vertex buffer to prevent unwanted fetches.
}
else
{
if (!IASetIndexBuffer(config.indices, config.nindices))
{
Console.Error("D3D11: Failed to upload indices (%u)", config.nindices);
return;
}
}
D3D11_PRIMITIVE_TOPOLOGY topology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
switch (config.topology)
{
case GSHWDrawConfig::Topology::Point: topology = D3D11_PRIMITIVE_TOPOLOGY_POINTLIST; break;
case GSHWDrawConfig::Topology::Line: topology = D3D11_PRIMITIVE_TOPOLOGY_LINELIST; break;
case GSHWDrawConfig::Topology::Triangle: topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; break;
}
IASetPrimitiveTopology(topology);
// Depth testing and sampling, bind resource as dsv read only and srv at the same time without the need of a copy.
// Disable this for depth feedback loops, since in those cases we simultaneously read/write depth.
ID3D11DepthStencilView* read_only_dsv = nullptr;
if (config.tex && config.tex == config.ds)
{
if (m_features.test_and_sample_depth && !config.depth.zwe && !config.IsFeedbackLoopDepth(config.ps) && !config.IsFeedbackLoopDepth(config.alpha_second_pass.ps))
read_only_dsv = static_cast<GSTexture11*>(config.ds)->ReadOnlyDepthStencilView();
}
SetupVS(config.vs, &config.cb_vs);
SetupPS(config.ps, &config.cb_ps, config.sampler);
if (primid_texture)
{
OMDepthStencilSelector dss = config.depth;
dss.zwe = 0;
const OMBlendSelector blend(GSHWDrawConfig::ColorMaskSelector(1),
GSHWDrawConfig::BlendState(true, CONST_ONE, CONST_ONE, 3 /* MIN */, CONST_ONE, CONST_ZERO, false, 0));
SetupOM(dss, blend, 0);
OMSetRenderTargets(primid_texture, nullptr, config.ds, nullptr, nullptr, &config.scissor, read_only_dsv);
SetRenderHWShaderResources(config, nullptr);
Draw(config);
config.ps.date = 3;
config.alpha_second_pass.ps.date = 3;
SetupPS(config.ps, nullptr, config.sampler);
}
// Avoid changing framebuffer just to switch from rt+depth to rt and vice versa.
// Make sure no tex is bound as both rtv and srv at the same time.
// All conflicts should've been taken care of by PSUnbindConflictingSRVs.
if (!(draw_rt || draw_rt_rov || draw_ds_as_rt) && draw_ds && m_state.current_rt && config.tex != m_state.current_rt && m_state.current_rt->GetSize() == draw_ds->GetSize())
{
draw_rt = m_state.current_rt;
}
else if (!(draw_ds || draw_ds_rov || draw_ds_as_rt) && draw_rt && m_state.current_ds && config.tex != m_state.current_ds && m_state.current_ds->GetSize() == draw_rt->GetSize())
{
draw_ds = m_state.current_ds;
}
else if (!(draw_rt || draw_rt_rov || draw_ds_as_rt) && draw_ds_rov && m_state.current_rt_uav && config.tex != m_state.current_rt_uav)
{
draw_rt_rov = m_state.current_rt_uav;
}
else if (!(draw_ds || draw_ds_rov || draw_ds_as_rt) && draw_rt_rov && m_state.current_ds_uav && config.tex != m_state.current_ds_uav)
{
draw_ds_rov = m_state.current_ds_uav;
}
const bool rt_feedbackloop_pass1 = config.IsFeedbackLoopRT(config.ps);
const bool rt_feedbackloop_pass2 = config.IsFeedbackLoopRT(config.alpha_second_pass.ps);
if (draw_rt && !draw_rt_rov && (((config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) &&
(rt_feedbackloop_pass1 || rt_feedbackloop_pass2))))
{
// Requires a copy of the RT.
draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true);
if (!draw_rt_clone)
Console.Warning("D3D11: Failed to allocate temp texture for RT copy.");
}
const bool ds_feedbackloop_pass1 = config.IsFeedbackLoopDepth(config.ps);
const bool ds_feedbackloop_pass2 = config.IsFeedbackLoopDepth(config.alpha_second_pass.ps);
if (draw_ds && !draw_ds_rov && (config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) &&
(ds_feedbackloop_pass1 || ds_feedbackloop_pass2))
{
// Requires a copy of the DS.
draw_ds_clone = CreateTexture(rtsize.x, rtsize.y, 1, (draw_ds_as_rt ? draw_ds_as_rt : draw_ds)->GetFormat(), true);
if (!draw_ds_clone)
Console.Warning("D3D11: Failed to allocate temp texture for DS copy.");
}
OMSetRenderTargets(draw_rt, draw_ds_as_rt, draw_ds, draw_rt_rov, draw_ds_rov, &config.scissor, read_only_dsv);
SetRenderHWShaderResources(config, primid_texture);
SetupOM(config.depth, OMBlendSelector(config.colormask, config.blend), config.blend.constant);
// Clear stencil as close as possible to the RT bind, to avoid framebuffer swaps.
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && need_barrier)
m_ctx->ClearDepthStencilView(*static_cast<GSTexture11*>(draw_ds), D3D11_CLEAR_STENCIL, 0.0f, 1);
SendHWDraw(config, rt_feedbackloop_pass1 ? draw_rt_clone : nullptr, draw_rt, ds_feedbackloop_pass1 ? draw_ds_clone : nullptr, draw_ds_as_rt ? draw_ds_as_rt : draw_ds,
config.require_one_barrier, config.require_full_barrier, rtsize);
if (config.blend_multi_pass.enable)
{
config.ps.no_color1 = config.blend_multi_pass.no_color1;
config.ps.blend_hw = config.blend_multi_pass.blend_hw;
config.ps.dither = config.blend_multi_pass.dither;
SetupPS(config.ps, &config.cb_ps, config.sampler);
SetupOM(config.depth, OMBlendSelector(config.colormask, config.blend_multi_pass.blend), config.blend_multi_pass.blend.constant);
Draw(config);
}
if (config.alpha_second_pass.enable)
{
if (config.cb_ps.FogColor_AREF.a != config.alpha_second_pass.ps_aref)
{
config.cb_ps.FogColor_AREF.a = config.alpha_second_pass.ps_aref;
SetupPS(config.alpha_second_pass.ps, &config.cb_ps, config.sampler);
}
else
{
// ps cbuffer hasn't changed, so don't bother checking
SetupPS(config.alpha_second_pass.ps, nullptr, config.sampler);
}
const bool one_barrier = config.alpha_second_pass.require_one_barrier && m_features.multidraw_fb_copy;
SetupOM(config.alpha_second_pass.depth, OMBlendSelector(config.alpha_second_pass.colormask, config.blend), config.blend.constant);
SendHWDraw(config, rt_feedbackloop_pass2 ? draw_rt_clone : nullptr, draw_rt, ds_feedbackloop_pass2 ? draw_ds_clone : nullptr, draw_ds_as_rt ? draw_ds_as_rt : draw_ds,
one_barrier, config.alpha_second_pass.require_full_barrier, rtsize);
}
if (colclip_rt)
{
config.colclip_update_area = config.colclip_update_area.runion(config.drawarea);
if (config.colclip_mode == GSHWDrawConfig::ColClipMode::ResolveOnly || config.colclip_mode == GSHWDrawConfig::ColClipMode::ConvertAndResolve)
{
const GSVector2i size = config.rt->GetSize();
const GSVector4 dRect(config.colclip_update_area);
const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy();
StretchRect(colclip_rt, sRect, config.rt, dRect, ShaderConvert::COLCLIP_RESOLVE, Nearest);
Recycle(colclip_rt);
g_gs_device->SetColorClipTexture(nullptr);
}
}
}
void GSDevice11::FeedbackCopyAndBind(const GSHWDrawConfig& config,
GSTexture* rt, GSTexture* rt_clone, GSTexture* ds, GSTexture* ds_clone, const GSVector4i& copyarea)
{
if (rt_clone)
{
CopyRect(rt, rt_clone, copyarea, copyarea.left, copyarea.top);
PSSetShaderResource(2, rt_clone);
if (config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_RT)
PSSetShaderResource(0, rt_clone);
}
if (ds_clone)
{
CopyRect(ds, ds_clone, copyarea, copyarea.left, copyarea.top);
PSSetShaderResource(4, ds_clone);
if (config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_DEPTH)
PSSetShaderResource(0, ds_clone);
}
}
// Choose the best copy area based on the hazards and whether we need RT and/or DS copies.
void GSDevice11::FeedbackCopyAndBind(const GSHWDrawConfig& config,
GSTexture* rt, GSTexture* rt_clone, GSTexture* ds, GSTexture* ds_clone,
const GSVector4i& copyarea, const GSVector4i& samplearea)
{
const GSVector4i rtsize = (rt ? rt : ds)->GetRect();
// DX11 can't do partial depth copies so only do attempt individual copies for RT hazards.
if (config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_RT)
{
const GSVector4i union_rect = config.drawarea.runion(config.samplearea);
const u32 size_union = union_rect.width() * union_rect.height();
const u32 size_indiv = config.drawarea.width() * config.drawarea.height() +
config.samplearea.width() * config.samplearea.height();
// Do an individual copy if the union is larger than the sum of individual areas.
if (size_union > size_indiv)
{
FeedbackCopyAndBind(config, rt, rt_clone, ds, ds_clone, ProcessCopyArea(rtsize, config.drawarea));
FeedbackCopyAndBind(config, rt, rt_clone, ds, ds_clone, ProcessCopyArea(rtsize, config.samplearea));
}
else
{
FeedbackCopyAndBind(config, rt, rt_clone, ds, ds_clone, ProcessCopyArea(rtsize, union_rect));
}
}
else
{
// No RT hazards so just need the draw area (or full area for DS).
FeedbackCopyAndBind(config, rt, rt_clone, ds, ds_clone, ProcessCopyArea(rtsize, config.drawarea));
}
};
void GSDevice11::SendHWDraw(const GSHWDrawConfig& config,
GSTexture* draw_rt_clone, GSTexture* draw_rt, GSTexture* draw_ds_clone, GSTexture* draw_ds,
const bool one_barrier, const bool full_barrier, GSVector2i rtsize)
{
#ifdef PCSX2_DEVBUILD
if ((one_barrier || full_barrier) && !(config.IsFeedbackLoopRT(config.ps) || config.IsFeedbackLoopDepth(config.ps))) [[unlikely]]
Console.Warning("D3D11: Possible unnecessary copy detected.");
#endif
if (full_barrier)
{
const u32 draw_list_size = static_cast<u32>(config.drawlist->size());
const u32 indices_per_prim = config.indices_per_prim;
pxAssert(config.drawlist && !config.drawlist->empty());
pxAssert(config.drawlist_bbox && static_cast<u32>(config.drawlist_bbox->size()) == draw_list_size);
if (config.tex_hazard != config.TEX_HAZARD_NONE)
FeedbackCopyAndBind(config, draw_rt, draw_rt_clone, draw_ds, draw_ds_clone, config.samplearea);
for (u32 n = 0, p = 0; n < draw_list_size; n++)
{
const u32 count = config.drawlist->at(n) * indices_per_prim;
const GSVector4i bbox = config.drawlist_bbox->at(n).rintersect(config.drawarea);
FeedbackCopyAndBind(config, draw_rt, draw_rt_clone, draw_ds, draw_ds_clone, bbox);
Draw(config, p, count);
p += count;
}
return;
}
if (one_barrier)
{
FeedbackCopyAndBind(config, draw_rt, draw_rt_clone, draw_ds, draw_ds_clone, config.drawarea, config.samplearea);
}
Draw(config);
if (config.ps.HasColorROV() || config.ps.HasDepthROV())
g_perfmon.Put(GSPerfMon::DrawCallsROV, 1);
}
void GSDevice11::SetRenderHWShaderResources(const GSHWDrawConfig& config, GSTexture* primid_texture)
{
if (config.tex && config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_NONE)
{
CommitClear(config.tex);
PSSetShaderResource(TEXTURE_TEXTURE, config.tex);
}
if (config.pal)
{
CommitClear(config.pal);
PSSetShaderResource(TEXTURE_PALETTE, config.pal);
}
if (primid_texture)
PSSetShaderResource(TEXTURE_PRIMID, primid_texture);
}