2019-12-05 04:48:57 +00:00
|
|
|
// Copyright 2019 Dolphin Emulator Project
|
2021-07-05 01:22:19 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2019-12-05 04:48:57 +00:00
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
#include "VideoCommon/FramebufferShaderGen.h"
|
2019-12-05 04:48:57 +00:00
|
|
|
|
2019-12-05 04:58:38 +00:00
|
|
|
#include <string_view>
|
2019-12-05 04:48:57 +00:00
|
|
|
|
2020-03-23 06:35:01 +00:00
|
|
|
#include "Common/Logging/Log.h"
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
#include "VideoCommon/FramebufferManager.h"
|
2020-10-19 20:47:28 +00:00
|
|
|
#include "VideoCommon/ShaderGenCommon.h"
|
2019-07-14 05:24:12 +00:00
|
|
|
#include "VideoCommon/TextureDecoder.h"
|
2019-02-15 01:59:50 +00:00
|
|
|
#include "VideoCommon/VertexShaderGen.h"
|
2019-12-22 18:40:40 +00:00
|
|
|
#include "VideoCommon/VideoCommon.h"
|
|
|
|
#include "VideoCommon/VideoConfig.h"
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
namespace FramebufferShaderGen
|
|
|
|
{
|
2019-12-05 04:51:23 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
APIType GetAPIType()
|
2019-02-15 01:59:50 +00:00
|
|
|
{
|
|
|
|
return g_ActiveConfig.backend_info.api_type;
|
|
|
|
}
|
|
|
|
|
2020-10-19 20:47:28 +00:00
|
|
|
void EmitUniformBufferDeclaration(ShaderCode& code)
|
2019-02-15 01:59:50 +00:00
|
|
|
{
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write("UBO_BINDING(std140, 1) uniform PSBlock\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
2020-10-19 20:47:28 +00:00
|
|
|
void EmitSamplerDeclarations(ShaderCode& code, u32 start = 0, u32 end = 1,
|
2019-12-05 04:51:23 +00:00
|
|
|
bool multisampled = false)
|
2019-02-15 01:59:50 +00:00
|
|
|
{
|
|
|
|
switch (GetAPIType())
|
|
|
|
{
|
|
|
|
case APIType::D3D:
|
|
|
|
case APIType::OpenGL:
|
|
|
|
case APIType::Vulkan:
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
const char* array_type = multisampled ? "sampler2DMSArray" : "sampler2DArray";
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = start; i < end; i++)
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("SAMPLER_BINDING({}) uniform {} samp{};\n", i, array_type, i);
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-19 20:47:28 +00:00
|
|
|
void EmitSampleTexture(ShaderCode& code, u32 n, std::string_view coords)
|
2019-02-15 01:59:50 +00:00
|
|
|
{
|
|
|
|
switch (GetAPIType())
|
|
|
|
{
|
|
|
|
case APIType::D3D:
|
|
|
|
case APIType::OpenGL:
|
|
|
|
case APIType::Vulkan:
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("texture(samp{}, {})", n, coords);
|
2019-02-15 01:59:50 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-14 05:24:12 +00:00
|
|
|
// Emits a texel fetch/load instruction. Assumes that "coords" is a 4-element vector, with z
|
|
|
|
// containing the layer, and w containing the mipmap level.
|
2020-10-19 20:47:28 +00:00
|
|
|
void EmitTextureLoad(ShaderCode& code, u32 n, std::string_view coords)
|
2019-07-14 05:24:12 +00:00
|
|
|
{
|
|
|
|
switch (GetAPIType())
|
|
|
|
{
|
|
|
|
case APIType::D3D:
|
|
|
|
case APIType::OpenGL:
|
|
|
|
case APIType::Vulkan:
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("texelFetch(samp{}, ({}).xyz, ({}).w)", n, coords, coords);
|
2019-07-14 05:24:12 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-19 20:47:28 +00:00
|
|
|
void EmitVertexMainDeclaration(ShaderCode& code, u32 num_tex_inputs, u32 num_color_inputs,
|
2019-12-05 04:51:23 +00:00
|
|
|
bool position_input, u32 num_tex_outputs, u32 num_color_outputs,
|
2019-12-05 04:58:38 +00:00
|
|
|
std::string_view extra_inputs = {})
|
2019-02-15 01:59:50 +00:00
|
|
|
{
|
|
|
|
switch (GetAPIType())
|
|
|
|
{
|
|
|
|
case APIType::D3D:
|
|
|
|
case APIType::OpenGL:
|
|
|
|
case APIType::Vulkan:
|
|
|
|
{
|
|
|
|
for (u32 i = 0; i < num_tex_inputs; i++)
|
2019-12-05 04:29:11 +00:00
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
const auto attribute = SHADER_TEXTURE0_ATTRIB + i;
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("ATTRIBUTE_LOCATION({}) in float3 rawtex{};\n", attribute, i);
|
2019-12-05 04:29:11 +00:00
|
|
|
}
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < num_color_inputs; i++)
|
2019-12-05 04:29:11 +00:00
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
const auto attribute = SHADER_COLOR0_ATTRIB + i;
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("ATTRIBUTE_LOCATION({}) in float4 rawcolor{};\n", attribute, i);
|
2019-12-05 04:29:11 +00:00
|
|
|
}
|
2019-02-15 01:59:50 +00:00
|
|
|
if (position_input)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("ATTRIBUTE_LOCATION({}) in float4 rawpos;\n", SHADER_POSITION_ATTRIB);
|
2019-04-20 13:44:33 +00:00
|
|
|
|
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders)
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("VARYING_LOCATION(0) out VertexData {{\n");
|
2019-04-20 13:44:33 +00:00
|
|
|
for (u32 i = 0; i < num_tex_outputs; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float3 v_tex{};\n", i);
|
2019-04-20 13:44:33 +00:00
|
|
|
for (u32 i = 0; i < num_color_outputs; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 v_col{};\n", i);
|
|
|
|
code.Write("}};\n");
|
2019-04-20 13:44:33 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (u32 i = 0; i < num_tex_outputs; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("VARYING_LOCATION({}) out float3 v_tex{};\n", i, i);
|
2019-04-20 13:44:33 +00:00
|
|
|
for (u32 i = 0; i < num_color_outputs; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("VARYING_LOCATION({}) out float4 v_col{};\n", num_tex_inputs + i, i);
|
2019-04-20 13:44:33 +00:00
|
|
|
}
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("#define opos gl_Position\n");
|
|
|
|
code.Write("{}\n", extra_inputs);
|
|
|
|
code.Write("void main()\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-19 20:47:28 +00:00
|
|
|
void EmitPixelMainDeclaration(ShaderCode& code, u32 num_tex_inputs, u32 num_color_inputs,
|
2019-12-05 04:58:38 +00:00
|
|
|
std::string_view output_type = "float4",
|
|
|
|
std::string_view extra_vars = {}, bool emit_frag_coord = false)
|
2019-02-15 01:59:50 +00:00
|
|
|
{
|
|
|
|
switch (GetAPIType())
|
|
|
|
{
|
|
|
|
case APIType::D3D:
|
|
|
|
case APIType::OpenGL:
|
|
|
|
case APIType::Vulkan:
|
|
|
|
{
|
2019-04-20 13:44:33 +00:00
|
|
|
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders)
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("VARYING_LOCATION(0) in VertexData {{\n");
|
2019-04-20 13:44:33 +00:00
|
|
|
for (u32 i = 0; i < num_tex_inputs; i++)
|
2022-03-21 21:16:25 +00:00
|
|
|
code.Write(" float3 v_tex{};\n", i);
|
2019-04-20 13:44:33 +00:00
|
|
|
for (u32 i = 0; i < num_color_inputs; i++)
|
2022-03-21 21:16:25 +00:00
|
|
|
code.Write(" float4 v_col{};\n", i);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("}};\n");
|
2019-04-20 13:44:33 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (u32 i = 0; i < num_tex_inputs; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("VARYING_LOCATION({}) in float3 v_tex{};\n", i, i);
|
2019-04-20 13:44:33 +00:00
|
|
|
for (u32 i = 0; i < num_color_inputs; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("VARYING_LOCATION({}) in float4 v_col{};\n", num_tex_inputs + i, i);
|
2019-04-20 13:44:33 +00:00
|
|
|
}
|
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("FRAGMENT_OUTPUT_LOCATION(0) out {} ocol0;\n", output_type);
|
|
|
|
code.Write("{}\n", extra_vars);
|
2019-07-14 05:24:12 +00:00
|
|
|
if (emit_frag_coord)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("#define frag_coord gl_FragCoord\n");
|
|
|
|
code.Write("void main()\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-12-05 04:51:23 +00:00
|
|
|
} // Anonymous namespace
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
std::string GenerateScreenQuadVertexShader()
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitVertexMainDeclaration(code, 0, 0, false, 1, 0,
|
2022-05-04 05:41:34 +00:00
|
|
|
|
|
|
|
"#define id gl_VertexID\n");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(
|
2020-10-19 20:47:28 +00:00
|
|
|
"{{\n"
|
|
|
|
" v_tex0 = float3(float((id << 1) & 2), float(id & 2), 0.0f);\n"
|
|
|
|
" opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
// NDC space is flipped in Vulkan. We also flip in GL so that (0,0) is in the lower-left.
|
|
|
|
if (GetAPIType() == APIType::Vulkan || GetAPIType() == APIType::OpenGL)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" opos.y = -opos.y;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("}}\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string GeneratePassthroughGeometryShader(u32 num_tex, u32 num_colors)
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
2019-02-15 01:59:50 +00:00
|
|
|
if (GetAPIType() == APIType::D3D)
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("struct VS_OUTPUT\n"
|
|
|
|
"{{\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < num_tex; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float3 tex{} : TEXCOORD{};\n", i, i);
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < num_colors; i++)
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write(" float4 color{} : TEXCOORD{};\n", i, i + num_tex);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 position : SV_Position;\n"
|
|
|
|
"}};\n");
|
2019-12-05 04:29:11 +00:00
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("struct GS_OUTPUT\n"
|
|
|
|
"{{");
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < num_tex; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float3 tex{} : TEXCOORD{};\n", i, i);
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < num_colors; i++)
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write(" float4 color{} : TEXCOORD{};\n", i, i + num_tex);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 position : SV_Position;\n"
|
|
|
|
" uint slice : SV_RenderTargetArrayIndex;\n"
|
|
|
|
"}};\n\n");
|
|
|
|
|
|
|
|
code.Write("[maxvertexcount(6)]\n"
|
|
|
|
"void main(triangle VS_OUTPUT vso[3], inout TriangleStream<GS_OUTPUT> output)\n"
|
|
|
|
"{{\n"
|
|
|
|
" for (uint slice = 0; slice < 2u; slice++)\n"
|
|
|
|
" {{\n"
|
|
|
|
" for (int i = 0; i < 3; i++)\n"
|
|
|
|
" {{\n"
|
|
|
|
" GS_OUTPUT gso;\n"
|
|
|
|
" gso.position = vso[i].position;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < num_tex; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" gso.tex{} = float3(vso[i].tex{}.xy, float(slice));\n", i, i);
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < num_colors; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" gso.color{} = vso[i].color{};\n", i, i);
|
|
|
|
code.Write(" gso.slice = slice;\n"
|
|
|
|
" output.Append(gso);\n"
|
|
|
|
" }}\n"
|
|
|
|
" output.RestartStrip();\n"
|
|
|
|
" }}\n"
|
|
|
|
"}}\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
else if (GetAPIType() == APIType::OpenGL || GetAPIType() == APIType::Vulkan)
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("layout(triangles) in;\n"
|
|
|
|
"layout(triangle_strip, max_vertices = 6) out;\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
|
2019-04-20 13:44:33 +00:00
|
|
|
if (num_tex > 0 || num_colors > 0)
|
2019-02-15 01:59:50 +00:00
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("VARYING_LOCATION(0) in VertexData {{\n");
|
2019-04-20 13:44:33 +00:00
|
|
|
for (u32 i = 0; i < num_tex; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float3 v_tex{};\n", i);
|
2019-04-20 13:44:33 +00:00
|
|
|
for (u32 i = 0; i < num_colors; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 v_col{};\n", i);
|
|
|
|
code.Write("}} v_in[];\n");
|
2019-12-05 04:29:11 +00:00
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("VARYING_LOCATION(0) out VertexData {{\n");
|
2019-04-20 13:44:33 +00:00
|
|
|
for (u32 i = 0; i < num_tex; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float3 v_tex{};\n", i);
|
2019-04-20 13:44:33 +00:00
|
|
|
for (u32 i = 0; i < num_colors; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 v_col{};\n", i);
|
|
|
|
code.Write("}} v_out;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("\n"
|
|
|
|
"void main()\n"
|
|
|
|
"{{\n"
|
|
|
|
" for (int j = 0; j < 2; j++)\n"
|
|
|
|
" {{\n"
|
|
|
|
" gl_Layer = j;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
// We have to explicitly unroll this loop otherwise the GL compiler gets cranky.
|
|
|
|
for (u32 v = 0; v < 3; v++)
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" gl_Position = gl_in[{}].gl_Position;\n", v);
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < num_tex; i++)
|
2020-10-19 20:47:28 +00:00
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" v_out.v_tex{} = float3(v_in[{}].v_tex{}.xy, float(j));\n", i, v, i);
|
2020-10-19 20:47:28 +00:00
|
|
|
}
|
2019-02-15 01:59:50 +00:00
|
|
|
for (u32 i = 0; i < num_colors; i++)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" v_out.v_col{} = v_in[{}].v_col{};\n", i, v, i);
|
|
|
|
code.Write(" EmitVertex();\n\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" EndPrimitive();\n"
|
|
|
|
" }}\n"
|
|
|
|
"}}\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string GenerateTextureCopyVertexShader()
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitUniformBufferDeclaration(code);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{"
|
|
|
|
" float2 src_offset;\n"
|
|
|
|
" float2 src_size;\n"
|
|
|
|
"}};\n\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
|
|
|
|
EmitVertexMainDeclaration(code, 0, 0, false, 1, 0,
|
2022-05-04 05:41:34 +00:00
|
|
|
|
|
|
|
"#define id gl_VertexID");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" v_tex0 = float3(float((id << 1) & 2), float(id & 2), 0.0f);\n"
|
|
|
|
" opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n"
|
|
|
|
" v_tex0 = float3(src_offset + (src_size * v_tex0.xy), 0.0f);\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
// NDC space is flipped in Vulkan. We also flip in GL so that (0,0) is in the lower-left.
|
|
|
|
if (GetAPIType() == APIType::Vulkan || GetAPIType() == APIType::OpenGL)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" opos.y = -opos.y;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("}}\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string GenerateTextureCopyPixelShader()
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitSamplerDeclarations(code, 0, 1, false);
|
|
|
|
EmitPixelMainDeclaration(code, 1, 0);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" ocol0 = ");
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitSampleTexture(code, 0, "v_tex0");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(";\n"
|
|
|
|
"}}\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string GenerateColorPixelShader()
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitPixelMainDeclaration(code, 0, 1);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" ocol0 = v_col0;\n"
|
|
|
|
"}}\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string GenerateResolveDepthPixelShader(u32 samples)
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitSamplerDeclarations(code, 0, 1, true);
|
2022-05-04 05:41:34 +00:00
|
|
|
EmitPixelMainDeclaration(code, 1, 0, "float", "");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" int layer = int(v_tex0.z);\n");
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write(" int3 coords = int3(int2(gl_FragCoord.xy), layer);\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
// Take the minimum of all depth samples.
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write(" ocol0 = texelFetch(samp0, coords, 0).r;\n");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" for (int i = 1; i < {}; i++)\n", samples);
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write(" ocol0 = min(ocol0, texelFetch(samp0, coords, i).r);\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("}}\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string GenerateClearVertexShader()
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitUniformBufferDeclaration(code);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" float4 clear_color;\n"
|
|
|
|
" float clear_depth;\n"
|
|
|
|
"}};\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
|
|
|
|
EmitVertexMainDeclaration(code, 0, 0, false, 0, 1,
|
2022-05-04 05:41:34 +00:00
|
|
|
|
|
|
|
"#define id gl_VertexID\n");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(
|
2020-10-19 20:47:28 +00:00
|
|
|
"{{\n"
|
|
|
|
" float2 coord = float2(float((id << 1) & 2), float(id & 2));\n"
|
|
|
|
" opos = float4(coord * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), clear_depth, 1.0f);\n"
|
|
|
|
" v_col0 = clear_color;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
// NDC space is flipped in Vulkan
|
|
|
|
if (GetAPIType() == APIType::Vulkan)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" opos.y = -opos.y;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("}}\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string GenerateEFBPokeVertexShader()
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitVertexMainDeclaration(code, 0, 1, true, 0, 1);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" v_col0 = rawcolor0;\n"
|
|
|
|
" opos = float4(rawpos.xyz, 1.0f);\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
if (g_ActiveConfig.backend_info.bSupportsLargePoints)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" gl_PointSize = rawpos.w;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
// NDC space is flipped in Vulkan.
|
|
|
|
if (GetAPIType() == APIType::Vulkan)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" opos.y = -opos.y;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("}}\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string GenerateFormatConversionShader(EFBReinterpretType convtype, u32 samples)
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitSamplerDeclarations(code, 0, 1, samples > 1);
|
2022-05-04 05:41:34 +00:00
|
|
|
EmitPixelMainDeclaration(code, 1, 0, "float4",
|
|
|
|
|
|
|
|
"");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" int layer = int(v_tex0.z);\n");
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write(" int3 coords = int3(int2(gl_FragCoord.xy), layer);\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
|
|
|
|
if (samples == 1)
|
|
|
|
{
|
|
|
|
// No MSAA at all.
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write(" float4 val = texelFetch(samp0, coords, 0);\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
else if (g_ActiveConfig.bSSAA)
|
|
|
|
{
|
|
|
|
// Sample shading, shader runs once per sample
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write(" float4 val = texelFetch(samp0, coords, gl_SampleID);");
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// MSAA without sample shading, average out all samples.
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 val = float4(0.0f, 0.0f, 0.0f, 0.0f);\n");
|
|
|
|
code.Write(" for (int i = 0; i < {}; i++)\n", samples);
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write(" val += texelFetch(samp0, coords, i);\n");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" val /= float({});\n", samples);
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (convtype)
|
|
|
|
{
|
|
|
|
case EFBReinterpretType::RGB8ToRGBA6:
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" int4 src8 = int4(round(val * 255.f));\n"
|
|
|
|
" int4 dst6;\n"
|
|
|
|
" dst6.r = src8.r >> 2;\n"
|
|
|
|
" dst6.g = ((src8.r & 0x3) << 4) | (src8.g >> 4);\n"
|
|
|
|
" dst6.b = ((src8.g & 0xF) << 2) | (src8.b >> 6);\n"
|
|
|
|
" dst6.a = src8.b & 0x3F;\n"
|
|
|
|
" ocol0 = float4(dst6) / 63.f;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EFBReinterpretType::RGB8ToRGB565:
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" ocol0 = val;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EFBReinterpretType::RGBA6ToRGB8:
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" int4 src6 = int4(round(val * 63.f));\n"
|
|
|
|
" int4 dst8;\n"
|
|
|
|
" dst8.r = (src6.r << 2) | (src6.g >> 4);\n"
|
|
|
|
" dst8.g = ((src6.g & 0xF) << 4) | (src6.b >> 2);\n"
|
|
|
|
" dst8.b = ((src6.b & 0x3) << 6) | src6.a;\n"
|
|
|
|
" dst8.a = 255;\n"
|
|
|
|
" ocol0 = float4(dst8) / 255.f;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EFBReinterpretType::RGBA6ToRGB565:
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" ocol0 = val;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EFBReinterpretType::RGB565ToRGB8:
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" ocol0 = val;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EFBReinterpretType::RGB565ToRGBA6:
|
|
|
|
//
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" ocol0 = val;\n");
|
2019-02-15 01:59:50 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("}}\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-02-15 01:59:50 +00:00
|
|
|
}
|
|
|
|
|
2019-07-14 05:24:12 +00:00
|
|
|
std::string GenerateTextureReinterpretShader(TextureFormat from_format, TextureFormat to_format)
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitSamplerDeclarations(code, 0, 1, false);
|
|
|
|
EmitPixelMainDeclaration(code, 1, 0, "float4", "", true);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" int layer = int(v_tex0.z);\n"
|
|
|
|
" int4 coords = int4(int2(frag_coord.xy), layer, 0);\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
|
|
|
|
// Convert to a 32-bit value encompassing all channels, filling the most significant bits with
|
|
|
|
// zeroes.
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" uint raw_value;\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
switch (from_format)
|
|
|
|
{
|
|
|
|
case TextureFormat::I8:
|
|
|
|
case TextureFormat::C8:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 temp_value = ");
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitTextureLoad(code, 0, "coords");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(";\n"
|
|
|
|
" raw_value = uint(temp_value.r * 255.0);\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TextureFormat::IA8:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 temp_value = ");
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitTextureLoad(code, 0, "coords");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(";\n"
|
|
|
|
" raw_value = uint(temp_value.r * 255.0) | (uint(temp_value.a * 255.0) << 8);\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-03-23 06:35:01 +00:00
|
|
|
case TextureFormat::I4:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 temp_value = ");
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitTextureLoad(code, 0, "coords");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(";\n"
|
|
|
|
" raw_value = uint(temp_value.r * 15.0);\n");
|
2020-03-23 06:35:01 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2019-07-14 05:24:12 +00:00
|
|
|
case TextureFormat::IA4:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 temp_value = ");
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitTextureLoad(code, 0, "coords");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(";\n"
|
|
|
|
" raw_value = uint(temp_value.r * 15.0) | (uint(temp_value.a * 15.0) << 4);\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TextureFormat::RGB565:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 temp_value = ");
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitTextureLoad(code, 0, "coords");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(";\n"
|
|
|
|
" raw_value = uint(temp_value.b * 31.0) | (uint(temp_value.g * 63.0) << 5) |\n"
|
|
|
|
" (uint(temp_value.r * 31.0) << 11);\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TextureFormat::RGB5A3:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float4 temp_value = ");
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitTextureLoad(code, 0, "coords");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(";\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
|
|
|
|
// 0.8784 = 224 / 255 which is the maximum alpha value that can be represented in 3 bits
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(
|
2020-10-19 20:47:28 +00:00
|
|
|
" if (temp_value.a > 0.878f) {{\n"
|
|
|
|
" raw_value = (uint(temp_value.b * 31.0)) | (uint(temp_value.g * 31.0) << 5) |\n"
|
|
|
|
" (uint(temp_value.r * 31.0) << 10) | 0x8000u;\n"
|
|
|
|
" }} else {{\n"
|
|
|
|
" raw_value = (uint(temp_value.b * 15.0)) | (uint(temp_value.g * 15.0) << 4) |\n"
|
|
|
|
" (uint(temp_value.r * 15.0) << 8) | (uint(temp_value.a * 7.0) << 12);\n"
|
|
|
|
" }}\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
break;
|
2020-03-23 06:35:01 +00:00
|
|
|
|
|
|
|
default:
|
2020-11-14 03:33:26 +00:00
|
|
|
WARN_LOG_FMT(VIDEO, "From format {} is not supported", static_cast<u32>(from_format));
|
2020-03-23 06:35:01 +00:00
|
|
|
return "{}\n";
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now convert it to its new representation.
|
|
|
|
switch (to_format)
|
|
|
|
{
|
|
|
|
case TextureFormat::I8:
|
|
|
|
case TextureFormat::C8:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float orgba = float(raw_value & 0xFFu) / 255.0;\n"
|
|
|
|
" ocol0 = float4(orgba, orgba, orgba, orgba);\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TextureFormat::IA8:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float orgb = float(raw_value & 0xFFu) / 255.0;\n"
|
|
|
|
" ocol0 = float4(orgb, orgb, orgb, float((raw_value >> 8) & 0xFFu) / 255.0);\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TextureFormat::IA4:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" float orgb = float(raw_value & 0xFu) / 15.0;\n"
|
|
|
|
" ocol0 = float4(orgb, orgb, orgb, float((raw_value >> 4) & 0xFu) / 15.0);\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TextureFormat::RGB565:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" ocol0 = float4(float((raw_value >> 10) & 0x1Fu) / 31.0,\n"
|
|
|
|
" float((raw_value >> 5) & 0x1Fu) / 31.0,\n"
|
|
|
|
" float(raw_value & 0x1Fu) / 31.0, 1.0);\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TextureFormat::RGB5A3:
|
|
|
|
{
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" if ((raw_value & 0x8000u) != 0u) {{\n"
|
|
|
|
" ocol0 = float4(float((raw_value >> 10) & 0x1Fu) / 31.0,\n"
|
|
|
|
" float((raw_value >> 5) & 0x1Fu) / 31.0,\n"
|
|
|
|
" float(raw_value & 0x1Fu) / 31.0, 1.0);\n"
|
|
|
|
" }} else {{\n"
|
|
|
|
" ocol0 = float4(float((raw_value >> 8) & 0x0Fu) / 15.0,\n"
|
|
|
|
" float((raw_value >> 4) & 0x0Fu) / 15.0,\n"
|
|
|
|
" float(raw_value & 0x0Fu) / 15.0,\n"
|
|
|
|
" float((raw_value >> 12) & 0x07u) / 7.0);\n"
|
|
|
|
" }}\n");
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
break;
|
2020-03-23 06:35:01 +00:00
|
|
|
default:
|
2020-11-14 03:33:26 +00:00
|
|
|
WARN_LOG_FMT(VIDEO, "To format {} is not supported", static_cast<u32>(to_format));
|
2020-03-23 06:35:01 +00:00
|
|
|
return "{}\n";
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("}}\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-07-14 05:24:12 +00:00
|
|
|
}
|
|
|
|
|
2019-06-29 11:05:30 +00:00
|
|
|
std::string GenerateEFBRestorePixelShader()
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitSamplerDeclarations(code, 0, 2, false);
|
2022-05-04 05:41:34 +00:00
|
|
|
EmitPixelMainDeclaration(code, 1, 0, "float4", "");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" ocol0 = ");
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitSampleTexture(code, 0, "v_tex0");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(";\n");
|
2022-05-04 05:41:34 +00:00
|
|
|
code.Write(" gl_FragDepth = ");
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitSampleTexture(code, 1, "v_tex0");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(".r;\n"
|
|
|
|
"}}\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-06-29 11:05:30 +00:00
|
|
|
}
|
|
|
|
|
2019-10-02 02:52:45 +00:00
|
|
|
std::string GenerateImGuiVertexShader()
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
2019-10-02 02:52:45 +00:00
|
|
|
|
|
|
|
// Uniform buffer contains the viewport size, and we transform in the vertex shader.
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitUniformBufferDeclaration(code);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
"float2 u_rcp_viewport_size_mul2;\n"
|
|
|
|
"}};\n\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
|
|
|
|
EmitVertexMainDeclaration(code, 1, 1, true, 1, 1);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" v_tex0 = float3(rawtex0.xy, 0.0);\n"
|
|
|
|
" v_col0 = rawcolor0;\n"
|
|
|
|
" opos = float4(rawpos.x * u_rcp_viewport_size_mul2.x - 1.0,"
|
|
|
|
" 1.0 - rawpos.y * u_rcp_viewport_size_mul2.y, 0.0, 1.0);\n");
|
2019-10-02 02:52:45 +00:00
|
|
|
|
|
|
|
// NDC space is flipped in Vulkan.
|
|
|
|
if (GetAPIType() == APIType::Vulkan)
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" opos.y = -opos.y;\n");
|
2019-10-02 02:52:45 +00:00
|
|
|
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("}}\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
return code.GetBuffer();
|
2019-10-02 02:52:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string GenerateImGuiPixelShader()
|
|
|
|
{
|
2020-10-19 20:47:28 +00:00
|
|
|
ShaderCode code;
|
|
|
|
EmitSamplerDeclarations(code, 0, 1, false);
|
|
|
|
EmitPixelMainDeclaration(code, 1, 1);
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write("{{\n"
|
|
|
|
" ocol0 = ");
|
2020-10-19 20:47:28 +00:00
|
|
|
EmitSampleTexture(code, 0, "float3(v_tex0.xy, 0.0)");
|
2020-11-09 07:23:32 +00:00
|
|
|
code.Write(" * v_col0;\n"
|
|
|
|
"}}\n");
|
2020-10-19 20:47:28 +00:00
|
|
|
|
|
|
|
return code.GetBuffer();
|
2019-10-02 02:52:45 +00:00
|
|
|
}
|
|
|
|
|
2019-02-15 01:59:50 +00:00
|
|
|
} // namespace FramebufferShaderGen
|