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
This commit is contained in:
TJnotJT
2026-06-20 21:13:15 -04:00
committed by Ty
parent b11a406519
commit d1cd5a62ed
18 changed files with 161 additions and 120 deletions
+3 -3
View File
@@ -101,7 +101,7 @@ static u64 s_total_barriers = 0;
static u64 s_total_copies = 0;
static u64 s_total_uploads = 0;
static u64 s_total_readbacks = 0;
static u64 s_total_depth_copies_rov = 0;
static u64 s_total_copies_rov = 0;
static u64 s_total_draws_rov = 0;
static u64 s_total_barriers_rov = 0;
static u32 s_total_frames = 0;
@@ -298,7 +298,7 @@ void Host::BeginPresentFrame()
update_stat(GSPerfMon::TextureCopies, s_total_copies, s_last_copies);
update_stat(GSPerfMon::TextureUploads, s_total_uploads, s_last_uploads);
update_stat(GSPerfMon::Readbacks, s_total_readbacks, s_last_readbacks);
update_stat(GSPerfMon::DepthCopiesROV, s_total_depth_copies_rov, s_last_depth_copies_rov);
update_stat(GSPerfMon::TextureCopiesROV, s_total_copies_rov, s_last_depth_copies_rov);
update_stat(GSPerfMon::DrawCallsROV, s_total_draws_rov, s_last_draws_rov);
update_stat(GSPerfMon::BarriersROV, s_total_barriers_rov, s_last_barriers_rov);
@@ -917,7 +917,7 @@ void GSRunner::DumpStats()
Console.WriteLn(fmt::format("@HWSTAT@ Copies: {} (avg {})", s_total_copies, static_cast<u64>(std::ceil(s_total_copies / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Uploads: {} (avg {})", s_total_uploads, static_cast<u64>(std::ceil(s_total_uploads / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Readbacks: {} (avg {})", s_total_readbacks, static_cast<u64>(std::ceil(s_total_readbacks / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Depth Copies (ROV): {} (avg {})", s_total_depth_copies_rov, static_cast<u64>(std::ceil(s_total_depth_copies_rov / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Copies (ROV): {} (avg {})", s_total_copies_rov, static_cast<u64>(std::ceil(s_total_copies_rov / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Draws Calls (ROV): {} (avg {})", s_total_draws_rov, static_cast<u64>(std::ceil(s_total_draws_rov / static_cast<double>(s_total_drawn_frames)))));
Console.WriteLn(fmt::format("@HWSTAT@ Barriers (ROV): {} (avg {})", s_total_barriers_rov, static_cast<u64>(std::ceil(s_total_barriers_rov / static_cast<double>(s_total_drawn_frames)))));
if (s_perf_enable)
+1 -1
View File
@@ -729,7 +729,7 @@ void GSgetStats(SmallStringBase& info)
(int)std::ceil(pm.Get(GSPerfMon::RenderPasses)),
(int)std::ceil(pm.Get(GSPerfMon::Readbacks)),
(int)std::ceil(pm.Get(GSPerfMon::TextureCopies)),
(int)std::ceil(pm.Get(GSPerfMon::DepthCopiesROV)),
(int)std::ceil(pm.Get(GSPerfMon::TextureCopiesROV)),
(int)std::ceil(pm.Get(GSPerfMon::TextureUploads)));
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ public:
SyncPoint,
Barriers,
RenderPasses,
DepthCopiesROV, // Overlaps with regular texture copies.
TextureCopiesROV, // Overlaps with regular texture copies.
DrawCallsROV, // Overlaps with regular draw calls.
BarriersROV, // Overlaps with regular barriers.
CounterLast,
-2
View File
@@ -696,8 +696,6 @@ void GSDevice::Recycle(GSTexture* t)
return;
t->SetLastFrameUsed(m_frame);
t->ClearUnorderedAccess();
#ifdef PCSX2_DEVBUILD
t->SetDebugName("");
+1 -1
View File
@@ -1492,7 +1492,7 @@ protected:
/// Resolves CAS shader includes for the specified source.
static bool GetCASShaderSource(std::string* source);
/// Applies CAS and writes to the destination texture, which should be a RWTexture.
/// Applies CAS and writes to the destination texture, which should be a shader writeable texture.
virtual bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants) = 0;
/// Perform texture operations for ImGui
+7 -11
View File
@@ -40,7 +40,7 @@ public:
static constexpr Usage Texture = Usage::Texture;
static constexpr Usage RenderTarget = Usage::RenderTarget;
static constexpr Usage DepthStencil = Usage::DepthStencil;
static constexpr Usage FeedbackTarget = Usage::RenderTarget | Usage::Feedback | Usage::ShaderWrite;
static constexpr Usage FeedbackTarget = Usage::RenderTarget | Usage::Feedback;
static constexpr Usage FeedbackDepth = Usage::DepthStencil | Usage::Feedback;
static constexpr Usage ShaderWriteTarget = Usage::RenderTarget | Usage::Feedback | Usage::ShaderWrite;
static constexpr Usage ShaderWriteTexture = Usage::Texture | Usage::ShaderWrite;
@@ -94,10 +94,6 @@ protected:
bool m_needs_mipmaps_generated = true;
ClearValue m_clear_value = {};
// For GL/DX11 since they don't track layouts.
// Used heuristically to decide whether to use ROV for a draw.
bool m_unordered_access = false;
#ifdef PCSX2_DEVBUILD
std::string m_debug_name;
#endif
@@ -223,6 +219,12 @@ public:
return IsFeedbackOrShaderWrite(m_usage);
}
virtual bool IsShaderWriteMode() const
{
// Backends that track state explicitly can specialize this to use the actual state.
return IsShaderWrite();
}
__fi State GetState() const { return m_state; }
__fi void SetState(State state) { m_state = state; }
@@ -254,12 +256,6 @@ public:
// Typical size of a RGBA texture
u32 GetMemUsage() const { return m_size.x * m_size.y * (m_format == Format::UNorm8 ? 1 : 4); }
// The unordered access flag is sticky, so once it's set we keep using ROV for the target
// until it's recycled/destroyed. Only used for DX11/GL, which don't track actual resource layout/state.
virtual bool IsUnorderedAccess() const { return m_unordered_access; }
void SetUnorderedAccess() { m_unordered_access = true; }
void ClearUnorderedAccess() { m_unordered_access = false; }
// Helper routines for formats/types
static bool IsCompressedFormat(Format format) { return (format >= Format::BC1 && format <= Format::BC7); }
};
+16 -1
View File
@@ -48,6 +48,15 @@ static bool IsTextureFormatHWBlendable(ID3D11Device* dev, DXGI_FORMAT format)
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));
@@ -594,7 +603,8 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
}
// 1x1 dummy texture.
m_null_texture = CreateSurface(GSTexture::ShaderWriteTarget, 1, 1, 1, GSTexture::Format::Color);
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;
@@ -717,6 +727,11 @@ void GSDevice11::SetFeatures(IDXGIAdapter1* adapter)
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");
+2
View File
@@ -102,6 +102,8 @@ void GSTexture11::SetDebugName(std::string_view name)
GSDevice11::SetD3DDebugObjectName(m_srv.get(), fmt::format("{} SRV", name));
if (m_rtv)
GSDevice11::SetD3DDebugObjectName(m_rtv.get(), fmt::format("{} RTV", name));
if (m_uav)
GSDevice11::SetD3DDebugObjectName(m_uav.get(), fmt::format("{} UAV", name));
m_debug_name = name;
}
+35 -15
View File
@@ -134,6 +134,15 @@ bool GSDevice12::SupportsTextureFormat(DXGI_FORMAT format)
(support.Support1 & required) == required;
}
bool GSDevice12::IsTextureFormatUAVCapable(DXGI_FORMAT format)
{
constexpr u32 required = D3D12_FORMAT_SUPPORT1_TYPED_UNORDERED_ACCESS_VIEW;
D3D12_FEATURE_DATA_FORMAT_SUPPORT support = { format };
return SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT, &support, sizeof(support))) &&
(support.Support1 & required) == required;
}
bool GSDevice12::SupportsProgrammableSamplePositions()
{
D3D12_FEATURE_DATA_D3D12_OPTIONS2 options = {};
@@ -1441,6 +1450,15 @@ bool GSDevice12::CheckFeatures(const u32& vendor_id)
D3D12_FEATURE_DATA_D3D12_OPTIONS options{};
m_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS));
m_features.rov = options.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)))
{
DXGI_FORMAT dxgi_fmt;
LookupNativeFormat(static_cast<GSTexture::Format>(fmt), &dxgi_fmt, nullptr, nullptr, nullptr, nullptr);
m_features.rov &= IsTextureFormatUAVCapable(dxgi_fmt);
}
}
Console.WriteLnFmt("D3D12: Rasterizer Ordered Views: {}", m_features.rov ? "Supported" : "Not Supported");
Console.WriteLnFmt("D3D12: Tight Alignment: {}", m_allocator->IsTightAlignmentSupported() ? "Supported" : "Not Supported");
@@ -1635,7 +1653,7 @@ void GSDevice12::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r,
sTex12->TransitionToState(GSTexture12::ResourceState::CopySrc);
sTex12->SetUseFenceCounter(GetCurrentFenceValue());
if (m_tfx_textures[0] && sTex12->GetSRVDescriptor() == m_tfx_textures[0])
PSSetShaderResource(0, nullptr, false);
PSSetShaderResource(TEXTURE_TEXTURE, nullptr, false);
dTex12->TransitionToState(GSTexture12::ResourceState::CopyDst);
dTex12->SetUseFenceCounter(GetCurrentFenceValue());
@@ -2605,9 +2623,11 @@ GSDevice12::ComPtr<ID3DBlob> GSDevice12::GetUtilityPixelShader(const std::string
bool GSDevice12::CreateNullTexture()
{
const GSTexture::Usage null_access = m_features.rov ? GSTexture::ShaderWriteTarget : GSTexture::FeedbackTarget;
const DXGI_FORMAT null_uav_format = m_features.rov ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_UNKNOWN;
m_null_texture =
GSTexture12::Create(GSTexture::ShaderWriteTarget, GSTexture::Format::Color, 1, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8B8A8_UNORM);
GSTexture12::Create(null_access, GSTexture::Format::Color, 1, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, null_uav_format);
if (!m_null_texture)
return false;
@@ -3528,7 +3548,7 @@ void GSDevice12::PSSetSampler(GSHWDrawConfig::SamplerSelector sel)
m_dirty_flags |= DIRTY_FLAG_TFX_SAMPLERS;
}
void GSDevice12::PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds)
void GSDevice12::PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds)
{
GSTexture12* d12Rt = static_cast<GSTexture12*>(rt);
GSTexture12* d12Ds = static_cast<GSTexture12*>(ds);
@@ -4222,7 +4242,7 @@ GSTexture12* GSDevice12::SetupPrimitiveTrackingDATE(GSHWDrawConfig& config, Pipe
// and bind the image to the primitive sampler
image->TransitionToState(GSTexture12::ResourceState::PixelShaderResource);
PSSetShaderResource(3, image, false);
PSSetShaderResource(TEXTURE_PRIMID, image, false);
return image;
}
@@ -4340,11 +4360,11 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
// bind textures before checking the render pass, in case we need to transition them
if (config.tex)
{
PSSetShaderResource(0, config.tex, config.tex != config.rt && config.tex != config.ds);
PSSetShaderResource(TEXTURE_TEXTURE, config.tex, config.tex != config.rt && config.tex != config.ds);
PSSetSampler(config.sampler);
}
if (config.pal)
PSSetShaderResource(1, config.pal, true);
PSSetShaderResource(TEXTURE_PALETTE, config.pal, true);
if (config.blend.constant_enable)
SetBlendConstants(config.blend.constant);
@@ -4400,7 +4420,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
// we're not drawing to the RT, so we can use it as a source
if (config.require_one_barrier && !m_features.texture_barrier)
PSSetShaderResource(2, draw_rt, true);
PSSetShaderResource(TEXTURE_RT, draw_rt, true);
}
draw_rt = colclip_rt;
@@ -4409,7 +4429,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
// Clear texture binding when it's bound to RT or DS.
if (!config.tex && ((draw_rt && static_cast<GSTexture12*>(draw_rt)->GetSRVDescriptor() == m_tfx_textures[0]) ||
(draw_ds && static_cast<GSTexture12*>(draw_ds)->GetSRVDescriptor() == m_tfx_textures[0])))
PSSetShaderResource(0, nullptr, false);
PSSetShaderResource(TEXTURE_TEXTURE, nullptr, false);
if (InRenderPass() && (m_current_render_target == draw_rt || m_current_depth_target == draw_ds))
{
@@ -4471,15 +4491,15 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
}
if (config.require_one_barrier)
PSSetShaderResource(2, draw_rt_clone, true);
PSSetShaderResource(TEXTURE_RT, draw_rt_clone, true);
if (config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_RT)
PSSetShaderResource(0, draw_rt_clone, true);
PSSetShaderResource(TEXTURE_TEXTURE, draw_rt_clone, true);
}
else
Console.Warning("D3D12: Failed to allocate temp texture for RT copy.");
}
PSSetUnorderedAccess(draw_rt_rov, draw_ds_rov, config.ps.HasColorOutput(), config.ps.HasDepthROVWrite());
PSSetROVs(draw_rt_rov, draw_ds_rov, config.ps.HasColorOutput(), config.ps.HasDepthROVWrite());
// For depth testing and sampling, use a read only dsv, otherwise use a write dsv
OMSetRenderTargets(draw_rt, draw_ds_as_rt, draw_ds, config.scissor,
@@ -4645,11 +4665,11 @@ void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig&
Console.Warning("D3D12: Possible unnecessary barrier detected.");
#endif
if ((one_barrier || full_barrier) && feedback_rt)
PSSetShaderResource(2, draw_rt, false, ResourceType::FBL);
PSSetShaderResource(TEXTURE_RT, draw_rt, false, ResourceType::FBL);
if (config.tex_hazard == GSHWDrawConfig::TEX_HAZARD_RT)
PSSetShaderResource(0, draw_rt, false, ResourceType::FBL);
PSSetShaderResource(TEXTURE_TEXTURE, draw_rt, false, ResourceType::FBL);
if ((one_barrier || full_barrier) && feedback_depth)
PSSetShaderResource(4, draw_ds, false, ResourceType::FBL);
PSSetShaderResource(TEXTURE_DEPTH, draw_ds, false, ResourceType::FBL);
if (full_barrier)
{
+4 -1
View File
@@ -152,6 +152,9 @@ public:
/// Test for support for the specified texture format.
bool SupportsTextureFormat(DXGI_FORMAT format);
/// Test for UAV support for the specified texture format.
bool IsTextureFormatUAVCapable(DXGI_FORMAT format);
// Partial depth copies require ProgrammableSamplePositions tier 1.
bool SupportsProgrammableSamplePositions();
@@ -552,7 +555,7 @@ public:
void PSSetShaderResource(int i, GSTexture* sr, bool check_state, ResourceType type = ResourceType::SRV);
void PSSetSampler(GSHWDrawConfig::SamplerSelector sel);
void PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds);
void PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds);
void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, GSTexture* ds_as_rt, const GSVector4i& scissor,
bool depth_read = false, const GSVector2i& viewport_size = {});
+1 -1
View File
@@ -56,7 +56,7 @@ public:
__fi ID3D12Resource* GetResource() const { return m_resource.get(); }
__fi ID3D12Resource* GetFBLResource() const { return m_resource_fbl.get(); }
virtual bool IsUnorderedAccess() const override { return GetResourceState() == ResourceState::PixelShaderUAV; }
bool IsShaderWriteMode() const override { return GetResourceState() == ResourceState::PixelShaderUAV; }
void* GetNativeHandle() const override;
+67 -65
View File
@@ -6241,7 +6241,7 @@ void GSRendererHW::EmulateDither()
if (m_conf.ps.dither || m_conf.blend_multi_pass.dither)
{
const GIFRegDIMX& DIMX = m_draw_env->DIMX;
GL_DBG("DITHERING mode %d (%d)", (GSConfig.Dithering == 3) ? "Force 32bit" : ((GSConfig.Dithering == 0) ? "Disabled" : "Enabled"), GSConfig.Dithering);
GL_DBG("DITHERING mode %s (%d)", (GSConfig.Dithering == 3) ? "Force 32bit" : ((GSConfig.Dithering == 0) ? "Disabled" : "Enabled"), GSConfig.Dithering);
if (m_conf.ps.dither || GSConfig.Dithering == 3)
m_conf.ps.dither = GSConfig.Dithering;
@@ -7601,7 +7601,7 @@ void GSRendererHW::DetermineROVUsage(GSTextureCache::Target* rt, GSTextureCache:
const bool multipass_depth = (full_barrier && m_conf.ps.IsFeedbackLoopDepth()) || two_pass_alpha;
// If already ROV, just continue the usage.
const bool color_is_rov = rt && rt->m_texture->IsUnorderedAccess();
const bool color_is_rov = rt && rt->m_texture->IsShaderWriteMode();
const bool depth_is_rov = ds && ds->m_texture->IsDepthColor();
bool use_rov_color = (color_write && multipass_color) || color_is_rov;
@@ -7688,7 +7688,7 @@ void GSRendererHW::ConfigureROV(bool color_rov, bool depth_rov)
{
m_conf.cb_ps.FbMask |= fbmask;
}
GL_INS("ROV: FbMask={ R=%x, G=%x, B=%x, A=%x }",
GL_INS("ROV: FbMask={R=%x, G=%x, B=%x, A=%x}",
m_conf.cb_ps.FbMask.r, m_conf.cb_ps.FbMask.g, m_conf.cb_ps.FbMask.b, m_conf.cb_ps.FbMask.a);
}
else
@@ -7807,89 +7807,92 @@ void GSRendererHW::ConfigureROV(bool color_rov, bool depth_rov)
}
}
void GSRendererHW::SetUnorderedAccessFlag(GSTextureCache::Target* rt)
void GSRendererHW::ConvertTextureTypeROVSingle(GSTextureCache::Target* tgt, bool shader_write)
{
// Set flag for ROV activation heuristic. Only used by DX11.
// Only needed for RT, as we use a different method for depth.
if (rt)
{
if (m_conf.ps.HasColorROV())
rt->m_texture->SetUnorderedAccess();
else
rt->m_texture->ClearUnorderedAccess();
}
}
const bool depth = (tgt->m_type == GSTextureCache::DepthStencil);
static void CopyDepthTextureROV(GSTexture* src, GSTexture* dst)
{
switch (src->GetState())
GSTexture* old_tex = depth ? m_conf.ds : m_conf.rt;
GSTexture::Usage usage = shader_write ? GSTexture::ShaderWriteTarget : GSTexture::FeedbackTarget;
GSTexture* new_tex = depth ?
(shader_write ?
g_gs_device->FetchSurface(usage, old_tex->GetSize(), 1, GSTexture::Format::DepthColor, false, false) :
g_gs_device->CreateDepthStencil(old_tex->GetSize(), false, true)) :
g_gs_device->FetchSurface(usage, old_tex->GetSize(), 1, GSTexture::Format::Color, false, false);
switch (old_tex->GetState())
{
case GSTexture::State::Cleared:
g_gs_device->ClearDepth(dst, src->GetClearDepth());
if (depth)
g_gs_device->ClearDepth(new_tex, old_tex->GetClearDepth());
else
g_gs_device->ClearRenderTarget(new_tex, old_tex->GetClearColor());
break;
case GSTexture::State::Invalidated:
g_gs_device->InvalidateRenderTarget(dst);
g_gs_device->InvalidateRenderTarget(new_tex);
break;
case GSTexture::State::Dirty:
g_gs_device->StretchRectAuto(src, dst, Nearest);
g_gs_device->StretchRectAuto(old_tex, new_tex, Nearest);
// Count stats as part of both standard and ROV.
g_perfmon.Put(GSPerfMon::TextureCopiesROV, 1.0);
g_perfmon.Put(GSPerfMon::DrawCallsROV, 1.0);
break;
default:
pxAssert(false);
break;
}
// These stats are counted both as part of ROV and non-ROV stats.
g_perfmon.Put(GSPerfMon::DepthCopiesROV, 1.0);
g_perfmon.Put(GSPerfMon::DrawCallsROV, 1.0);
};
void GSRendererHW::ConvertDepthFormatROV(GSTextureCache::Target* ds)
{
if (!ds)
return;
GSTexture* ds_tex_old = m_conf.ds;
GSTexture* ds_tex_new = nullptr;
// Convert depth to depth color or vice versa if needed.
bool depth_to_color;
if (m_conf.ps.HasDepthROV() && !ds_tex_old->IsDepthColor())
{
depth_to_color = true;
ds_tex_new = g_gs_device->CreateShaderWriteTarget(ds_tex_old->GetSize(), GSTexture::Format::DepthColor, false, true);
}
else if (!m_conf.ps.HasDepthROV() && ds_tex_old->IsDepthColor())
{
depth_to_color = false;
ds_tex_new = g_gs_device->CreateDepthStencil(ds->m_texture->GetSize(), false, true);
}
else
{
return;
}
GL_PUSH("HW: Convert %s for ROV.", depth_to_color ? "DepthStencil -> DepthColor" : "DepthColor -> DepthStencil");
CopyDepthTextureROV(ds_tex_old, ds_tex_new);
#if PCSX2_DEVBUILD
ds_tex_new->SetDebugName(ds->m_texture->GetDebugName());
new_tex->SetDebugName(tgt->m_texture->GetDebugName());
#endif
// Fix up the texture cache.
if (ds->m_texture == ds_tex_old)
if (tgt->m_texture == old_tex)
{
GL_CACHE("HW: Replaced texture for DS @ 0x%04x", ds->m_TEX0.TBP0);
ds->m_texture = ds_tex_new;
GL_CACHE("HW: Replaced texture for %s @ 0x%04x", depth ? "DS" : "RT", tgt->m_TEX0.TBP0);
tgt->m_texture = new_tex;
}
else
{
// Must be the temporary Z.
pxAssert(g_texture_cache->GetTemporaryZ() == ds_tex_old);
pxAssert(depth && g_texture_cache->GetTemporaryZ() == old_tex);
GL_CACHE("HW: Replaced texture for temporary Z @ 0x%04x", g_texture_cache->GetTemporaryZInfo().ZBP);
g_texture_cache->SetTemporaryZ(ds_tex_new);
g_texture_cache->SetTemporaryZ(new_tex);
}
g_gs_device->Recycle(ds_tex_old);
// Fixup the backend config.
if (depth)
m_conf.ds = new_tex;
else
m_conf.rt = new_tex;
m_conf.ds = ds_tex_new;
g_gs_device->Recycle(old_tex);
}
void GSRendererHW::ConvertTextureTypeROV(GSTextureCache::Target* rt, GSTextureCache::Target* ds)
{
// Convert depth to the proper type/format.
if (ds)
{
if (m_conf.ps.HasDepthROV() && !ds->m_texture->IsShaderWrite())
{
GL_PUSH("HW: Convert DepthStencil -> DepthColor for ROV.");
ConvertTextureTypeROVSingle(ds, true);
}
else if (!m_conf.ps.HasDepthROV() && !ds->m_texture->IsDepthStencil())
{
GL_PUSH("HW: Convert DepthColor -> DepthStencil for non-ROV.");
ConvertTextureTypeROVSingle(ds, false);
}
}
// Convert color to the proper type. This only adds the shader read/write flag and doesn't remove it,
// since adding the read/write flag doesn't lose any pipeline capabilities.
if (rt && m_conf.ps.HasColorROV() && !rt->m_texture->IsShaderWrite())
{
GL_PUSH("HW: Convert RenderTarget -> RenderTarget (shader write) for ROV.");
ConvertTextureTypeROVSingle(rt, true);
}
}
__ri static constexpr bool IsRedundantClamp(u8 clamp, u32 clamp_min, u32 clamp_max, u32 tsize)
@@ -9455,8 +9458,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
// Call before computing the full drawlist in case ROV is used and we don't need it.
DetermineROVUsage(rt, ds);
ConvertDepthFormatROV(ds);
SetUnorderedAccessFlag(rt);
ConvertTextureTypeROV(rt, ds);
// Barriers must be determined before indices are modified via HandleProvokingVertexFirst/SetupIA.
// This also computes the drawlist if needed.
+2 -2
View File
@@ -256,8 +256,8 @@ private:
void GetForcedROVUsage(bool& color_cov, bool& depth_rov); // Whether having color or depth with the current config forces the other.
void DetermineROVUsage(GSTextureCache::Target* rt, GSTextureCache::Target* ds); // Heuristics to determine whether to enable/disable ROV
void ConfigureROV(bool color_rov, bool depth_rov); // Actual config for ROV
void SetUnorderedAccessFlag(GSTextureCache::Target* rt); // Set flag for DX11 ROV heuristic to work.
void ConvertDepthFormatROV(GSTextureCache::Target* ds); // Convert between depth and depth color if needed.
void ConvertTextureTypeROV(GSTextureCache::Target* rt, GSTextureCache::Target* ds); // Convert to RW capable textures if needed.
void ConvertTextureTypeROVSingle(GSTextureCache::Target* tgt, bool shader_write); // Helper to do the above.
void SetTCOffset();
bool NextDrawColClip() const;
+1 -1
View File
@@ -2007,7 +2007,7 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
MTLRenderPipelineColorAttachmentDescriptor* color1 = [[pdesc colorAttachments] objectAtIndexedSubscript:1];
[color1 setPixelFormat:MTLPixelFormatR32Float];
}
NSString* pname = [NSString stringWithFormat:@"HW Render %x.%x.%llx.%llx", vssel_mtl.key, pssel.key_hi, pssel.key_lo, extras.fullkey];
NSString* pname = [NSString stringWithFormat:@"HW Render %x.%llx.%llx.%x", vssel_mtl.key, pssel.key_hi, pssel.key_lo, extras.fullkey];
auto pipeline = MakePipeline(pdesc, vs, ps, pname);
[m_current_render.encoder setRenderPipelineState:pipeline];
@@ -908,11 +908,6 @@ bool GSDeviceOGL::CheckFeatures()
}
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand && m_features.feedback_loops();
if (GSConfig.HWROV)
{
Console.Warning("GL: ROV is not implemented for GL and will be disabled.");
}
return true;
}
+18 -8
View File
@@ -2735,14 +2735,18 @@ bool GSDeviceVK::CheckFeatures()
m_features.point_expand ? "hardware" : "vertex expanding",
m_features.line_expand ? "hardware" : "vertex expanding");
bool has_rov_storage_flags = true;
// Check texture format support before we try to create them.
for (u32 fmt = static_cast<u32>(GSTexture::Format::Color); fmt < static_cast<u32>(GSTexture::Format::PrimID); fmt++)
{
const VkFormat vkfmt = LookupNativeFormat(static_cast<GSTexture::Format>(fmt));
const VkFormatFeatureFlags bits =
(static_cast<GSTexture::Format>(fmt) == GSTexture::Format::DepthStencil) ?
(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) :
(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT);
VkFormatFeatureFlags bits = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
if (static_cast<GSTexture::Format>(fmt) == GSTexture::Format::DepthStencil)
bits |= VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
else
bits |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT;
VkFormatProperties props = {};
vkGetPhysicalDeviceFormatProperties(m_physical_device, vkfmt, &props);
@@ -2753,6 +2757,9 @@ bool GSDeviceVK::CheckFeatures()
fmt, static_cast<unsigned>(vkfmt), props.optimalTilingFeatures, bits);
return false;
}
if (GSTexture::IsShaderWriteFormat(static_cast<GSTexture::Format>(fmt)))
has_rov_storage_flags &= ((props.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0);
}
m_features.dxt_textures = m_device_features.textureCompressionBC;
@@ -2769,7 +2776,9 @@ bool GSDeviceVK::CheckFeatures()
m_max_texture_size = m_device_properties.limits.maxImageDimension2D;
m_features.rov = m_optional_extensions.vk_ext_fragment_shader_interlock &&
m_device_features.fragmentStoresAndAtomics;
m_device_features.fragmentStoresAndAtomics &&
has_rov_storage_flags &&
!m_features.framebuffer_fetch;
return true;
}
@@ -3822,7 +3831,8 @@ VkShaderModule GSDeviceVK::GetUtilityFragmentShader(const std::string& source, c
bool GSDeviceVK::CreateNullTexture()
{
m_null_texture = GSTextureVK::Create(GSTexture::ShaderWriteTarget, GSTexture::Format::Color, 1, 1, 1);
GSTexture::Usage null_usage = m_features.rov ? GSTexture::ShaderWriteTarget : GSTexture::FeedbackTarget;
m_null_texture = GSTextureVK::Create(null_usage, GSTexture::Format::Color, 1, 1, 1);
if (!m_null_texture)
return false;
@@ -5266,7 +5276,7 @@ void GSDeviceVK::SetLineWidth(float width)
m_dirty_flags |= DIRTY_FLAG_LINE_WIDTH;
}
void GSDeviceVK::PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds)
void GSDeviceVK::PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds)
{
GSTextureVK* vkRt = static_cast<GSTextureVK*>(rt);
GSTextureVK* vkDs = static_cast<GSTextureVK*>(ds);
@@ -6183,7 +6193,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
PSSetShaderResource(TFX_TEXTURE_DEPTH, nullptr, false);
}
PSSetUnorderedAccess(draw_rt_rov, draw_ds_rov, config.ps.HasColorOutput(), config.ps.HasDepthROVWrite());
PSSetROVs(draw_rt_rov, draw_ds_rov, config.ps.HasColorOutput(), config.ps.HasDepthROVWrite());
OMSetRenderTargets(draw_rt, draw_ds, config.scissor, static_cast<FeedbackLoopFlag>(pipe.feedback_loop_flags), rtsize);
+1 -1
View File
@@ -607,7 +607,7 @@ public:
void IASetIndexBuffer(const void* index, size_t count);
void VSSetIndexBuffer(const void* index, size_t count);
void PSSetUnorderedAccess(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds);
void PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool write_ds);
void PSSetShaderResource(int i, GSTexture* sr, bool check_state, ResourceType type = ResourceType::SRV);
void PSSetSampler(GSHWDrawConfig::SamplerSelector sel);
+1 -1
View File
@@ -42,7 +42,7 @@ public:
__fi VkImage GetImage() const { return m_image; }
__fi VkImageView GetView() const { return m_view; }
__fi Layout GetLayout() const { return m_layout; }
virtual bool IsUnorderedAccess() const override { return GetLayout() == Layout::ReadWriteImage; }
bool IsShaderWriteMode() const override { return GetLayout() == Layout::ReadWriteImage; }
__fi VkFormat GetVkFormat() const { return m_vk_format; }