GS/HW: Reduce number of copies for HDR

This commit is contained in:
refractionpcsx2 2025-01-30 04:46:54 +00:00 committed by Ty
parent cd98f72f10
commit 2d6a42ac06
12 changed files with 633 additions and 265 deletions

View File

@ -799,7 +799,8 @@ void ps_fbmask(inout float4 C, float2 pos_xy)
{ {
if (PS_FBMASK) if (PS_FBMASK)
{ {
float4 RT = trunc(RtTexture.Load(int3(pos_xy, 0)) * 255.0f + 0.1f); float multi = PS_HDR ? 65535.0f : 255.0f;
float4 RT = trunc(RtTexture.Load(int3(pos_xy, 0)) * multi + 0.1f);
C = (float4)(((uint4)C & ~FbMask) | ((uint4)RT & FbMask)); C = (float4)(((uint4)C & ~FbMask) | ((uint4)RT & FbMask));
} }
} }
@ -895,7 +896,8 @@ void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy)
} }
float Ad = PS_RTA_CORRECTION ? trunc(RT.a * 128.0f + 0.1f) / 128.0f : trunc(RT.a * 255.0f + 0.1f) / 128.0f; float Ad = PS_RTA_CORRECTION ? trunc(RT.a * 128.0f + 0.1f) / 128.0f : trunc(RT.a * 255.0f + 0.1f) / 128.0f;
float3 Cd = trunc(RT.rgb * 255.0f + 0.1f); float color_multi = PS_HDR ? 65535.0f : 255.0f;
float3 Cd = trunc(RT.rgb * color_multi + 0.1f);
float3 Cs = Color.rgb; float3 Cs = Color.rgb;
float3 A = (PS_BLEND_A == 0) ? Cs : ((PS_BLEND_A == 1) ? Cd : (float3)0.0f); float3 A = (PS_BLEND_A == 0) ? Cs : ((PS_BLEND_A == 1) ? Cd : (float3)0.0f);

View File

@ -708,7 +708,11 @@ void ps_fbmask(inout vec4 C)
{ {
// FIXME do I need special case for 16 bits // FIXME do I need special case for 16 bits
#if PS_FBMASK #if PS_FBMASK
#if PS_HDR == 1
vec4 RT = trunc(fetch_rt() * 65535.0f);
#else
vec4 RT = trunc(fetch_rt() * 255.0f + 0.1f); vec4 RT = trunc(fetch_rt() * 255.0f + 0.1f);
#endif
C = vec4((uvec4(C) & ~FbMask) | (uvec4(RT) & FbMask)); C = vec4((uvec4(C) & ~FbMask) | (uvec4(RT) & FbMask));
#endif #endif
} }
@ -823,7 +827,11 @@ float As = As_rgba.a;
#endif #endif
// Let the compiler do its jobs ! // Let the compiler do its jobs !
#if PS_HDR == 1
vec3 Cd = trunc(RT.rgb * 65535.0f);
#else
vec3 Cd = trunc(RT.rgb * 255.0f + 0.1f); vec3 Cd = trunc(RT.rgb * 255.0f + 0.1f);
#endif
vec3 Cs = Color.rgb; vec3 Cs = Color.rgb;
#if PS_BLEND_A == 0 #if PS_BLEND_A == 0

View File

@ -972,7 +972,12 @@ vec4 ps_color()
void ps_fbmask(inout vec4 C) void ps_fbmask(inout vec4 C)
{ {
#if PS_FBMASK #if PS_FBMASK
#if PS_HDR == 1
vec4 RT = trunc(sample_from_rt() * 65535.0f);
#else
vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f); vec4 RT = trunc(sample_from_rt() * 255.0f + 0.1f);
#endif
C = vec4((uvec4(C) & ~FbMask) | (uvec4(RT) & FbMask)); C = vec4((uvec4(C) & ~FbMask) | (uvec4(RT) & FbMask));
#endif #endif
} }
@ -1090,7 +1095,11 @@ void ps_blend(inout vec4 Color, inout vec4 As_rgba)
#endif #endif
// Let the compiler do its jobs ! // Let the compiler do its jobs !
#if PS_HDR == 1
vec3 Cd = trunc(RT.rgb * 65535.0f);
#else
vec3 Cd = trunc(RT.rgb * 255.0f + 0.1f); vec3 Cd = trunc(RT.rgb * 255.0f + 0.1f);
#endif
vec3 Cs = Color.rgb; vec3 Cs = Color.rgb;
#if PS_BLEND_A == 0 #if PS_BLEND_A == 0

View File

@ -8,6 +8,7 @@
#include "GS/GS.h" #include "GS/GS.h"
#include "GS/Renderers/Common/GSFastList.h" #include "GS/Renderers/Common/GSFastList.h"
#include "GS/Renderers/Common/GSTexture.h" #include "GS/Renderers/Common/GSTexture.h"
#include "GS/Renderers/Vulkan/GSTextureVK.h"
#include "GS/Renderers/Common/GSVertex.h" #include "GS/Renderers/Common/GSVertex.h"
#include "GS/GSAlignedClass.h" #include "GS/GSAlignedClass.h"
#include "GS/GSExtra.h" #include "GS/GSExtra.h"
@ -674,6 +675,15 @@ struct alignas(16) GSHWDrawConfig
Full, ///< Full emulation (using barriers / ROV) Full, ///< Full emulation (using barriers / ROV)
}; };
enum class HDRMode : u8
{
NoModify = 0,
ConvertOnly = 1,
ResolveOnly = 2,
ConvertAndResolve = 3,
EarlyResolve = 4
};
GSTexture* rt; ///< Render target GSTexture* rt; ///< Render target
GSTexture* ds; ///< Depth stencil GSTexture* ds; ///< Depth stencil
GSTexture* tex; ///< Source texture GSTexture* tex; ///< Source texture
@ -730,6 +740,11 @@ struct alignas(16) GSHWDrawConfig
VSConstantBuffer cb_vs; VSConstantBuffer cb_vs;
PSConstantBuffer cb_ps; PSConstantBuffer cb_ps;
// These are here as they need to be preserved between draws, and the state clear only does up to the constant buffers.
HDRMode hdr_mode;
GIFRegFRAME hdr_frame;
GSVector4i hdr_update_area; ///< Area in the framebuffer which HDR will modify;
}; };
class GSDevice : public GSAlignedClass<32> class GSDevice : public GSAlignedClass<32>
@ -850,6 +865,7 @@ protected:
GSTexture* m_target_tmp = nullptr; GSTexture* m_target_tmp = nullptr;
GSTexture* m_current = nullptr; GSTexture* m_current = nullptr;
GSTexture* m_cas = nullptr; GSTexture* m_cas = nullptr;
GSTexture* m_hdr_rt = nullptr; ///< Temp HDR texture
bool AcquireWindow(bool recreate_window); bool AcquireWindow(bool recreate_window);
@ -874,6 +890,10 @@ public:
/// Returns a string containing current adapter in use. /// Returns a string containing current adapter in use.
const std::string& GetName() const { return m_name; } const std::string& GetName() const { return m_name; }
GSTexture* GetHDRTexture() const { return m_hdr_rt; }
void SetHDRTexture(GSTexture* tex) { m_hdr_rt = tex; }
/// Returns a string representing the specified API. /// Returns a string representing the specified API.
static const char* RenderAPIToString(RenderAPI api); static const char* RenderAPIToString(RenderAPI api);

View File

@ -2521,6 +2521,47 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize(); GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize();
GSTexture* hdr_rt = g_gs_device->GetHDRTexture();
if (hdr_rt)
{
if (config.hdr_mode == GSHWDrawConfig::HDRMode::EarlyResolve)
{
const GSVector2i size = config.rt->GetSize();
const GSVector4 dRect(config.hdr_update_area);
const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy();
StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
Recycle(hdr_rt);
g_gs_device->SetHDRTexture(nullptr);
hdr_rt = nullptr;
}
else
config.ps.hdr = 1;
}
if (config.ps.hdr)
{
if (!hdr_rt)
{
config.hdr_update_area = config.drawarea;
const GSVector4 dRect = GSVector4((config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea);
const GSVector4 sRect = dRect / GSVector4(rtsize.x, rtsize.y).xyxy();
hdr_rt = CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor);
if (!hdr_rt)
return;
g_gs_device->SetHDRTexture(hdr_rt);
// Warning: StretchRect must be called before BeginScene otherwise
// vertices will be overwritten. Trust me you don't want to do that.
StretchRect(config.rt, sRect, hdr_rt, dRect, ShaderConvert::HDR_INIT, false);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
}
}
GSTexture* primid_tex = nullptr; GSTexture* primid_tex = nullptr;
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking) if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking)
{ {
@ -2528,7 +2569,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
if (!primid_tex) if (!primid_tex)
return; return;
StretchRect(config.rt, GSVector4(config.drawarea) / GSVector4(rtsize).xyxy(), StretchRect(hdr_rt ? hdr_rt : config.rt, GSVector4(config.drawarea) / GSVector4(rtsize).xyxy(),
primid_tex, GSVector4(config.drawarea), m_date.primid_init_ps[static_cast<u8>(config.datm)].get(), nullptr, false); primid_tex, GSVector4(config.drawarea), m_date.primid_init_ps[static_cast<u8>(config.datm)].get(), nullptr, false);
} }
else if (config.destination_alpha != GSHWDrawConfig::DestinationAlphaMode::Off) else if (config.destination_alpha != GSHWDrawConfig::DestinationAlphaMode::Off)
@ -2544,22 +2585,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
{GSVector4(dst.z, -dst.w, 0.5f, 1.0f), GSVector2(src.z, src.w)}, {GSVector4(dst.z, -dst.w, 0.5f, 1.0f), GSVector2(src.z, src.w)},
}; };
SetupDATE(config.rt, config.ds, vertices, config.datm); SetupDATE(hdr_rt ? hdr_rt : config.rt, config.ds, vertices, config.datm);
}
GSTexture* hdr_rt = nullptr;
if (config.ps.hdr)
{
const GSVector4 dRect(config.drawarea);
const GSVector4 sRect = dRect / GSVector4(rtsize.x, rtsize.y).xyxy();
hdr_rt = CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor);
if (!hdr_rt)
return;
// Warning: StretchRect must be called before BeginScene otherwise
// vertices will be overwritten. Trust me you don't want to do that.
StretchRect(config.rt, sRect, hdr_rt, dRect, ShaderConvert::HDR_INIT, false);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
} }
if (config.vs.expand != GSHWDrawConfig::VSExpand::None) if (config.vs.expand != GSHWDrawConfig::VSExpand::None)
@ -2623,7 +2649,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
// Do not always bind the rt when it's not needed, // Do not always bind the rt when it's not needed,
// only bind it when effects use it such as fbmask emulation currently // only bind it when effects use it such as fbmask emulation currently
// because we copy the frame buffer and it is quite slow. // because we copy the frame buffer and it is quite slow.
CloneTexture(config.rt, &rt_copy, config.drawarea); CloneTexture(hdr_rt ? hdr_rt : config.rt, &rt_copy, config.drawarea);
if (rt_copy) if (rt_copy)
{ {
if (config.require_one_barrier) if (config.require_one_barrier)
@ -2689,12 +2715,19 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
Recycle(primid_tex); Recycle(primid_tex);
if (hdr_rt) if (hdr_rt)
{
config.hdr_update_area = config.hdr_update_area.runion(config.drawarea);
if (config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve)
{ {
const GSVector2i size = config.rt->GetSize(); const GSVector2i size = config.rt->GetSize();
const GSVector4 dRect(config.drawarea); const GSVector4 dRect(config.hdr_update_area);
const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy(); const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy();
StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false); StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false);
g_perfmon.Put(GSPerfMon::TextureCopies, 1); g_perfmon.Put(GSPerfMon::TextureCopies, 1);
Recycle(hdr_rt); Recycle(hdr_rt);
g_gs_device->SetHDRTexture(nullptr);
}
} }
} }

View File

@ -3816,18 +3816,64 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
// Destination Alpha Setup // Destination Alpha Setup
const bool stencil_DATE = (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::Stencil || const bool stencil_DATE = (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::Stencil ||
config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne); config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne);
GSTexture12* hdr_rt = static_cast<GSTexture12*>(g_gs_device->GetHDRTexture());
GSTexture12* draw_rt = static_cast<GSTexture12*>(config.rt);
GSTexture12* draw_ds = static_cast<GSTexture12*>(config.ds);
GSTexture12* draw_rt_clone = nullptr;
// Align the render area to 128x128, hopefully avoiding render pass restarts for small render area changes (e.g. Ratchet and Clank).
const GSVector2i rtsize(config.rt ? config.rt->GetSize() : config.ds->GetSize());
PipelineSelector& pipe = m_pipeline_selector;
// figure out the pipeline
UpdateHWPipelineSelector(config);
// now blit the hdr texture back to the original target
if (hdr_rt)
{
if (config.hdr_mode == GSHWDrawConfig::HDRMode::EarlyResolve)
{
GL_PUSH("Blit HDR back to RT");
EndRenderPass();
hdr_rt->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
draw_rt = static_cast<GSTexture12*>(config.rt);
OMSetRenderTargets(draw_rt, draw_ds, config.scissor);
// if this target was cleared and never drawn to, perform the clear as part of the resolve here.
BeginRenderPass(GetLoadOpForTexture(draw_rt), D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE,
GetLoadOpForTexture(draw_ds),
draw_ds ? D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE : D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
draw_rt->GetUNormClearColor(), 0.0f, 0);
const GSVector4 sRect(GSVector4(config.hdr_update_area) / GSVector4(rtsize.x, rtsize.y).xyxy());
SetPipeline(m_hdr_finish_pipelines[pipe.ds].get());
SetUtilityTexture(hdr_rt, m_point_sampler_cpu);
DrawStretchRect(sRect, GSVector4(config.hdr_update_area), rtsize);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
Recycle(hdr_rt);
g_gs_device->SetHDRTexture(nullptr);
}
else
{
draw_rt = hdr_rt;
pipe.ps.hdr = 1;
}
}
if (stencil_DATE) if (stencil_DATE)
SetupDATE(config.rt, config.ds, config.datm, config.drawarea); SetupDATE(draw_rt, config.ds, config.datm, config.drawarea);
// stream buffer in first, in case we need to exec // stream buffer in first, in case we need to exec
SetVSConstantBuffer(config.cb_vs); SetVSConstantBuffer(config.cb_vs);
SetPSConstantBuffer(config.cb_ps); SetPSConstantBuffer(config.cb_ps);
// figure out the pipeline
UpdateHWPipelineSelector(config);
// bind textures before checking the render pass, in case we need to transition them // bind textures before checking the render pass, in case we need to transition them
PipelineSelector& pipe = m_pipeline_selector;
if (config.tex) if (config.tex)
{ {
PSSetShaderResource(0, config.tex, config.tex != config.rt); PSSetShaderResource(0, config.tex, config.tex != config.rt);
@ -3843,7 +3889,10 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
GSTexture12* date_image = nullptr; GSTexture12* date_image = nullptr;
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking) if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking)
{ {
GSTexture* backup_rt = config.rt;
config.rt = draw_rt;
date_image = SetupPrimitiveTrackingDATE(config, pipe); date_image = SetupPrimitiveTrackingDATE(config, pipe);
config.rt = backup_rt;
if (!date_image) if (!date_image)
{ {
Console.WriteLn("D3D12: Failed to allocate DATE image, aborting draw."); Console.WriteLn("D3D12: Failed to allocate DATE image, aborting draw.");
@ -3851,35 +3900,45 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
} }
} }
// Align the render area to 128x128, hopefully avoiding render pass restarts for small render area changes (e.g. Ratchet and Clank). if (config.require_one_barrier)
const int render_area_alignment = 128 * GSConfig.UpscaleMultiplier; {
const GSVector2i rtsize(config.rt ? config.rt->GetSize() : config.ds->GetSize()); // requires a copy of the RT
const GSVector4i render_area( draw_rt_clone = static_cast<GSTexture12*>(CreateTexture(rtsize.x, rtsize.y, 1, hdr_rt ? GSTexture::Format::HDRColor : GSTexture::Format::Color, true));
config.ps.hdr ? config.drawarea : if (draw_rt_clone)
GSVector4i(Common::AlignDownPow2(config.scissor.left, render_area_alignment), {
Common::AlignDownPow2(config.scissor.top, render_area_alignment), EndRenderPass();
std::min(Common::AlignUpPow2(config.scissor.right, render_area_alignment), rtsize.x),
std::min(Common::AlignUpPow2(config.scissor.bottom, render_area_alignment), rtsize.y)));
GSTexture12* draw_rt = static_cast<GSTexture12*>(config.rt); GL_PUSH("Copy RT to temp texture for fbmask {%d,%d %dx%d}", config.drawarea.left, config.drawarea.top,
GSTexture12* draw_ds = static_cast<GSTexture12*>(config.ds); config.drawarea.width(), config.drawarea.height());
GSTexture12* draw_rt_clone = nullptr;
GSTexture12* hdr_rt = nullptr; draw_rt_clone->SetState(GSTexture::State::Invalidated);
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
PSSetShaderResource(2, draw_rt_clone, true);
}
}
// Switch to hdr target for colclip rendering // Switch to hdr target for colclip rendering
if (pipe.ps.hdr) if (pipe.ps.hdr)
{ {
if (!hdr_rt)
{
config.hdr_update_area = config.drawarea;
EndRenderPass(); EndRenderPass();
hdr_rt = static_cast<GSTexture12*>(CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor, false)); hdr_rt = static_cast<GSTexture12*>(CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor, false));
if (!hdr_rt) if (!hdr_rt)
{ {
Console.WriteLn("D3D12: Failed to allocate HDR render target, aborting draw."); Console.WriteLn("D3D12: Failed to allocate HDR render target, aborting draw.");
if (date_image) if (date_image)
Recycle(date_image); Recycle(date_image);
return; return;
} }
g_gs_device->SetHDRTexture(static_cast<GSTexture*>(hdr_rt));
// propagate clear value through if the hdr render is the first // propagate clear value through if the hdr render is the first
if (draw_rt->GetState() == GSTexture::State::Cleared) if (draw_rt->GetState() == GSTexture::State::Cleared)
{ {
@ -3895,28 +3954,13 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
// we're not drawing to the RT, so we can use it as a source // we're not drawing to the RT, so we can use it as a source
if (config.require_one_barrier) if (config.require_one_barrier)
PSSetShaderResource(2, draw_rt, true); PSSetShaderResource(2, draw_rt, true);
}
draw_rt = hdr_rt; draw_rt = hdr_rt;
} }
else if (config.require_one_barrier)
{
// requires a copy of the RT
draw_rt_clone = static_cast<GSTexture12*>(CreateTexture(rtsize.x, rtsize.y, 1, GSTexture::Format::Color, true));
if (draw_rt_clone)
{
EndRenderPass();
GL_PUSH("Copy RT to temp texture for fbmask {%d,%d %dx%d}", config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
draw_rt_clone->SetState(GSTexture::State::Invalidated);
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
PSSetShaderResource(2, draw_rt_clone, true);
}
}
// clear texture binding when it's bound to RT or DS // clear texture binding when it's bound to RT or DS
if (((config.rt && static_cast<GSTexture12*>(config.rt)->GetSRVDescriptor() == m_tfx_textures[0]) || if (((draw_rt && static_cast<GSTexture12*>(draw_rt)->GetSRVDescriptor() == m_tfx_textures[0]) ||
(config.ds && static_cast<GSTexture12*>(config.ds)->GetSRVDescriptor() == m_tfx_textures[0]))) (config.ds && static_cast<GSTexture12*>(config.ds)->GetSRVDescriptor() == m_tfx_textures[0])))
{ {
PSSetShaderResource(0, nullptr, false); PSSetShaderResource(0, nullptr, false);
@ -3964,13 +4008,14 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
} }
// rt -> hdr blit if enabled // rt -> hdr blit if enabled
if (hdr_rt && config.rt->GetState() == GSTexture::State::Dirty) if (hdr_rt && (config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve) && config.rt->GetState() == GSTexture::State::Dirty)
{ {
SetUtilityTexture(static_cast<GSTexture12*>(config.rt), m_point_sampler_cpu); SetUtilityTexture(static_cast<GSTexture12*>(config.rt), m_point_sampler_cpu);
SetPipeline(m_hdr_setup_pipelines[pipe.ds].get()); SetPipeline(m_hdr_setup_pipelines[pipe.ds].get());
const GSVector4 sRect(GSVector4(render_area) / GSVector4(rtsize.x, rtsize.y).xyxy()); const GSVector4 drawareaf = GSVector4((config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea);
DrawStretchRect(sRect, GSVector4(render_area), rtsize); const GSVector4 sRect(drawareaf / GSVector4(rtsize.x, rtsize.y).xyxy());
DrawStretchRect(sRect, GSVector4(drawareaf), rtsize);
g_perfmon.Put(GSPerfMon::TextureCopies, 1); g_perfmon.Put(GSPerfMon::TextureCopies, 1);
GL_POP(); GL_POP();
@ -4024,6 +4069,10 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
// now blit the hdr texture back to the original target // now blit the hdr texture back to the original target
if (hdr_rt) if (hdr_rt)
{
config.hdr_update_area = config.hdr_update_area.runion(config.drawarea);
if ((config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve))
{ {
GL_PUSH("Blit HDR back to RT"); GL_PUSH("Blit HDR back to RT");
@ -4040,13 +4089,15 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
draw_rt->GetUNormClearColor(), 0.0f, 0); draw_rt->GetUNormClearColor(), 0.0f, 0);
const GSVector4 sRect(GSVector4(render_area) / GSVector4(rtsize.x, rtsize.y).xyxy()); const GSVector4 sRect(GSVector4(config.hdr_update_area) / GSVector4(rtsize.x, rtsize.y).xyxy());
SetPipeline(m_hdr_finish_pipelines[pipe.ds].get()); SetPipeline(m_hdr_finish_pipelines[pipe.ds].get());
SetUtilityTexture(hdr_rt, m_point_sampler_cpu); SetUtilityTexture(hdr_rt, m_point_sampler_cpu);
DrawStretchRect(sRect, GSVector4(render_area), rtsize); DrawStretchRect(sRect, GSVector4(config.hdr_update_area), rtsize);
g_perfmon.Put(GSPerfMon::TextureCopies, 1); g_perfmon.Put(GSPerfMon::TextureCopies, 1);
Recycle(hdr_rt); Recycle(hdr_rt);
g_gs_device->SetHDRTexture(nullptr);
}
} }
} }

View File

@ -970,6 +970,21 @@ GSVector2i GSRendererHW::GetTargetSize(const GSTextureCache::Source* tex, const
return g_texture_cache->GetTargetSize(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, valid_size.x, valid_size.y, can_expand); return g_texture_cache->GetTargetSize(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, valid_size.x, valid_size.y, can_expand);
} }
bool GSRendererHW::NextDrawHDR() const
{
const int get_next_ctx = (m_state_flush_reason == CONTEXTCHANGE) ? m_env.PRIM.CTXT : m_backed_up_ctx;
const GSDrawingContext& next_ctx = m_env.CTXT[get_next_ctx];
// If it wasn't a context change we can't guarantee the next draw is going to be set up
if (m_state_flush_reason != GSFlushReason::CONTEXTCHANGE || m_env.COLCLAMP.CLAMP != 0 || m_env.PRIM.ABE == 0 ||
(m_context->FRAME.U64 ^ next_ctx.FRAME.U64) != 0 || (m_env.PRIM.TME && next_ctx.TEX0.TBP0 == m_context->FRAME.Block()))
{
return false;
}
return true;
}
bool GSRendererHW::IsPossibleChannelShuffle() const bool GSRendererHW::IsPossibleChannelShuffle() const
{ {
if (!PRIM->TME || m_cached_ctx.TEX0.PSM != PSMT8 || // 8-bit texture draw if (!PRIM->TME || m_cached_ctx.TEX0.PSM != PSMT8 || // 8-bit texture draw
@ -2150,6 +2165,35 @@ void GSRendererHW::Draw()
return; return;
} }
// I hate that I have to do this, but some games (like Pac-Man World Rally) troll us by causing a flush with degenerate triangles, so we don't have all available information about the next draw.
// So we have to check when the next draw happens if our frame has changed or if it's become recursive.
const bool has_HDR_texture = g_gs_device->GetHDRTexture() != nullptr;
if (!no_rt && has_HDR_texture && (m_conf.hdr_frame.FBP != m_cached_ctx.FRAME.FBP || m_conf.hdr_frame.Block() == m_cached_ctx.TEX0.TBP0))
{
GIFRegTEX0 FRAME;
FRAME.TBP0 = m_conf.hdr_frame.Block();
FRAME.TBW = m_conf.hdr_frame.FBW;
FRAME.PSM = m_conf.hdr_frame.PSM;
GSTextureCache::Target* old_rt = g_texture_cache->LookupTarget(FRAME, GSVector2i(1, 1), GetTextureScaleFactor(), GSTextureCache::RenderTarget, true,
fm, false, false, true, true, GSVector4i(0, 0, 1, 1), true, false, false);
if (old_rt)
{
GL_CACHE("Pre-draw resolve of HDR! Address: %x", FRAME.TBP0);
GSTexture* hdr_texture = g_gs_device->GetHDRTexture();
g_gs_device->StretchRect(hdr_texture, GSVector4(m_conf.hdr_update_area) / GSVector4(GSVector4i(hdr_texture->GetSize()).xyxy()), old_rt->m_texture, GSVector4(m_conf.hdr_update_area),
ShaderConvert::HDR_RESOLVE, false);
g_gs_device->Recycle(hdr_texture);
g_gs_device->SetHDRTexture(nullptr);
}
else
DevCon.Warning("Error resolving HDR texture for pre-draw resolve");
}
const bool draw_sprite_tex = PRIM->TME && (m_vt.m_primclass == GS_SPRITE_CLASS); const bool draw_sprite_tex = PRIM->TME && (m_vt.m_primclass == GS_SPRITE_CLASS);
// We trigger the sw prim render here super early, to avoid creating superfluous render targets. // We trigger the sw prim render here super early, to avoid creating superfluous render targets.
@ -3413,10 +3457,19 @@ void GSRendererHW::Draw()
GSTextureCache::RenderTarget, m_cached_ctx.ZBUF.Block(), m_cached_ctx.ZBUF.PSM, zm); GSTextureCache::RenderTarget, m_cached_ctx.ZBUF.Block(), m_cached_ctx.ZBUF.PSM, zm);
} }
// //
if (GSConfig.DumpGSData) if (GSConfig.DumpGSData)
{ {
const bool writeback_HDR_texture = g_gs_device->GetHDRTexture() != nullptr;
if (writeback_HDR_texture)
{
GSTexture* hdr_texture = g_gs_device->GetHDRTexture();
g_gs_device->StretchRect(hdr_texture, GSVector4(m_conf.hdr_update_area) / GSVector4(GSVector4i(hdr_texture->GetSize()).xyxy()), rt->m_texture, GSVector4(m_conf.hdr_update_area),
ShaderConvert::HDR_RESOLVE, false);
}
const u64 frame = g_perfmon.GetFrame(); const u64 frame = g_perfmon.GetFrame();
std::string s; std::string s;
@ -4404,14 +4457,36 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo
// Color clip // Color clip
if (COLCLAMP.CLAMP == 0) if (COLCLAMP.CLAMP == 0)
{ {
const bool free_colclip = features.framebuffer_fetch || no_prim_overlap || blend_non_recursive; bool has_HDR_texture = g_gs_device->GetHDRTexture() != nullptr;
// Don't know any game that resizes the RT mid HDR, but gotta be careful.
if (has_HDR_texture)
{
GSTexture* hdr_texture = g_gs_device->GetHDRTexture();
if (hdr_texture->GetSize() != rt->m_texture->GetSize())
{
GL_CACHE("Pre-Blend resolve of HDR due to size change! Address: %x", rt->m_TEX0.TBP0);
g_gs_device->StretchRect(hdr_texture, GSVector4(m_conf.hdr_update_area) / GSVector4(GSVector4i(hdr_texture->GetSize()).xyxy()), rt->m_texture, GSVector4(m_conf.hdr_update_area),
ShaderConvert::HDR_RESOLVE, false);
g_gs_device->Recycle(hdr_texture);
g_gs_device->SetHDRTexture(nullptr);
has_HDR_texture = false;
}
}
const bool free_colclip = !has_HDR_texture && (features.framebuffer_fetch || no_prim_overlap || blend_non_recursive);
GL_DBG("COLCLIP Info (Blending: %u/%u/%u/%u, OVERLAP: %d)", m_conf.ps.blend_a, m_conf.ps.blend_b, m_conf.ps.blend_c, m_conf.ps.blend_d, m_prim_overlap); GL_DBG("COLCLIP Info (Blending: %u/%u/%u/%u, OVERLAP: %d)", m_conf.ps.blend_a, m_conf.ps.blend_b, m_conf.ps.blend_c, m_conf.ps.blend_d, m_prim_overlap);
if (color_dest_blend || color_dest_blend2 || blend_zero_to_one_range) if (color_dest_blend || color_dest_blend2 || blend_zero_to_one_range)
{ {
// No overflow, disable colclip. // No overflow, disable colclip.
GL_INS("COLCLIP mode DISABLED"); GL_INS("COLCLIP mode DISABLED");
sw_blending = false; sw_blending = false;
m_conf.hdr_mode = (has_HDR_texture && !NextDrawHDR()) ? GSHWDrawConfig::HDRMode::ResolveOnly : GSHWDrawConfig::HDRMode::NoModify;
} }
else if (free_colclip) else if (free_colclip)
{ {
@ -4422,6 +4497,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo
// Disable the HDR algo // Disable the HDR algo
accumulation_blend = false; accumulation_blend = false;
blend_mix = false; blend_mix = false;
m_conf.hdr_mode = (has_HDR_texture && !NextDrawHDR()) ? GSHWDrawConfig::HDRMode::ResolveOnly : GSHWDrawConfig::HDRMode::NoModify;
} }
else if (accumulation_blend) else if (accumulation_blend)
{ {
@ -4429,18 +4505,24 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo
GL_INS("COLCLIP Fast HDR mode ENABLED"); GL_INS("COLCLIP Fast HDR mode ENABLED");
m_conf.ps.hdr = 1; m_conf.ps.hdr = 1;
sw_blending = true; // Enable sw blending for the HDR algo sw_blending = true; // Enable sw blending for the HDR algo
m_conf.hdr_mode = has_HDR_texture ? (NextDrawHDR() ? GSHWDrawConfig::HDRMode::NoModify : GSHWDrawConfig::HDRMode::ResolveOnly) : (NextDrawHDR() ? GSHWDrawConfig::HDRMode::ConvertOnly : GSHWDrawConfig::HDRMode::ConvertAndResolve);
} }
else if (sw_blending) else if (sw_blending)
{ {
// A slow algo that could requires several passes (barely used) // A slow algo that could requires several passes (barely used)
GL_INS("COLCLIP SW mode ENABLED"); GL_INS("COLCLIP SW mode ENABLED");
m_conf.ps.colclip = 1; m_conf.ps.colclip = 1;
m_conf.hdr_mode = (has_HDR_texture && !NextDrawHDR()) ? GSHWDrawConfig::HDRMode::ResolveOnly : GSHWDrawConfig::HDRMode::NoModify;
} }
else else
{ {
GL_INS("COLCLIP HDR mode ENABLED"); GL_INS("COLCLIP HDR mode ENABLED");
m_conf.ps.hdr = 1; m_conf.ps.hdr = 1;
m_conf.hdr_mode = has_HDR_texture ? (NextDrawHDR() ? GSHWDrawConfig::HDRMode::NoModify : GSHWDrawConfig::HDRMode::ResolveOnly) : (NextDrawHDR() ? GSHWDrawConfig::HDRMode::ConvertOnly : GSHWDrawConfig::HDRMode::ConvertAndResolve);
} }
m_conf.hdr_frame = m_cached_ctx.FRAME;
} }
// Per pixel alpha blending // Per pixel alpha blending
@ -4474,8 +4556,10 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo
// HDR mode should be disabled when doing sw blend, swap with sw colclip. // HDR mode should be disabled when doing sw blend, swap with sw colclip.
if (m_conf.ps.hdr) if (m_conf.ps.hdr)
{ {
bool has_HDR_texture = g_gs_device->GetHDRTexture() != nullptr;
m_conf.ps.hdr = 0; m_conf.ps.hdr = 0;
m_conf.ps.colclip = 1; m_conf.ps.colclip = 1;
m_conf.hdr_mode = has_HDR_texture ? GSHWDrawConfig::HDRMode::EarlyResolve : GSHWDrawConfig::HDRMode::NoModify;
} }
} }
else else

View File

@ -111,7 +111,7 @@ private:
void EmulateATST(float& AREF, GSHWDrawConfig::PSSelector& ps, bool pass_2); void EmulateATST(float& AREF, GSHWDrawConfig::PSSelector& ps, bool pass_2);
void SetTCOffset(); void SetTCOffset();
bool NextDrawHDR() const;
bool IsPossibleChannelShuffle() const; bool IsPossibleChannelShuffle() const;
bool NextDrawMatchesShuffle() const; bool NextDrawMatchesShuffle() const;
bool IsSplitTextureShuffle(GSTextureCache::Target* rt); bool IsSplitTextureShuffle(GSTextureCache::Target* rt);

View File

@ -2135,6 +2135,64 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
GSTexture* stencil = nullptr; GSTexture* stencil = nullptr;
GSTexture* primid_tex = nullptr; GSTexture* primid_tex = nullptr;
GSTexture* rt = config.rt; GSTexture* rt = config.rt;
GSTexture* hdr_rt = g_gs_device->GetHDRTexture();
if (hdr_rt)
{
if (config.hdr_mode == GSHWDrawConfig::HDRMode::EarlyResolve)
{
BeginRenderPass(@"HDR Resolve", config.rt, MTLLoadActionLoad, nullptr, MTLLoadActionDontCare);
RenderCopy(hdr_rt, m_hdr_resolve_pipeline, config.hdr_update_area);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
Recycle(hdr_rt);
g_gs_device->SetHDRTexture(nullptr);
hdr_rt = nullptr;
}
else
config.ps.hdr = 1;
}
if (config.ps.hdr)
{
if (!hdr_rt)
{
config.hdr_update_area = config.drawarea;
GSVector2i size = config.rt->GetSize();
rt = hdr_rt = CreateRenderTarget(size.x, size.y, GSTexture::Format::HDRColor, false);
g_gs_device->SetHDRTexture(hdr_rt);
const GSVector4i copy_rect = (config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly) ? GSVector4i::loadh(size) : config.drawarea;
switch (config.rt->GetState())
{
case GSTexture::State::Dirty:
BeginRenderPass(@"HDR Init", hdr_rt, MTLLoadActionDontCare, nullptr, MTLLoadActionDontCare);
RenderCopy(config.rt, m_hdr_init_pipeline, copy_rect);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
break;
case GSTexture::State::Cleared:
{
BeginRenderPass(@"HDR Clear", hdr_rt, MTLLoadActionDontCare, nullptr, MTLLoadActionDontCare);
GSVector4 color = GSVector4::rgba32(config.rt->GetClearColor()) / GSVector4::cxpr(65535, 65535, 65535, 255);
[m_current_render.encoder setFragmentBytes:&color length:sizeof(color) atIndex:GSMTLBufferIndexUniforms];
RenderCopy(nullptr, m_hdr_clear_pipeline, copy_rect);
break;
}
case GSTexture::State::Invalidated:
break;
}
}
rt = hdr_rt;
}
switch (config.destination_alpha) switch (config.destination_alpha)
{ {
case GSHWDrawConfig::DestinationAlphaMode::Off: case GSHWDrawConfig::DestinationAlphaMode::Off:
@ -2142,18 +2200,18 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
break; // No setup break; // No setup
case GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking: case GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking:
{ {
FlushClears(config.rt); FlushClears(rt);
GSVector2i size = config.rt->GetSize(); GSVector2i size = rt->GetSize();
primid_tex = CreateRenderTarget(size.x, size.y, GSTexture::Format::PrimID); primid_tex = CreateRenderTarget(size.x, size.y, GSTexture::Format::PrimID);
DepthStencilSelector dsel = config.depth; DepthStencilSelector dsel = config.depth;
dsel.zwe = 0; dsel.zwe = 0;
GSTexture* depth = dsel.key == DepthStencilSelector::NoDepth().key ? nullptr : config.ds; GSTexture* depth = dsel.key == DepthStencilSelector::NoDepth().key ? nullptr : config.ds;
BeginRenderPass(@"PrimID Destination Alpha Init", primid_tex, MTLLoadActionDontCare, depth, MTLLoadActionLoad); BeginRenderPass(@"PrimID Destination Alpha Init", primid_tex, MTLLoadActionDontCare, depth, MTLLoadActionLoad);
RenderCopy(config.rt, m_primid_init_pipeline[static_cast<bool>(depth)][static_cast<u8>(config.datm)], config.drawarea); RenderCopy(rt, m_primid_init_pipeline[static_cast<bool>(depth)][static_cast<u8>(config.datm)], config.drawarea);
MRESetDSS(dsel); MRESetDSS(dsel);
pxAssert(config.ps.date == 1 || config.ps.date == 2); pxAssert(config.ps.date == 1 || config.ps.date == 2);
if (config.ps.tex_is_fb) if (config.ps.tex_is_fb)
MRESetTexture(config.rt, GSMTLTextureIndexRenderTarget); MRESetTexture(rt, GSMTLTextureIndexRenderTarget);
config.require_one_barrier = false; // Ending render pass is our barrier config.require_one_barrier = false; // Ending render pass is our barrier
pxAssert(config.require_full_barrier == false && config.drawlist == nullptr); pxAssert(config.require_full_barrier == false && config.drawlist == nullptr);
MRESetHWPipelineState(config.vs, config.ps, {}, {}); MRESetHWPipelineState(config.vs, config.ps, {}, {});
@ -2170,38 +2228,11 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
stencil = config.ds; stencil = config.ds;
break; break;
case GSHWDrawConfig::DestinationAlphaMode::Stencil: case GSHWDrawConfig::DestinationAlphaMode::Stencil:
SetupDestinationAlpha(config.rt, config.ds, config.drawarea, config.datm); SetupDestinationAlpha(rt, config.ds, config.drawarea, config.datm);
stencil = config.ds; stencil = config.ds;
break; break;
} }
GSTexture* hdr_rt = nullptr;
if (config.ps.hdr)
{
GSVector2i size = config.rt->GetSize();
rt = hdr_rt = CreateRenderTarget(size.x, size.y, GSTexture::Format::HDRColor, false);
switch (config.rt->GetState())
{
case GSTexture::State::Dirty:
BeginRenderPass(@"HDR Init", hdr_rt, MTLLoadActionDontCare, nullptr, MTLLoadActionDontCare);
RenderCopy(config.rt, m_hdr_init_pipeline, config.drawarea);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
break;
case GSTexture::State::Cleared:
{
BeginRenderPass(@"HDR Clear", hdr_rt, MTLLoadActionDontCare, nullptr, MTLLoadActionDontCare);
GSVector4 color = GSVector4::rgba32(config.rt->GetClearColor()) / GSVector4::cxpr(65535, 65535, 65535, 255);
[m_current_render.encoder setFragmentBytes:&color length:sizeof(color) atIndex:GSMTLBufferIndexUniforms];
RenderCopy(nullptr, m_hdr_clear_pipeline, config.drawarea);
break;
}
case GSTexture::State::Invalidated:
break;
}
}
// Try to reduce render pass restarts // Try to reduce render pass restarts
if (!config.ds && m_current_render.color_target == rt && stencil == m_current_render.stencil_target && m_current_render.depth_target != config.tex) if (!config.ds && m_current_render.color_target == rt && stencil == m_current_render.stencil_target && m_current_render.depth_target != config.tex)
config.ds = m_current_render.depth_target; config.ds = m_current_render.depth_target;
@ -2224,7 +2255,7 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
[mtlenc setStencilReferenceValue:1]; [mtlenc setStencilReferenceValue:1];
MREInitHWDraw(config, allocation); MREInitHWDraw(config, allocation);
if (config.require_one_barrier || config.require_full_barrier) if (config.require_one_barrier || config.require_full_barrier)
MRESetTexture(config.rt, GSMTLTextureIndexRenderTarget); MRESetTexture(rt, GSMTLTextureIndexRenderTarget);
if (primid_tex) if (primid_tex)
MRESetTexture(primid_tex, GSMTLTextureIndexPrimIDs); MRESetTexture(primid_tex, GSMTLTextureIndexPrimIDs);
if (config.blend.constant_enable) if (config.blend.constant_enable)
@ -2247,12 +2278,19 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
} }
if (hdr_rt) if (hdr_rt)
{
config.hdr_update_area = config.hdr_update_area.runion(config.drawarea);
if ((config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve))
{ {
BeginRenderPass(@"HDR Resolve", config.rt, MTLLoadActionLoad, nullptr, MTLLoadActionDontCare); BeginRenderPass(@"HDR Resolve", config.rt, MTLLoadActionLoad, nullptr, MTLLoadActionDontCare);
RenderCopy(hdr_rt, m_hdr_resolve_pipeline, config.drawarea); RenderCopy(hdr_rt, m_hdr_resolve_pipeline, config.hdr_update_area);
g_perfmon.Put(GSPerfMon::TextureCopies, 1); g_perfmon.Put(GSPerfMon::TextureCopies, 1);
Recycle(hdr_rt); Recycle(hdr_rt);
g_gs_device->SetHDRTexture(nullptr);
}
} }
if (primid_tex) if (primid_tex)

View File

@ -856,7 +856,10 @@ struct PSMain
void ps_fbmask(thread float4& C) void ps_fbmask(thread float4& C)
{ {
if (PS_FBMASK) if (PS_FBMASK)
C = float4((uint4(int4(C)) & (cb.fbmask ^ 0xff)) | (uint4(current_color * 255.5) & cb.fbmask)); {
float multi = PS_HDR ? 65535.0 : 255.5;
C = float4((uint4(int4(C)) & (cb.fbmask ^ 0xff)) | (uint4(current_color * float4(multi, multi, multi, 255)) & cb.fbmask));
}
} }
void ps_dither(thread float4& C, float As) void ps_dither(thread float4& C, float As)
@ -956,8 +959,8 @@ struct PSMain
current_color.a = float(denorm_rt.g & 0x80); current_color.a = float(denorm_rt.g & 0x80);
} }
} }
float multi = PS_HDR ? 65535.0 : 255.5;
float3 Cd = trunc(current_color.rgb * 255.5f); float3 Cd = trunc(current_color.rgb * multi);
float3 Cs = Color.rgb; float3 Cs = Color.rgb;
float3 A = pick(PS_BLEND_A, Cs, Cd, float3(0.f)); float3 A = pick(PS_BLEND_A, Cs, Cd, float3(0.f));

View File

@ -2414,6 +2414,45 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize(); GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize();
GSTexture* primid_texture = nullptr; GSTexture* primid_texture = nullptr;
GSTexture* hdr_rt = g_gs_device->GetHDRTexture();
if (hdr_rt)
{
if (config.hdr_mode == GSHWDrawConfig::HDRMode::EarlyResolve)
{
const GSVector2i size = config.rt->GetSize();
const GSVector4 dRect(config.hdr_update_area);
const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy();
StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false);
Recycle(hdr_rt);
g_gs_device->SetHDRTexture(nullptr);
hdr_rt = nullptr;
}
else
{
config.ps.hdr = 1;
}
}
if (config.ps.hdr)
{
if (!hdr_rt)
{
config.hdr_update_area = config.drawarea;
hdr_rt = CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor, false);
OMSetRenderTargets(hdr_rt, config.ds, nullptr);
g_gs_device->SetHDRTexture(hdr_rt);
const GSVector4 dRect = GSVector4((config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea);
const GSVector4 sRect = dRect / GSVector4(rtsize.x, rtsize.y).xyxy();
StretchRect(config.rt, sRect, hdr_rt, dRect, ShaderConvert::HDR_INIT, false);
}
}
// Destination Alpha Setup // Destination Alpha Setup
switch (config.destination_alpha) switch (config.destination_alpha)
@ -2422,7 +2461,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
case GSHWDrawConfig::DestinationAlphaMode::Full: case GSHWDrawConfig::DestinationAlphaMode::Full:
break; // No setup break; // No setup
case GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking: case GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking:
primid_texture = InitPrimDateTexture(config.rt, config.drawarea, config.datm); primid_texture = InitPrimDateTexture(hdr_rt ? hdr_rt : config.rt, config.drawarea, config.datm);
break; break;
case GSHWDrawConfig::DestinationAlphaMode::StencilOne: case GSHWDrawConfig::DestinationAlphaMode::StencilOne:
if (m_features.texture_barrier) if (m_features.texture_barrier)
@ -2442,29 +2481,20 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
{GSVector4(dst.x, dst.w, 0.0f, 0.0f), GSVector2(src.x, src.w)}, {GSVector4(dst.x, dst.w, 0.0f, 0.0f), GSVector2(src.x, src.w)},
{GSVector4(dst.z, dst.w, 0.0f, 0.0f), GSVector2(src.z, src.w)}, {GSVector4(dst.z, dst.w, 0.0f, 0.0f), GSVector2(src.z, src.w)},
}; };
SetupDATE(config.rt, config.ds, vertices, config.datm); SetupDATE(hdr_rt ? hdr_rt : config.rt, config.ds, vertices, config.datm);
} }
} }
GSTexture* hdr_rt = nullptr;
GSTexture* draw_rt_clone = nullptr; GSTexture* draw_rt_clone = nullptr;
if (config.ps.hdr)
{
hdr_rt = CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor, false);
OMSetRenderTargets(hdr_rt, config.ds, &config.scissor);
GSVector4 dRect(config.drawarea); if (config.require_one_barrier && !m_features.texture_barrier)
const GSVector4 sRect = dRect / GSVector4(rtsize.x, rtsize.y).xyxy();
StretchRect(config.rt, sRect, hdr_rt, dRect, ShaderConvert::HDR_INIT, false);
}
else if (config.require_one_barrier && !m_features.texture_barrier)
{ {
// Requires a copy of the RT // Requires a copy of the RT
draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, GSTexture::Format::Color, true); draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, hdr_rt ? GSTexture::Format::HDRColor : GSTexture::Format::Color, true);
GL_PUSH("Copy RT to temp texture for fbmask {%d,%d %dx%d}", GL_PUSH("Copy RT to temp texture for fbmask {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top, config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height()); config.drawarea.width(), config.drawarea.height());
CopyRect(config.rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top); CopyRect(hdr_rt ? hdr_rt : config.rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
} }
else if (config.tex && config.tex == config.ds) else if (config.tex && config.tex == config.ds)
{ {
@ -2510,7 +2540,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
if (draw_rt_clone) if (draw_rt_clone)
PSSetShaderResource(2, draw_rt_clone); PSSetShaderResource(2, draw_rt_clone);
else if (config.require_one_barrier || config.require_full_barrier) else if (config.require_one_barrier || config.require_full_barrier)
PSSetShaderResource(2, config.rt); PSSetShaderResource(2, hdr_rt ? hdr_rt : config.rt);
SetupSampler(config.sampler); SetupSampler(config.sampler);
@ -2669,12 +2699,19 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
if (hdr_rt) if (hdr_rt)
{ {
GSVector2i size = config.rt->GetSize(); config.hdr_update_area = config.hdr_update_area.runion(config.drawarea);
GSVector4 dRect(config.drawarea);
if ((config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve))
{
const GSVector2i size = config.rt->GetSize();
const GSVector4 dRect(config.hdr_update_area);
const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy(); const GSVector4 sRect = dRect / GSVector4(size.x, size.y).xyxy();
StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false); StretchRect(hdr_rt, sRect, config.rt, dRect, ShaderConvert::HDR_RESOLVE, false);
Recycle(hdr_rt); Recycle(hdr_rt);
g_gs_device->SetHDRTexture(nullptr);
}
} }
} }

View File

@ -9,6 +9,7 @@
#include "GS/Renderers/Vulkan/VKBuilders.h" #include "GS/Renderers/Vulkan/VKBuilders.h"
#include "GS/Renderers/Vulkan/VKShaderCache.h" #include "GS/Renderers/Vulkan/VKShaderCache.h"
#include "GS/Renderers/Vulkan/VKSwapChain.h" #include "GS/Renderers/Vulkan/VKSwapChain.h"
#include "GS/Renderers/Common/GSDevice.h"
#include "BuildVersion.h" #include "BuildVersion.h"
#include "Host.h" #include "Host.h"
@ -5589,28 +5590,12 @@ GSTextureVK* GSDeviceVK::SetupPrimitiveTrackingDATE(GSHWDrawConfig& config)
void GSDeviceVK::RenderHW(GSHWDrawConfig& config) void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
{ {
// Destination Alpha Setup
switch (config.destination_alpha)
{
case GSHWDrawConfig::DestinationAlphaMode::Off: // No setup
case GSHWDrawConfig::DestinationAlphaMode::Full: // No setup
case GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking: // Setup is done below
break;
case GSHWDrawConfig::DestinationAlphaMode::StencilOne: // setup is done below
{
// we only need to do the setup here if we don't have barriers, in which case do full DATE.
if (!m_features.texture_barrier)
{
SetupDATE(config.rt, config.ds, config.datm, config.drawarea);
config.destination_alpha = GSHWDrawConfig::DestinationAlphaMode::Stencil;
}
}
break;
case GSHWDrawConfig::DestinationAlphaMode::Stencil: const GSVector2i rtsize(config.rt ? config.rt->GetSize() : config.ds->GetSize());
SetupDATE(config.rt, config.ds, config.datm, config.drawarea); GSTextureVK* draw_rt = static_cast<GSTextureVK*>(config.rt);
break; GSTextureVK* draw_ds = static_cast<GSTextureVK*>(config.ds);
} GSTextureVK* draw_rt_clone = nullptr;
GSTextureVK* hdr_rt = static_cast<GSTextureVK*>(g_gs_device->GetHDRTexture());
// stream buffer in first, in case we need to exec // stream buffer in first, in case we need to exec
SetVSConstantBuffer(config.cb_vs); SetVSConstantBuffer(config.cb_vs);
@ -5632,41 +5617,143 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
SetLineWidth(config.line_expand ? config.cb_ps.ScaleFactor.z : 1.0f); SetLineWidth(config.line_expand ? config.cb_ps.ScaleFactor.z : 1.0f);
// Primitive ID tracking DATE setup. // Primitive ID tracking DATE setup.
// Needs to be done before
GSTextureVK* date_image = nullptr; GSTextureVK* date_image = nullptr;
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking) if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking)
{ {
// If we have a HDR in progress, we need to use the HDR texture, but we can't check this later as there's a chicken/egg problem with the pipe setup.
GSTexture* backup_rt = config.rt;
if(hdr_rt)
config.rt = hdr_rt;
date_image = SetupPrimitiveTrackingDATE(config); date_image = SetupPrimitiveTrackingDATE(config);
if (!date_image) if (!date_image)
{ {
Console.WriteLn("Failed to allocate DATE image, aborting draw."); Console.WriteLn("Failed to allocate DATE image, aborting draw.");
return; return;
} }
config.rt = backup_rt;
} }
// figure out the pipeline // figure out the pipeline
PipelineSelector& pipe = m_pipeline_selector; PipelineSelector& pipe = m_pipeline_selector;
UpdateHWPipelineSelector(config, pipe); UpdateHWPipelineSelector(config, pipe);
const GSVector2i rtsize(config.rt ? config.rt->GetSize() : config.ds->GetSize()); // now blit the hdr texture back to the original target
GSTextureVK* draw_rt = static_cast<GSTextureVK*>(config.rt); if (hdr_rt)
GSTextureVK* draw_ds = static_cast<GSTextureVK*>(config.ds); {
GSTextureVK* draw_rt_clone = nullptr; if (config.hdr_mode == GSHWDrawConfig::HDRMode::EarlyResolve)
GSTextureVK* hdr_rt = nullptr; {
GL_PUSH("Blit HDR back to RT");
EndRenderPass();
hdr_rt->TransitionToLayout(GSTextureVK::Layout::ShaderReadOnly);
draw_rt = static_cast<GSTextureVK*>(config.rt);
OMSetRenderTargets(draw_rt, draw_ds, GSVector4i::loadh(rtsize), static_cast<FeedbackLoopFlag>(pipe.feedback_loop_flags));
// if this target was cleared and never drawn to, perform the clear as part of the resolve here.
if (draw_rt->GetState() == GSTexture::State::Cleared)
{
alignas(16) VkClearValue cvs[2];
u32 cv_count = 0;
GSVector4::store<true>(&cvs[cv_count++].color, draw_rt->GetUNormClearColor());
if (draw_ds)
cvs[cv_count++].depthStencil = {draw_ds->GetClearDepth(), 1};
BeginClearRenderPass(GetTFXRenderPass(true, pipe.ds, false, false, pipe.IsRTFeedbackLoop(),
pipe.IsTestingAndSamplingDepth(), VK_ATTACHMENT_LOAD_OP_CLEAR,
pipe.ds ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE),
draw_rt->GetRect(), cvs, cv_count);
draw_rt->SetState(GSTexture::State::Dirty);
}
else
{
BeginRenderPass(GetTFXRenderPass(true, pipe.ds, false, false, pipe.IsRTFeedbackLoop(),
pipe.IsTestingAndSamplingDepth(), VK_ATTACHMENT_LOAD_OP_LOAD,
pipe.ds ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE),
draw_rt->GetRect());
}
const GSVector4 drawareaf = GSVector4(config.hdr_update_area);
const GSVector4 sRect(drawareaf / GSVector4(rtsize).xyxy());
SetPipeline(m_hdr_finish_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]);
SetUtilityTexture(hdr_rt, m_point_sampler);
DrawStretchRect(sRect, drawareaf, rtsize);
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
Recycle(hdr_rt);
g_gs_device->SetHDRTexture(nullptr);
hdr_rt = nullptr;
}
else
{
pipe.ps.hdr = 1;
draw_rt = hdr_rt;
}
}
// Destination Alpha Setup
switch (config.destination_alpha)
{
case GSHWDrawConfig::DestinationAlphaMode::Off: // No setup
case GSHWDrawConfig::DestinationAlphaMode::Full: // No setup
case GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking: // Setup is done below
break;
case GSHWDrawConfig::DestinationAlphaMode::StencilOne: // setup is done below
{
// we only need to do the setup here if we don't have barriers, in which case do full DATE.
if (!m_features.texture_barrier)
{
SetupDATE(draw_rt, config.ds, config.datm, config.drawarea);
config.destination_alpha = GSHWDrawConfig::DestinationAlphaMode::Stencil;
}
}
break;
case GSHWDrawConfig::DestinationAlphaMode::Stencil:
SetupDATE(draw_rt, config.ds, config.datm, config.drawarea);
break;
}
if (config.require_one_barrier && !m_features.texture_barrier)
{
// requires a copy of the RT
draw_rt_clone = static_cast<GSTextureVK*>(CreateTexture(rtsize.x, rtsize.y, 1, hdr_rt ? GSTexture::Format::HDRColor : GSTexture::Format::Color, true));
if (draw_rt_clone)
{
EndRenderPass();
GL_PUSH("Copy RT to temp texture for fbmask {%d,%d %dx%d}", config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
PSSetShaderResource(2, draw_rt_clone, true);
}
}
// Switch to hdr target for colclip rendering // Switch to hdr target for colclip rendering
if (pipe.ps.hdr) if (pipe.ps.hdr)
{ {
if (!hdr_rt)
{
config.hdr_update_area = config.drawarea;
EndRenderPass(); EndRenderPass();
hdr_rt = static_cast<GSTextureVK*>(CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor, false)); hdr_rt = static_cast<GSTextureVK*>(CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::HDRColor, false));
if (!hdr_rt) if (!hdr_rt)
{ {
Console.WriteLn("Failed to allocate HDR render target, aborting draw."); Console.WriteLn("Failed to allocate HDR render target, aborting draw.");
if (date_image) if (date_image)
Recycle(date_image); Recycle(date_image);
GL_POP(); GL_POP();
return; return;
} }
g_gs_device->SetHDRTexture(static_cast<GSTexture*>(hdr_rt));
// propagate clear value through if the hdr render is the first // propagate clear value through if the hdr render is the first
if (draw_rt->GetState() == GSTexture::State::Cleared) if (draw_rt->GetState() == GSTexture::State::Cleared)
@ -5687,24 +5774,9 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
// we're not drawing to the RT, so we can use it as a source // we're not drawing to the RT, so we can use it as a source
if (config.require_one_barrier && !m_features.texture_barrier) if (config.require_one_barrier && !m_features.texture_barrier)
PSSetShaderResource(2, draw_rt, true); PSSetShaderResource(2, draw_rt, true);
}
draw_rt = hdr_rt; draw_rt = hdr_rt;
} }
else if (config.require_one_barrier && !m_features.texture_barrier)
{
// requires a copy of the RT
draw_rt_clone = static_cast<GSTextureVK*>(CreateTexture(rtsize.x, rtsize.y, 1, GSTexture::Format::Color, true));
if (draw_rt_clone)
{
EndRenderPass();
GL_PUSH("Copy RT to temp texture for fbmask {%d,%d %dx%d}", config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
PSSetShaderResource(2, draw_rt_clone, true);
}
}
// clear texture binding when it's bound to RT or DS. // clear texture binding when it's bound to RT or DS.
if (!config.tex && ((config.rt && static_cast<GSTextureVK*>(config.rt) == m_tfx_textures[0]) || if (!config.tex && ((config.rt && static_cast<GSTextureVK*>(config.rt) == m_tfx_textures[0]) ||
@ -5714,7 +5786,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
} }
// render pass restart optimizations // render pass restart optimizations
if (hdr_rt) if (hdr_rt && (config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly))
{ {
// HDR requires blitting. // HDR requires blitting.
EndRenderPass(); EndRenderPass();
@ -5774,7 +5846,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
// Only draw to the active area of the HDR target. Except when depth is cleared, we need to use the full // Only draw to the active area of the HDR target. Except when depth is cleared, we need to use the full
// buffer size, otherwise it'll only clear the draw part of the depth buffer. // buffer size, otherwise it'll only clear the draw part of the depth buffer.
const GSVector4i render_area = (pipe.ps.hdr && ds_op != VK_ATTACHMENT_LOAD_OP_CLEAR) ? config.drawarea : const GSVector4i render_area = (pipe.ps.hdr && (config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve) && ds_op != VK_ATTACHMENT_LOAD_OP_CLEAR) ? config.drawarea :
GSVector4i::loadh(rtsize); GSVector4i::loadh(rtsize);
if (is_clearing_rt) if (is_clearing_rt)
@ -5813,17 +5885,19 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
} }
// rt -> hdr blit if enabled // rt -> hdr blit if enabled
if (hdr_rt && config.rt->GetState() == GSTexture::State::Dirty) if (hdr_rt && (config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve) && config.rt->GetState() == GSTexture::State::Dirty)
{ {
OMSetRenderTargets(draw_rt, draw_ds, GSVector4i::loadh(rtsize), static_cast<FeedbackLoopFlag>(pipe.feedback_loop_flags));
SetUtilityTexture(static_cast<GSTextureVK*>(config.rt), m_point_sampler); SetUtilityTexture(static_cast<GSTextureVK*>(config.rt), m_point_sampler);
SetPipeline(m_hdr_setup_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]); SetPipeline(m_hdr_setup_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]);
const GSVector4 drawareaf = GSVector4(config.drawarea); const GSVector4 drawareaf = GSVector4((config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertOnly) ? GSVector4i::loadh(rtsize) : config.drawarea);
const GSVector4 sRect(drawareaf / GSVector4(rtsize).xyxy()); const GSVector4 sRect(drawareaf / GSVector4(rtsize).xyxy());
DrawStretchRect(sRect, drawareaf, rtsize); DrawStretchRect(sRect, drawareaf, rtsize);
g_perfmon.Put(GSPerfMon::TextureCopies, 1); g_perfmon.Put(GSPerfMon::TextureCopies, 1);
GL_POP(); GL_POP();
OMSetRenderTargets(draw_rt, draw_ds, config.scissor, static_cast<FeedbackLoopFlag>(pipe.feedback_loop_flags));
} }
// VB/IB upload, if we did DATE setup and it's not HDR this has already been done // VB/IB upload, if we did DATE setup and it's not HDR this has already been done
@ -5879,14 +5953,19 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
// now blit the hdr texture back to the original target // now blit the hdr texture back to the original target
if (hdr_rt) if (hdr_rt)
{
config.hdr_update_area = config.hdr_update_area.runion(config.drawarea);
if ((config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly || config.hdr_mode == GSHWDrawConfig::HDRMode::ConvertAndResolve))
{ {
GL_PUSH("Blit HDR back to RT"); GL_PUSH("Blit HDR back to RT");
EndRenderPass(); EndRenderPass();
hdr_rt->TransitionToLayout(GSTextureVK::Layout::ShaderReadOnly); hdr_rt->TransitionToLayout(GSTextureVK::Layout::ShaderReadOnly);
draw_rt = static_cast<GSTextureVK*>(config.rt); draw_rt = static_cast<GSTextureVK*>(config.rt);
OMSetRenderTargets(draw_rt, draw_ds, config.scissor, static_cast<FeedbackLoopFlag>(pipe.feedback_loop_flags)); OMSetRenderTargets(draw_rt, draw_ds, (config.hdr_mode == GSHWDrawConfig::HDRMode::ResolveOnly) ? GSVector4i::loadh(rtsize) : config.scissor, static_cast<FeedbackLoopFlag>(pipe.feedback_loop_flags));
// if this target was cleared and never drawn to, perform the clear as part of the resolve here. // if this target was cleared and never drawn to, perform the clear as part of the resolve here.
if (draw_rt->GetState() == GSTexture::State::Cleared) if (draw_rt->GetState() == GSTexture::State::Cleared)
@ -5911,7 +5990,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
draw_rt->GetRect()); draw_rt->GetRect());
} }
const GSVector4 drawareaf = GSVector4(config.drawarea); const GSVector4 drawareaf = GSVector4(config.hdr_update_area);
const GSVector4 sRect(drawareaf / GSVector4(rtsize).xyxy()); const GSVector4 sRect(drawareaf / GSVector4(rtsize).xyxy());
SetPipeline(m_hdr_finish_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]); SetPipeline(m_hdr_finish_pipelines[pipe.ds][pipe.IsRTFeedbackLoop()]);
SetUtilityTexture(hdr_rt, m_point_sampler); SetUtilityTexture(hdr_rt, m_point_sampler);
@ -5919,9 +5998,13 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
g_perfmon.Put(GSPerfMon::TextureCopies, 1); g_perfmon.Put(GSPerfMon::TextureCopies, 1);
Recycle(hdr_rt); Recycle(hdr_rt);
g_gs_device->SetHDRTexture(nullptr);
} }
} }
config.hdr_mode = GSHWDrawConfig::HDRMode::NoModify;
}
void GSDeviceVK::UpdateHWPipelineSelector(GSHWDrawConfig& config, PipelineSelector& pipe) void GSDeviceVK::UpdateHWPipelineSelector(GSHWDrawConfig& config, PipelineSelector& pipe)
{ {
pipe.vs.key = config.vs.key; pipe.vs.key = config.vs.key;