Now CG plays nice with this new stuff.
This commit is contained in:
parent
e67dbb33de
commit
f8eb45637f
|
@ -47,13 +47,13 @@ char *GenerateLightShader(char *p, int index, const LitChannel& chan, const char
|
||||||
// atten disabled
|
// atten disabled
|
||||||
switch (chan.diffusefunc) {
|
switch (chan.diffusefunc) {
|
||||||
case LIGHTDIF_NONE:
|
case LIGHTDIF_NONE:
|
||||||
WRITE(p, "lacc.%s += %s.lights[%d].col.%s;\n", swizzle, lightsName, index, swizzle);
|
WRITE(p, "lacc.%s += %s[%d].%s;\n", swizzle, lightsName, index * 5, swizzle);
|
||||||
break;
|
break;
|
||||||
case LIGHTDIF_SIGN:
|
case LIGHTDIF_SIGN:
|
||||||
case LIGHTDIF_CLAMP:
|
case LIGHTDIF_CLAMP:
|
||||||
WRITE(p, "ldir = normalize(%s.lights[%d].pos.xyz - pos.xyz);\n", lightsName, index);
|
WRITE(p, "ldir = normalize(%s[%d + 3].xyz - pos.xyz);\n", lightsName, index * 5);
|
||||||
WRITE(p, "lacc.%s += %sdot(ldir, _norm0)) * %s.lights[%d].col.%s;\n",
|
WRITE(p, "lacc.%s += %sdot(ldir, _norm0)) * %s[%d].%s;\n",
|
||||||
swizzle, chan.diffusefunc != LIGHTDIF_SIGN ? "max(0.0f," :"(", lightsName, index, swizzle);
|
swizzle, chan.diffusefunc != LIGHTDIF_SIGN ? "max(0.0f," :"(", lightsName, index * 5, swizzle);
|
||||||
break;
|
break;
|
||||||
default: _assert_(0);
|
default: _assert_(0);
|
||||||
}
|
}
|
||||||
|
@ -62,32 +62,32 @@ char *GenerateLightShader(char *p, int index, const LitChannel& chan, const char
|
||||||
|
|
||||||
if (chan.attnfunc == 3)
|
if (chan.attnfunc == 3)
|
||||||
{ // spot
|
{ // spot
|
||||||
WRITE(p, "ldir = %s.lights[%d].pos.xyz - pos.xyz;\n", lightsName, index);
|
WRITE(p, "ldir = %s[%d + 3].xyz - pos.xyz;\n", lightsName, index * 5);
|
||||||
WRITE(p, "dist2 = dot(ldir, ldir);\n"
|
WRITE(p, "dist2 = dot(ldir, ldir);\n"
|
||||||
"dist = sqrt(dist2);\n"
|
"dist = sqrt(dist2);\n"
|
||||||
"ldir = ldir / dist;\n"
|
"ldir = ldir / dist;\n"
|
||||||
"attn = max(0.0f, dot(ldir, %s.lights[%d].dir.xyz));\n", lightsName, index);
|
"attn = max(0.0f, dot(ldir, %s[%d + 4].xyz));\n", lightsName, index * 5);
|
||||||
WRITE(p, "attn = max(0.0f, dot(%s.lights[%d].cosatt.xyz, float3(1.0f, attn, attn*attn))) / dot(%s.lights[%d].distatt.xyz, float3(1.0f,dist,dist2));\n", lightsName, index, lightsName, index);
|
WRITE(p, "attn = max(0.0f, dot(%s[%d + 1].xyz, float3(1.0f, attn, attn*attn))) / dot(%s[%d + 2].xyz, float3(1.0f,dist,dist2));\n", lightsName, index * 5, lightsName, index * 5);
|
||||||
}
|
}
|
||||||
else if (chan.attnfunc == 1)
|
else if (chan.attnfunc == 1)
|
||||||
{ // specular
|
{ // specular
|
||||||
WRITE(p, "ldir = normalize(%s.lights[%d].pos.xyz);\n", lightsName, index);
|
WRITE(p, "ldir = normalize(%s[%d + 3].xyz);\n", lightsName, index * 5);
|
||||||
WRITE(p, "attn = (dot(_norm0,ldir) >= 0.0f) ? max(0.0f, dot(_norm0, %s.lights[%d].dir.xyz)) : 0.0f;\n", lightsName, index);
|
WRITE(p, "attn = (dot(_norm0,ldir) >= 0.0f) ? max(0.0f, dot(_norm0, %s[%d + 4].xyz)) : 0.0f;\n", lightsName, index * 5);
|
||||||
WRITE(p, "attn = max(0.0f, dot(%s.lights[%d].cosatt.xyz, float3(1,attn,attn*attn))) / dot(%s.lights[%d].distatt.xyz, float3(1,attn,attn*attn));\n", lightsName, index, lightsName, index);
|
WRITE(p, "attn = max(0.0f, dot(%s[%d + 1].xyz, float3(1,attn,attn*attn))) / dot(%s[%d + 2].xyz, float3(1,attn,attn*attn));\n", lightsName, index * 5, lightsName, index * 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (chan.diffusefunc)
|
switch (chan.diffusefunc)
|
||||||
{
|
{
|
||||||
case LIGHTDIF_NONE:
|
case LIGHTDIF_NONE:
|
||||||
WRITE(p, "lacc.%s += attn * %s.lights[%d].col.%s;\n", swizzle, lightsName, index, swizzle);
|
WRITE(p, "lacc.%s += attn * %s[%d].%s;\n", swizzle, lightsName, index * 5, swizzle);
|
||||||
break;
|
break;
|
||||||
case LIGHTDIF_SIGN:
|
case LIGHTDIF_SIGN:
|
||||||
case LIGHTDIF_CLAMP:
|
case LIGHTDIF_CLAMP:
|
||||||
WRITE(p, "lacc.%s += attn * %sdot(ldir, _norm0)) * %s.lights[%d].col.%s;\n",
|
WRITE(p, "lacc.%s += attn * %sdot(ldir, _norm0)) * %s[%d].%s;\n",
|
||||||
swizzle,
|
swizzle,
|
||||||
chan.diffusefunc != LIGHTDIF_SIGN ? "max(0.0f," :"(",
|
chan.diffusefunc != LIGHTDIF_SIGN ? "max(0.0f," :"(",
|
||||||
lightsName,
|
lightsName,
|
||||||
index,
|
index * 5,
|
||||||
swizzle);
|
swizzle);
|
||||||
break;
|
break;
|
||||||
default: _assert_(0);
|
default: _assert_(0);
|
||||||
|
@ -120,7 +120,7 @@ char *GenerateLightingShader(char *p, int components, const char* materialsName,
|
||||||
WRITE(p, "mat = float4(1.0f, 1.0f, 1.0f, 1.0f);\n");
|
WRITE(p, "mat = float4(1.0f, 1.0f, 1.0f, 1.0f);\n");
|
||||||
}
|
}
|
||||||
else // from color
|
else // from color
|
||||||
WRITE(p, "mat = %s.C%d;\n", materialsName, j+2);
|
WRITE(p, "mat = %s[%d];\n", materialsName, j+2);
|
||||||
|
|
||||||
if (color.enablelighting) {
|
if (color.enablelighting) {
|
||||||
if (color.ambsource) { // from vertex
|
if (color.ambsource) { // from vertex
|
||||||
|
@ -132,7 +132,7 @@ char *GenerateLightingShader(char *p, int components, const char* materialsName,
|
||||||
WRITE(p, "lacc = float4(0.0f, 0.0f, 0.0f, 0.0f);\n");
|
WRITE(p, "lacc = float4(0.0f, 0.0f, 0.0f, 0.0f);\n");
|
||||||
}
|
}
|
||||||
else // from color
|
else // from color
|
||||||
WRITE(p, "lacc = %s.C%d;\n", materialsName, j);
|
WRITE(p, "lacc = %s[%d];\n", materialsName, j);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -149,7 +149,7 @@ char *GenerateLightingShader(char *p, int components, const char* materialsName,
|
||||||
else WRITE(p, "mat.w = 1.0f;\n");
|
else WRITE(p, "mat.w = 1.0f;\n");
|
||||||
}
|
}
|
||||||
else // from color
|
else // from color
|
||||||
WRITE(p, "mat.w = %s.C%d.w;\n", materialsName, j+2);
|
WRITE(p, "mat.w = %s[%d].w;\n", materialsName, j+2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alpha.enablelighting)
|
if (alpha.enablelighting)
|
||||||
|
@ -163,7 +163,7 @@ char *GenerateLightingShader(char *p, int components, const char* materialsName,
|
||||||
WRITE(p, "lacc.w = 0.0f;\n");
|
WRITE(p, "lacc.w = 0.0f;\n");
|
||||||
}
|
}
|
||||||
else // from color
|
else // from color
|
||||||
WRITE(p, "lacc.w = %s.C%d.w;\n", materialsName, j);
|
WRITE(p, "lacc.w = %s[%d].w;\n", materialsName, j);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -292,11 +292,11 @@ const char *GenerateVertexShaderCode(u32 components, API_TYPE ApiType)
|
||||||
WRITE(p, "int posmtx = fposmtx;\n");
|
WRITE(p, "int posmtx = fposmtx;\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
WRITE(p, "float4 pos = float4(dot("I_TRANSFORMMATRICES".T[posmtx].t, rawpos), dot("I_TRANSFORMMATRICES".T[posmtx+1].t, rawpos), dot("I_TRANSFORMMATRICES".T[posmtx+2].t, rawpos), 1);\n");
|
WRITE(p, "float4 pos = float4(dot("I_TRANSFORMMATRICES"[posmtx], rawpos), dot("I_TRANSFORMMATRICES"[posmtx+1], rawpos), dot("I_TRANSFORMMATRICES"[posmtx+2], rawpos), 1);\n");
|
||||||
|
|
||||||
if (components & VB_HAS_NRMALL) {
|
if (components & VB_HAS_NRMALL) {
|
||||||
WRITE(p, "int normidx = posmtx >= 32 ? (posmtx-32) : posmtx;\n");
|
WRITE(p, "int normidx = posmtx >= 32 ? (posmtx-32) : posmtx;\n");
|
||||||
WRITE(p, "float3 N0 = "I_NORMALMATRICES".T[normidx].t.xyz, N1 = "I_NORMALMATRICES".T[normidx+1].t.xyz, N2 = "I_NORMALMATRICES".T[normidx+2].t.xyz;\n");
|
WRITE(p, "float3 N0 = "I_NORMALMATRICES"[normidx].xyz, N1 = "I_NORMALMATRICES"[normidx+1].xyz, N2 = "I_NORMALMATRICES"[normidx+2].xyz;\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (components & VB_HAS_NRM0)
|
if (components & VB_HAS_NRM0)
|
||||||
|
@ -308,13 +308,13 @@ const char *GenerateVertexShaderCode(u32 components, API_TYPE ApiType)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WRITE(p, "float4 pos = float4(dot("I_POSNORMALMATRIX".T0, rawpos), dot("I_POSNORMALMATRIX".T1, rawpos), dot("I_POSNORMALMATRIX".T2, rawpos), 1.0f);\n");
|
WRITE(p, "float4 pos = float4(dot("I_POSNORMALMATRIX"[0], rawpos), dot("I_POSNORMALMATRIX"[1], rawpos), dot("I_POSNORMALMATRIX"[2], rawpos), 1.0f);\n");
|
||||||
if (components & VB_HAS_NRM0)
|
if (components & VB_HAS_NRM0)
|
||||||
WRITE(p, "float3 _norm0 = normalize(float3(dot("I_POSNORMALMATRIX".N0.xyz, rawnorm0), dot("I_POSNORMALMATRIX".N1.xyz, rawnorm0), dot("I_POSNORMALMATRIX".N2.xyz, rawnorm0)));\n");
|
WRITE(p, "float3 _norm0 = normalize(float3(dot("I_POSNORMALMATRIX"[3].xyz, rawnorm0), dot("I_POSNORMALMATRIX"[4].xyz, rawnorm0), dot("I_POSNORMALMATRIX"[5].xyz, rawnorm0)));\n");
|
||||||
if (components & VB_HAS_NRM1)
|
if (components & VB_HAS_NRM1)
|
||||||
WRITE(p, "float3 _norm1 = float3(dot("I_POSNORMALMATRIX".N0.xyz, rawnorm1), dot("I_POSNORMALMATRIX".N1.xyz, rawnorm1), dot("I_POSNORMALMATRIX".N2.xyz, rawnorm1));\n");
|
WRITE(p, "float3 _norm1 = float3(dot("I_POSNORMALMATRIX"[3].xyz, rawnorm1), dot("I_POSNORMALMATRIX"[4].xyz, rawnorm1), dot("I_POSNORMALMATRIX"[5].xyz, rawnorm1));\n");
|
||||||
if (components & VB_HAS_NRM2)
|
if (components & VB_HAS_NRM2)
|
||||||
WRITE(p, "float3 _norm2 = float3(dot("I_POSNORMALMATRIX".N0.xyz, rawnorm2), dot("I_POSNORMALMATRIX".N1.xyz, rawnorm2), dot("I_POSNORMALMATRIX".N2.xyz, rawnorm2));\n");
|
WRITE(p, "float3 _norm2 = float3(dot("I_POSNORMALMATRIX".[3].xyz, rawnorm2), dot("I_POSNORMALMATRIX"[4].xyz, rawnorm2), dot("I_POSNORMALMATRIX"[5].xyz, rawnorm2));\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(components & VB_HAS_NRM0))
|
if (!(components & VB_HAS_NRM0))
|
||||||
|
@ -322,7 +322,7 @@ const char *GenerateVertexShaderCode(u32 components, API_TYPE ApiType)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
WRITE(p, "o.pos = float4(dot("I_PROJECTION".T0, pos), dot("I_PROJECTION".T1, pos), dot("I_PROJECTION".T2, pos), dot("I_PROJECTION".T3, pos));\n");
|
WRITE(p, "o.pos = float4(dot("I_PROJECTION"[0], pos), dot("I_PROJECTION"[1], pos), dot("I_PROJECTION"[2], pos), dot("I_PROJECTION"[3], pos));\n");
|
||||||
|
|
||||||
WRITE(p, "float4 mat, lacc;\n"
|
WRITE(p, "float4 mat, lacc;\n"
|
||||||
"float3 ldir, h;\n"
|
"float3 ldir, h;\n"
|
||||||
|
@ -421,18 +421,20 @@ const char *GenerateVertexShaderCode(u32 components, API_TYPE ApiType)
|
||||||
break;
|
break;
|
||||||
case XF_TEXGEN_REGULAR:
|
case XF_TEXGEN_REGULAR:
|
||||||
default:
|
default:
|
||||||
if (components & (VB_HAS_TEXMTXIDX0<<i)) {
|
if (components & (VB_HAS_TEXMTXIDX0<<i))
|
||||||
|
{
|
||||||
|
WRITE(p, "int tmp = int(tex%d.z);\n", i);
|
||||||
if (texinfo.projection == XF_TEXPROJ_STQ)
|
if (texinfo.projection == XF_TEXPROJ_STQ)
|
||||||
WRITE(p, "o.tex%d.xyz = float3(dot(coord, "I_TRANSFORMMATRICES".T[tex%d.z].t), dot(coord, "I_TRANSFORMMATRICES".T[tex%d.z+1].t), dot(coord, "I_TRANSFORMMATRICES".T[tex%d.z+2].t));\n", i, i, i, i);
|
WRITE(p, "o.tex%d.xyz = float3(dot(coord, "I_TRANSFORMMATRICES"[tmp]), dot(coord, "I_TRANSFORMMATRICES"[tmp+1]), dot(coord, "I_TRANSFORMMATRICES"[tmp+2]));\n", i);
|
||||||
else {
|
else {
|
||||||
WRITE(p, "o.tex%d.xyz = float3(dot(coord, "I_TRANSFORMMATRICES".T[tex%d.z].t), dot(coord, "I_TRANSFORMMATRICES".T[tex%d.z+1].t), 1);\n", i, i, i);
|
WRITE(p, "o.tex%d.xyz = float3(dot(coord, "I_TRANSFORMMATRICES"[tmp]), dot(coord, "I_TRANSFORMMATRICES"[tmp+1]), 1);\n", i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (texinfo.projection == XF_TEXPROJ_STQ)
|
if (texinfo.projection == XF_TEXPROJ_STQ)
|
||||||
WRITE(p, "o.tex%d.xyz = float3(dot(coord, "I_TEXMATRICES".T[%d].t), dot(coord, "I_TEXMATRICES".T[%d].t), dot(coord, "I_TEXMATRICES".T[%d].t));\n", i, 3*i, 3*i+1, 3*i+2);
|
WRITE(p, "o.tex%d.xyz = float3(dot(coord, "I_TEXMATRICES"[%d]), dot(coord, "I_TEXMATRICES"[%d]), dot(coord, "I_TEXMATRICES"[%d]));\n", i, 3*i, 3*i+1, 3*i+2);
|
||||||
else
|
else
|
||||||
WRITE(p, "o.tex%d.xyz = float3(dot(coord, "I_TEXMATRICES".T[%d].t), dot(coord, "I_TEXMATRICES".T[%d].t), 1);\n", i, 3*i, 3*i+1);
|
WRITE(p, "o.tex%d.xyz = float3(dot(coord, "I_TEXMATRICES"[%d]), dot(coord, "I_TEXMATRICES"[%d]), 1);\n", i, 3*i, 3*i+1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -441,9 +443,9 @@ const char *GenerateVertexShaderCode(u32 components, API_TYPE ApiType)
|
||||||
const PostMtxInfo& postInfo = xfregs.postMtxInfo[i];
|
const PostMtxInfo& postInfo = xfregs.postMtxInfo[i];
|
||||||
|
|
||||||
int postidx = postInfo.index;
|
int postidx = postInfo.index;
|
||||||
WRITE(p, "float4 P0 = "I_POSTTRANSFORMMATRICES".T[%d].t;\n"
|
WRITE(p, "float4 P0 = "I_POSTTRANSFORMMATRICES"[%d];\n"
|
||||||
"float4 P1 = "I_POSTTRANSFORMMATRICES".T[%d].t;\n"
|
"float4 P1 = "I_POSTTRANSFORMMATRICES"[%d];\n"
|
||||||
"float4 P2 = "I_POSTTRANSFORMMATRICES".T[%d].t;\n",
|
"float4 P2 = "I_POSTTRANSFORMMATRICES"[%d];\n",
|
||||||
postidx&0x3f, (postidx+1)&0x3f, (postidx+2)&0x3f);
|
postidx&0x3f, (postidx+1)&0x3f, (postidx+2)&0x3f);
|
||||||
|
|
||||||
if (texGenSpecialCase) {
|
if (texGenSpecialCase) {
|
||||||
|
|
|
@ -269,10 +269,7 @@ bool PixelShaderCache::CompilePixelShader(FRAGMENTSHADER& ps, const char* pstrpr
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined HAVE_CG && HAVE_CG
|
#if defined HAVE_CG && HAVE_CG
|
||||||
char stropt[128];
|
CGprogram tempprog = cgCreateProgram(g_cgcontext, CG_SOURCE, pstrprogram, g_cgfProf, "main", NULL);
|
||||||
sprintf(stropt, "MaxLocalParams=32,NumInstructionSlots=%d", s_nMaxPixelInstructions);
|
|
||||||
const char *opts[] = {"-profileopts", stropt, "-O2", "-q", NULL};
|
|
||||||
CGprogram tempprog = cgCreateProgram(g_cgcontext, CG_SOURCE, pstrprogram, g_cgfProf, "main", opts);
|
|
||||||
|
|
||||||
// handle errors
|
// handle errors
|
||||||
if (!cgIsProgram(tempprog))
|
if (!cgIsProgram(tempprog))
|
||||||
|
@ -281,13 +278,13 @@ bool PixelShaderCache::CompilePixelShader(FRAGMENTSHADER& ps, const char* pstrpr
|
||||||
|
|
||||||
static int num_failures = 0;
|
static int num_failures = 0;
|
||||||
char szTemp[MAX_PATH];
|
char szTemp[MAX_PATH];
|
||||||
sprintf(szTemp, "%sbad_ps_%04i.txt", File::GetUserPath(D_DUMP_IDX).c_str(), num_failures++);
|
sprintf(szTemp, "bad_ps_%04i.txt", num_failures++);
|
||||||
std::ofstream file(szTemp);
|
std::ofstream file(szTemp);
|
||||||
file << pstrprogram;
|
file << pstrprogram;
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
PanicAlert("Failed to compile pixel shader!\nThis usually happens when trying to use Dolphin with an outdated GPU or integrated GPU like the Intel GMA series.\n\nIf you're sure this is Dolphin's error anyway, post the contents of %s along with this error message at the forums.\n\nDebug info (%d):\n%s",
|
PanicAlert("Failed to compile pixel shader %d!\nThis usually happens when trying to use Dolphin with an outdated GPU or integrated GPU like the Intel GMA series.\n\nIf you're sure this is Dolphin's error anyway, post the contents of %s along with this error message at the forums.\n\nDebug info (%d):\n%s",
|
||||||
szTemp,
|
num_failures - 1, szTemp,
|
||||||
g_cgfProf,
|
g_cgfProf,
|
||||||
cgGetLastListing(g_cgcontext));
|
cgGetLastListing(g_cgcontext));
|
||||||
|
|
||||||
|
|
|
@ -155,20 +155,17 @@ bool VertexShaderCache::CompileVertexShader(VERTEXSHADER& vs, const char* pstrpr
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined HAVE_CG && HAVE_CG
|
#if defined HAVE_CG && HAVE_CG
|
||||||
char stropt[64];
|
CGprogram tempprog = cgCreateProgram(g_cgcontext, CG_SOURCE, pstrprogram, g_cgvProf, "main", NULL);
|
||||||
sprintf(stropt, "MaxLocalParams=256,MaxInstructions=%d", s_nMaxVertexInstructions);
|
|
||||||
const char *opts[] = {"-profileopts", stropt, "-O2", "-q", NULL};
|
|
||||||
CGprogram tempprog = cgCreateProgram(g_cgcontext, CG_SOURCE, pstrprogram, g_cgvProf, "main", opts);
|
|
||||||
if (!cgIsProgram(tempprog)) {
|
if (!cgIsProgram(tempprog)) {
|
||||||
static int num_failures = 0;
|
static int num_failures = 0;
|
||||||
char szTemp[MAX_PATH];
|
char szTemp[MAX_PATH];
|
||||||
sprintf(szTemp, "%sbad_vs_%04i.txt", File::GetUserPath(D_DUMP_IDX).c_str(), num_failures++);
|
sprintf(szTemp, "bad_vs_%04i.txt", num_failures++);
|
||||||
std::ofstream file(szTemp);
|
std::ofstream file(szTemp);
|
||||||
file << pstrprogram;
|
file << pstrprogram;
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
PanicAlert("Failed to compile vertex shader!\nThis usually happens when trying to use Dolphin with an outdated GPU or integrated GPU like the Intel GMA series.\n\nIf you're sure this is Dolphin's error anyway, post the contents of %s along with this error message at the forums.\n\nDebug info (%d):\n%s",
|
PanicAlert("Failed to compile vertex shader %d!\nThis usually happens when trying to use Dolphin with an outdated GPU or integrated GPU like the Intel GMA series.\n\nIf you're sure this is Dolphin's error anyway, post the contents of %s along with this error message at the forums.\n\nDebug info (%d):\n%s",
|
||||||
szTemp,
|
num_failures - 1, szTemp,
|
||||||
g_cgfProf,
|
g_cgfProf,
|
||||||
cgGetLastListing(g_cgcontext));
|
cgGetLastListing(g_cgcontext));
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue