soft shadows fixes
This commit is contained in:
@@ -28,14 +28,15 @@ uniform samplerCubeArray tex_depths_omni;
|
|||||||
uniform samplerCube tex_env;
|
uniform samplerCube tex_env;
|
||||||
uniform int lights_start, lights_count, soft_shadows_samples = 16, noise_size = 64;
|
uniform int lights_start, lights_count, soft_shadows_samples = 16, noise_size = 64;
|
||||||
uniform bool soft_shadows_enabled = false;
|
uniform bool soft_shadows_enabled = false;
|
||||||
|
uniform float soft_shadows_quality = 1.;
|
||||||
|
|
||||||
uniform vec4 fog_color = vec4(0.5, 0.5, 0.5, 1);
|
uniform vec4 fog_color = vec4(0.5, 0.5, 0.5, 1.);
|
||||||
uniform float fog_decay = 10, fog_density = 0;
|
uniform float fog_decay = 10., fog_density = 0.;
|
||||||
uniform mat3 view_mat;
|
uniform mat3 view_mat;
|
||||||
const float _pe = 2.4e-7;
|
const float _pe = 2.4e-7;
|
||||||
|
|
||||||
const vec3 luma = vec3(0.299, 0.587, 0.114);
|
const vec3 luma = vec3(0.299, 0.587, 0.114);
|
||||||
const float _min_rough = 1.e-8, max_lod = 8;
|
const float _min_rough = 1.e-8, max_lod = 8.;
|
||||||
const float PI = 3.1416;
|
const float PI = 3.1416;
|
||||||
|
|
||||||
ivec2 tc;
|
ivec2 tc;
|
||||||
@@ -177,28 +178,21 @@ void calcLight(in int index, in vec3 n, in vec3 v) {
|
|||||||
int layer = index - lights_start;
|
int layer = index - lights_start;
|
||||||
float shadow = 0.;
|
float shadow = 0.;
|
||||||
//float bias = abs(tan(PI/2.*(1 - abs(dot(normal, ldir)))) + 1) * z_near * 1;
|
//float bias = abs(tan(PI/2.*(1 - abs(dot(normal, ldir)))) + 1) * z_near * 1;
|
||||||
float bias = (1. + 1. / abs(dot(normal, ldir))) * z_near * 2;
|
float bias = (1. + 1. / abs(dot(normal, ldir))) * z_near * 2.;
|
||||||
//bias = bias * bias + z_near;
|
//bias = bias * bias + z_near;
|
||||||
|
|
||||||
if (soft_shadows_enabled) {
|
if (soft_shadows_enabled) {
|
||||||
|
|
||||||
|
float depth = 1.;
|
||||||
#ifdef SPOT
|
#ifdef SPOT
|
||||||
float depth = 1;
|
const int gm_size = 2;
|
||||||
const int gm_size = 3;
|
|
||||||
for (int i = -gm_size; i <= gm_size; ++i) {
|
for (int i = -gm_size; i <= gm_size; ++i) {
|
||||||
for (int j = -gm_size; j <= gm_size; ++j) {
|
for (int j = -gm_size; j <= gm_size; ++j) {
|
||||||
depth = min(depth, textureOffset(tex_depths_cone, vec3(shp.xy, layer), ivec2(i, j)).x);
|
depth = min(depth, textureOffset(tex_depths_cone, vec3(shp.xy, layer), ivec2(i, j)).x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
depth = 1 / (1 - depth) - 1 + z_near;
|
|
||||||
float dz = max(0, shp.z - depth);
|
|
||||||
float ds = qgl_light_parameter[index].size * dz / (ldist - dz);
|
|
||||||
//qgl_FragColor.rgb = vec3(-texture(tex_depths_cone, vec3(shp.xy,layer)).r*1000);
|
|
||||||
//qgl_FragColor.rgb = vec3(depth);
|
|
||||||
//qgl_FragColor.rgb = vec3(dz);
|
|
||||||
#else
|
#else
|
||||||
float depth = 1;
|
const int gm_size = 2;
|
||||||
const int gm_size = 3;
|
|
||||||
for (int i = -gm_size; i <= gm_size; ++i) {
|
for (int i = -gm_size; i <= gm_size; ++i) {
|
||||||
for (int j = -gm_size; j <= gm_size; ++j) {
|
for (int j = -gm_size; j <= gm_size; ++j) {
|
||||||
for (int k = -gm_size; k <= gm_size; ++k) {
|
for (int k = -gm_size; k <= gm_size; ++k) {
|
||||||
@@ -206,21 +200,23 @@ void calcLight(in int index, in vec3 n, in vec3 v) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
depth = 1 / (1 - depth) - 1 + z_near;
|
shp.z = ldist;
|
||||||
float dz = max(0, ldist - depth);
|
#endif
|
||||||
|
depth = 1. / (1. - depth) - 1. + z_near;
|
||||||
|
float dz = max(0., shp.z - depth);
|
||||||
float ds = qgl_light_parameter[index].size * dz / (ldist - dz);
|
float ds = qgl_light_parameter[index].size * dz / (ldist - dz);
|
||||||
//float ds = (ldist / 2.) / qgl_light_parameter[index].size;
|
//float ds = (ldist / 2.) / qgl_light_parameter[index].size;
|
||||||
//qgl_FragColor.rgb = vec3(ds);
|
float srate = abs(ds / pos.z) * 10. * soft_shadows_quality;
|
||||||
#endif
|
qgl_FragColor.rgb = vec3(srate);
|
||||||
|
|
||||||
int samples = clamp(1, soft_shadows_samples, int(round(ds * float(soft_shadows_samples))));
|
int samples = clamp(int(round(srate * float(soft_shadows_samples))), 1, soft_shadows_samples);
|
||||||
vds = ds * bn.xyz;
|
vds = ds * bn.xyz;
|
||||||
vds2 = ds * bn2.xyz;
|
vds2 = ds * bn2.xyz;
|
||||||
vec2 so;
|
vec2 so;
|
||||||
ivec2 sotc = tc;
|
ivec2 sotc = tc;
|
||||||
|
|
||||||
noise2init(vec2(hash(ivec2(tc.x * 2 + 1, tc.y)), hash(ivec2(tc.x, (tc.y * 2 + 1)))));
|
noise2init(vec2(hash(ivec2(tc.x * 2. + 1., tc.y)), hash(ivec2(tc.x, (tc.y * 2. + 1.)))));
|
||||||
for (int i = 1; i <= samples; ++i) {
|
for (int i = 0; i < samples; ++i) {
|
||||||
so = noise2();
|
so = noise2();
|
||||||
#ifdef SPOT
|
#ifdef SPOT
|
||||||
//vec4 nc = texelFetch(tex_noise, sotc % noise_size, 0);
|
//vec4 nc = texelFetch(tex_noise, sotc % noise_size, 0);
|
||||||
@@ -242,7 +238,7 @@ void calcLight(in int index, in vec3 n, in vec3 v) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
spot *= min(1, 2. * shadow / samples);
|
spot *= min(1., 2. * shadow / samples);
|
||||||
//spot *= shadow / soft_shadows_samples;
|
//spot *= shadow / soft_shadows_samples;
|
||||||
//spot *= shadow / soft_shadows_samples + 1;
|
//spot *= shadow / soft_shadows_samples + 1;
|
||||||
|
|
||||||
@@ -262,7 +258,7 @@ void calcLight(in int index, in vec3 n, in vec3 v) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 dist_decay = vec3(1, ldist, ldist*ldist);
|
vec3 dist_decay = vec3(1., ldist, ldist*ldist);
|
||||||
spot /= dot(qgl_light_parameter[index].decay_intensity.xyz, dist_decay);
|
spot /= dot(qgl_light_parameter[index].decay_intensity.xyz, dist_decay);
|
||||||
|
|
||||||
float NdotLs = NdotL*NdotL;
|
float NdotLs = NdotL*NdotL;
|
||||||
@@ -288,7 +284,7 @@ void main(void) {
|
|||||||
if (z == 1.) {
|
if (z == 1.) {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
pos.w = 1;
|
pos.w = 1.;
|
||||||
pos.xyz = view_dir * z;
|
pos.xyz = view_dir * z;
|
||||||
vec3 v = normalize(-pos.xyz);
|
vec3 v = normalize(-pos.xyz);
|
||||||
|
|
||||||
@@ -306,14 +302,16 @@ void main(void) {
|
|||||||
float reflectivity = v2.b;
|
float reflectivity = v2.b;
|
||||||
float NdotV = dot(normal, v);
|
float NdotV = dot(normal, v);
|
||||||
float roughness3 = roughness*roughness*roughness;
|
float roughness3 = roughness*roughness*roughness;
|
||||||
bn = normalize(cross(normal, vec3(1,0,0)) + cross(normal, vec3(0,1,0)));
|
bn = normalize(cross(normal, vec3(1.,0.,0.)) + cross(normal, vec3(0.,1.,0.)));
|
||||||
bn2 = normalize(cross(normal, bn));
|
bn2 = normalize(cross(normal, bn));
|
||||||
bn = cross(normal, bn2);
|
bn = cross(normal, bn2);
|
||||||
rough_diff = max(roughness, _min_rough);
|
rough_diff = max(roughness, _min_rough);
|
||||||
rough_spec = max(roughness3, _min_rough);
|
rough_spec = max(roughness3, _min_rough);
|
||||||
float shlick = clamp(metalness + (1 - metalness) * pow(1 - NdotV, 5), 0, 1);
|
float shlick = clamp(metalness + (1. - metalness) * pow(1. - NdotV, 5.), 0., 1.);
|
||||||
flags = uint(round(v2.w));
|
flags = uint(round(v2.w));
|
||||||
|
|
||||||
|
qgl_FragColor.rgba = vec4(0);
|
||||||
|
|
||||||
li = vec3(0.);//qgl_AmbientLight.color.rgb * qgl_AmbientLight.intensity;
|
li = vec3(0.);//qgl_AmbientLight.color.rgb * qgl_AmbientLight.intensity;
|
||||||
si = vec3(0.);
|
si = vec3(0.);
|
||||||
if (bitfieldExtract(flags, 0, 1) == 1) {
|
if (bitfieldExtract(flags, 0, 1) == 1) {
|
||||||
@@ -323,22 +321,22 @@ void main(void) {
|
|||||||
li = vec3(1.);
|
li = vec3(1.);
|
||||||
}
|
}
|
||||||
si *= shlick;
|
si *= shlick;
|
||||||
li *= (1 - shlick);
|
li *= (1. - shlick);
|
||||||
alpha = min(1, alpha * (1 + shlick));
|
alpha = min(1., alpha * (1. + shlick));
|
||||||
|
|
||||||
vec2 brdf = texture(tex_coeffs[0], vec2(NdotV*0.99, roughness*0.995)).rg;
|
vec2 brdf = texture(tex_coeffs[0], vec2(NdotV*0.99, roughness*0.995)).rg;
|
||||||
float env_spec = shlick * brdf.x + brdf.y;
|
float env_spec = shlick * brdf.x + brdf.y;
|
||||||
vec3 spec_col = mix(vec3(1), diffuse, metalness);
|
vec3 spec_col = mix(vec3(1.), diffuse, metalness);
|
||||||
vec3 env_dir = view_mat * reflect(-v, normal);
|
vec3 env_dir = view_mat * reflect(-v, normal);
|
||||||
vec3 env_col = textureLod(tex_env, env_dir, sqrt(roughness) * max_lod).rgb * spec_col;
|
vec3 env_col = textureLod(tex_env, env_dir, sqrt(roughness) * max_lod).rgb * spec_col;
|
||||||
|
|
||||||
vec3 res_col = max(vec3(0), li * diffuse + si * spec_col + emission);
|
vec3 res_col = max(vec3(0.), li * diffuse + si * spec_col + emission);
|
||||||
res_col = mix(res_col, env_col, env_spec * reflectivity);
|
res_col = mix(res_col, env_col, env_spec * reflectivity);
|
||||||
|
|
||||||
if (bitfieldExtract(flags, 1, 1) == 1) {
|
if (bitfieldExtract(flags, 1, 1) == 1) {
|
||||||
float plen = length(pos.xyz);
|
float plen = length(pos.xyz);
|
||||||
float fog = 1 - exp(-plen / fog_decay);
|
float fog = 1. - exp(-plen / fog_decay);
|
||||||
fog = clamp(fog * fog_color.a * fog_density, 0, 1);
|
fog = clamp(fog * fog_color.a * fog_density, 0., 1.);
|
||||||
res_col = mix(res_col, fog_color.rgb, fog);
|
res_col = mix(res_col, fog_color.rgb, fog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ void Renderer::reloadShaders() {
|
|||||||
shader_fxaa = nullptr;
|
shader_fxaa = nullptr;
|
||||||
if (tone_proc.shader_sum) delete tone_proc.shader_sum;
|
if (tone_proc.shader_sum) delete tone_proc.shader_sum;
|
||||||
tone_proc.shader_sum = nullptr;
|
tone_proc.shader_sum = nullptr;
|
||||||
QString dir = ":/shaders/";
|
QString dir = "./shaders/";
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
it.next();
|
it.next();
|
||||||
loadShadersMulti(shaders[it.key()], dir + it.value(), true, shader_defines.value(it.key()));
|
loadShadersMulti(shaders[it.key()], dir + it.value(), true, shader_defines.value(it.key()));
|
||||||
@@ -348,6 +348,7 @@ void Renderer::renderLight(int first_wr_buff, bool clear_only) {
|
|||||||
prog->setUniformValue("noise_size", noise_size);
|
prog->setUniformValue("noise_size", noise_size);
|
||||||
prog->setUniformValue("soft_shadows_enabled", view->soft_shadows);
|
prog->setUniformValue("soft_shadows_enabled", view->soft_shadows);
|
||||||
prog->setUniformValue("soft_shadows_samples", view->soft_shadows_samples);
|
prog->setUniformValue("soft_shadows_samples", view->soft_shadows_samples);
|
||||||
|
prog->setUniformValue("soft_shadows_quality", view->soft_shadows_quality);
|
||||||
prog->setUniformValue("tex_shadows_cone", (int)tarShadowsCone);
|
prog->setUniformValue("tex_shadows_cone", (int)tarShadowsCone);
|
||||||
prog->setUniformValue("tex_shadows_omni", (int)tarShadowsOmni);
|
prog->setUniformValue("tex_shadows_omni", (int)tarShadowsOmni);
|
||||||
prog->setUniformValue("tex_depths_cone", (int)tarDepthsCone);
|
prog->setUniformValue("tex_depths_cone", (int)tarDepthsCone);
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ QGLView::QGLView(): OpenGLWindow(), renderer_(this), mouse(this) {
|
|||||||
shaders_supported = false;
|
shaders_supported = false;
|
||||||
FXAA_ = false;
|
FXAA_ = false;
|
||||||
fps_cnt = 0;
|
fps_cnt = 0;
|
||||||
soft_shadows_samples = 16;
|
soft_shadows_quality = 1.;
|
||||||
|
soft_shadows_samples = 32;
|
||||||
soft_shadows = false;
|
soft_shadows = false;
|
||||||
fps_tm = fps_ = 0.;
|
fps_tm = fps_ = 0.;
|
||||||
fogColor_ = Qt::darkGray;
|
fogColor_ = Qt::darkGray;
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class QGLENGINE_CORE_EXPORT QGLView
|
|||||||
Q_PROPERTY(QSize textureMapSize READ textureMapSize WRITE setTextureMapSize)
|
Q_PROPERTY(QSize textureMapSize READ textureMapSize WRITE setTextureMapSize)
|
||||||
Q_PROPERTY(QSize shadowMapSize READ shadowMapSize WRITE setShadowMapSize)
|
Q_PROPERTY(QSize shadowMapSize READ shadowMapSize WRITE setShadowMapSize)
|
||||||
Q_PROPERTY(int softShadowsSamples READ softShadowsSamples WRITE setSoftShadowsSamples)
|
Q_PROPERTY(int softShadowsSamples READ softShadowsSamples WRITE setSoftShadowsSamples)
|
||||||
|
Q_PROPERTY(float softShadowsQuality READ softShadowsQuality WRITE setSoftShadowsQuality)
|
||||||
Q_PROPERTY(bool softShadows READ softShadows WRITE setSoftShadows)
|
Q_PROPERTY(bool softShadows READ softShadows WRITE setSoftShadows)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -168,6 +169,8 @@ public:
|
|||||||
|
|
||||||
int softShadowsSamples() const { return soft_shadows_samples; }
|
int softShadowsSamples() const { return soft_shadows_samples; }
|
||||||
void setSoftShadowsSamples(int s) { soft_shadows_samples = s; }
|
void setSoftShadowsSamples(int s) { soft_shadows_samples = s; }
|
||||||
|
float softShadowsQuality() const { return soft_shadows_quality; }
|
||||||
|
void setSoftShadowsQuality(float s) { soft_shadows_quality = s; }
|
||||||
bool softShadows() const { return soft_shadows; }
|
bool softShadows() const { return soft_shadows; }
|
||||||
void setSoftShadows(bool on) { soft_shadows = on; }
|
void setSoftShadows(bool on) { soft_shadows = on; }
|
||||||
|
|
||||||
@@ -208,7 +211,7 @@ private:
|
|||||||
GLint max_anisotropic, max_texture_chanels;
|
GLint max_anisotropic, max_texture_chanels;
|
||||||
RenderMode render_mode;
|
RenderMode render_mode;
|
||||||
QSize prev_size, shadow_map_size;
|
QSize prev_size, shadow_map_size;
|
||||||
float lineWidth_;
|
float lineWidth_, soft_shadows_quality;
|
||||||
float fps_, fps_tm, fogDensity_, fogDecay_;
|
float fps_, fps_tm, fogDensity_, fogDecay_;
|
||||||
float hoverHaloFill_, selectionHaloFill_, m_motionBlurFactor;
|
float hoverHaloFill_, selectionHaloFill_, m_motionBlurFactor;
|
||||||
int timer, fps_cnt, sh_id_loc, soft_shadows_samples;
|
int timer, fps_cnt, sh_id_loc, soft_shadows_samples;
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ void ViewEditor::assignQGLView(QGLView * v) {
|
|||||||
ui->spinFogDensity->setValue(view->fogDensity());
|
ui->spinFogDensity->setValue(view->fogDensity());
|
||||||
ui->checkSoftShadows->setChecked(view->softShadows());
|
ui->checkSoftShadows->setChecked(view->softShadows());
|
||||||
ui->spinSoftShadowSamples->setValue(view->softShadowsSamples());
|
ui->spinSoftShadowSamples->setValue(view->softShadowsSamples());
|
||||||
|
ui->spinSoftShadowQuality->setValue(view->softShadowsQuality());
|
||||||
auto setMapSize = [](QComboBox * combo, QSize sz) {
|
auto setMapSize = [](QComboBox * combo, QSize sz) {
|
||||||
for (int i = 0; i < combo->count(); ++i) {
|
for (int i = 0; i < combo->count(); ++i) {
|
||||||
if (combo->itemData(i).toSize() == sz) {
|
if (combo->itemData(i).toSize() == sz) {
|
||||||
@@ -247,6 +248,12 @@ void ViewEditor::on_spinSoftShadowSamples_valueChanged(double arg1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ViewEditor::on_spinSoftShadowQuality_valueChanged(double arg1) {
|
||||||
|
if (!view || !active) return;
|
||||||
|
view->setSoftShadowsQuality(arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void ViewEditor::on_checkVSync_clicked(bool val) {
|
void ViewEditor::on_checkVSync_clicked(bool val) {
|
||||||
if (!view || !active) return;
|
if (!view || !active) return;
|
||||||
view->setVSync(val);
|
view->setVSync(val);
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ private slots:
|
|||||||
void on_comboMapSizeShadow_currentIndexChanged(int index);
|
void on_comboMapSizeShadow_currentIndexChanged(int index);
|
||||||
void on_checkSoftShadows_clicked(bool arg1);
|
void on_checkSoftShadows_clicked(bool arg1);
|
||||||
void on_spinSoftShadowSamples_valueChanged(double arg1);
|
void on_spinSoftShadowSamples_valueChanged(double arg1);
|
||||||
|
void on_spinSoftShadowQuality_valueChanged(double arg1);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VIEW_EDITOR_H
|
#endif // VIEW_EDITOR_H
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>453</width>
|
<width>453</width>
|
||||||
<height>773</height>
|
<height>807</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
@@ -218,30 +218,56 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QFormLayout" name="formLayout_2">
|
||||||
<item>
|
<property name="labelAlignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_9">
|
<widget class="QLabel" name="label_9">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Soft shadow samples:</string>
|
<string>Soft shadow samples:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="0" column="1">
|
||||||
<widget class="SpinSlider" name="spinSoftShadowSamples">
|
<widget class="SpinSlider" name="spinSoftShadowSamples">
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
<double>1.000000000000000</double>
|
<double>1.000000000000000</double>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<double>128.000000000000000</double>
|
<double>256.000000000000000</double>
|
||||||
</property>
|
</property>
|
||||||
<property name="value">
|
<property name="value">
|
||||||
<double>16.000000000000000</double>
|
<double>32.000000000000000</double>
|
||||||
</property>
|
</property>
|
||||||
<property name="decimals">
|
<property name="decimals">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_10">
|
||||||
|
<property name="text">
|
||||||
|
<string>Soft shadow quality:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="SpinSlider" name="spinSoftShadowQuality">
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.100000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>10.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>1.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="decimals">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
|||||||
Reference in New Issue
Block a user