Commit 5b3c500e authored by Ivan Gyurdiev's avatar Ivan Gyurdiev Committed by Alexandre Julliard

wined3d: Reverse semantics maps for shaders.

parent c611012d
...@@ -340,18 +340,9 @@ static void vshader_program_add_param(SHADER_OPCODE_ARG *arg, const DWORD param, ...@@ -340,18 +340,9 @@ static void vshader_program_add_param(SHADER_OPCODE_ARG *arg, const DWORD param,
break; break;
case D3DSPR_INPUT: case D3DSPR_INPUT:
if (This->semantics_in[WINED3DSHADERDECLUSAGE_DIFFUSE] && if (vshader_input_is_color((IWineD3DVertexShader*) This, reg))
reg == (This->semantics_in[WINED3DSHADERDECLUSAGE_DIFFUSE] & D3DSP_REGNUM_MASK))
is_color = TRUE; is_color = TRUE;
if (This->semantics_in[WINED3DSHADERDECLUSAGE_SPECULAR] &&
reg == (This->semantics_in[WINED3DSHADERDECLUSAGE_SPECULAR] & D3DSP_REGNUM_MASK))
is_color = TRUE;
/* FIXME: Shaders in 8.1 appear to not require a dcl statement - use
* the reg value from the vertex declaration. However, semantics are not initialized
* in that case - how can we know if an input contains color data or not? */
sprintf(tmpReg, "vertex.attrib[%lu]", reg); sprintf(tmpReg, "vertex.attrib[%lu]", reg);
strcat(hwLine, tmpReg); strcat(hwLine, tmpReg);
break; break;
......
...@@ -171,132 +171,14 @@ unsigned int shader_get_float_offset(const DWORD reg) { ...@@ -171,132 +171,14 @@ unsigned int shader_get_float_offset(const DWORD reg) {
} }
} }
static void shader_parse_decl_usage(
DWORD *semantics_map,
DWORD usage_token, DWORD param) {
unsigned int usage = (usage_token & D3DSP_DCL_USAGE_MASK) >> D3DSP_DCL_USAGE_SHIFT;
unsigned int usage_idx = (usage_token & D3DSP_DCL_USAGEINDEX_MASK) >> D3DSP_DCL_USAGEINDEX_SHIFT;
unsigned int regnum = param & D3DSP_REGNUM_MASK;
switch(usage) {
case D3DDECLUSAGE_POSITION:
if (usage_idx == 0) { /* tween data */
TRACE("Setting position to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_POSITION] = param;
} else {
/* TODO: position indexes go from 0-8!!*/
TRACE("Setting position 2 to %d because usage_idx = %d\n", regnum, usage_idx);
/* robots uses positions up to 8, the position arrays are just packed.*/
if (usage_idx > 1) {
TRACE("Loaded for position %d (greater than 2)\n", usage_idx);
}
semantics_map[WINED3DSHADERDECLUSAGE_POSITION2 + usage_idx-1] = param;
}
break;
case D3DDECLUSAGE_BLENDINDICES:
TRACE("Setting BLENDINDICES to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_BLENDINDICES] = param;
if (usage_idx != 0) FIXME("Extended BLENDINDICES\n");
break;
case D3DDECLUSAGE_BLENDWEIGHT:
TRACE("Setting BLENDWEIGHT to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_BLENDWEIGHT] = param;
if (usage_idx != 0) FIXME("Extended blend weights\n");
break;
case D3DDECLUSAGE_NORMAL:
if (usage_idx == 0) { /* tween data */
TRACE("Setting normal to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_NORMAL] = param;
} else {
TRACE("Setting normal 2 to %d because usage = %d\n", regnum, usage_idx);
semantics_map[WINED3DSHADERDECLUSAGE_NORMAL2] = param;
}
break;
case D3DDECLUSAGE_PSIZE:
TRACE("Setting PSIZE to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_PSIZE] = param;
if (usage_idx != 0) FIXME("Extended PSIZE\n");
break;
case D3DDECLUSAGE_COLOR:
if (usage_idx == 0) {
TRACE("Setting DIFFUSE to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_DIFFUSE] = param;
} else {
TRACE("Setting SPECULAR to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_SPECULAR] = param;
}
break;
case D3DDECLUSAGE_TEXCOORD:
if (usage_idx > 7) {
FIXME("Program uses texture coordinate %d but only 0-7 have been "
"implemented\n", usage_idx);
} else {
TRACE("Setting TEXCOORD %d to %d\n", usage_idx, regnum);
semantics_map[WINED3DSHADERDECLUSAGE_TEXCOORD0 + usage_idx] = param;
}
break;
case D3DDECLUSAGE_TANGENT:
TRACE("Setting TANGENT to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_TANGENT] = param;
break;
case D3DDECLUSAGE_BINORMAL:
TRACE("Setting BINORMAL to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_BINORMAL] = param;
break;
case D3DDECLUSAGE_TESSFACTOR:
TRACE("Setting TESSFACTOR to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_TESSFACTOR] = param;
break;
case D3DDECLUSAGE_POSITIONT:
if (usage_idx == 0) { /* tween data */
FIXME("Setting positiont to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_POSITIONT] = param;
} else {
FIXME("Setting positiont 2 to %d because usage = %d\n", regnum, usage_idx);
semantics_map[WINED3DSHADERDECLUSAGE_POSITIONT2] = param;
if (usage_idx != 0) FIXME("Extended positiont\n");
}
break;
case D3DDECLUSAGE_FOG:
TRACE("Setting FOG to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_FOG] = param;
break;
case D3DDECLUSAGE_DEPTH:
TRACE("Setting DEPTH to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_DEPTH] = param;
break;
case D3DDECLUSAGE_SAMPLE:
TRACE("Setting SAMPLE to %d\n", regnum);
semantics_map[WINED3DSHADERDECLUSAGE_SAMPLE] = param;
break;
default:
FIXME("Unrecognised dcl %#x", usage);
}
}
/* Note that this does not count the loop register /* Note that this does not count the loop register
* as an address register. */ * as an address register. */
void shader_get_registers_used( void shader_get_registers_used(
IWineD3DBaseShader *iface, IWineD3DBaseShader *iface,
shader_reg_maps* reg_maps, shader_reg_maps* reg_maps,
DWORD* semantics_in, semantic* semantics_in,
DWORD* semantics_out, semantic* semantics_out,
CONST DWORD* pToken) { CONST DWORD* pToken) {
IWineD3DBaseShaderImpl* This = (IWineD3DBaseShaderImpl*) iface; IWineD3DBaseShaderImpl* This = (IWineD3DBaseShaderImpl*) iface;
...@@ -350,12 +232,14 @@ void shader_get_registers_used( ...@@ -350,12 +232,14 @@ void shader_get_registers_used(
else else
reg_maps->packed_input[regnum] = 1; reg_maps->packed_input[regnum] = 1;
shader_parse_decl_usage(semantics_in, usage, param); semantics_in[regnum].usage = usage;
semantics_in[regnum].reg = param;
/* Vshader: mark 3.0 output registers used, save token */ /* Vshader: mark 3.0 output registers used, save token */
} else if (D3DSPR_OUTPUT == regtype) { } else if (D3DSPR_OUTPUT == regtype) {
reg_maps->packed_output[regnum] = 1; reg_maps->packed_output[regnum] = 1;
shader_parse_decl_usage(semantics_out, usage, param); semantics_out[regnum].usage = usage;
semantics_out[regnum].reg = param;
/* Save sampler usage token */ /* Save sampler usage token */
} else if (D3DSPR_SAMPLER == regtype) } else if (D3DSPR_SAMPLER == regtype)
......
...@@ -501,20 +501,8 @@ static void shader_glsl_get_register_name( ...@@ -501,20 +501,8 @@ static void shader_glsl_get_register_name(
strcpy(tmpStr, "gl_SecondaryColor"); strcpy(tmpStr, "gl_SecondaryColor");
} }
} else { } else {
IWineD3DVertexShaderImpl *This = (IWineD3DVertexShaderImpl*) arg->shader; if (vshader_input_is_color((IWineD3DVertexShader*) This, reg))
*is_color = TRUE;
if (This->semantics_in[WINED3DSHADERDECLUSAGE_DIFFUSE] &&
reg == (This->semantics_in[WINED3DSHADERDECLUSAGE_DIFFUSE] & D3DSP_REGNUM_MASK))
*is_color = TRUE;
if (This->semantics_in[WINED3DSHADERDECLUSAGE_SPECULAR] &&
reg == (This->semantics_in[WINED3DSHADERDECLUSAGE_SPECULAR] & D3DSP_REGNUM_MASK))
*is_color = TRUE;
/* FIXME: Shaders in 8.1 appear to not require a dcl statement - use
* the reg value from the vertex declaration. However, semantics are not initialized
* in that case - how can we know if an input contains color data or not? */
sprintf(tmpStr, "attrib%lu", reg); sprintf(tmpStr, "attrib%lu", reg);
} }
break; break;
...@@ -1390,53 +1378,50 @@ void pshader_glsl_dp2add(SHADER_OPCODE_ARG* arg) { ...@@ -1390,53 +1378,50 @@ void pshader_glsl_dp2add(SHADER_OPCODE_ARG* arg) {
void pshader_glsl_input_pack( void pshader_glsl_input_pack(
SHADER_BUFFER* buffer, SHADER_BUFFER* buffer,
DWORD* semantics_in) { semantic* semantics_in) {
unsigned int i; unsigned int i;
for (i = 0; i < WINED3DSHADERDECLUSAGE_MAX_USAGE; i++) { for (i = 0; i < MAX_REG_INPUT; i++) {
DWORD reg = semantics_in[i]; DWORD usage_token = semantics_in[i].usage;
unsigned int regnum = reg & D3DSP_REGNUM_MASK; DWORD register_token = semantics_in[i].reg;
DWORD usage, usage_idx;
char reg_mask[6]; char reg_mask[6];
/* Uninitialized */ /* Uninitialized */
if (!reg) continue; if (!usage_token) continue;
usage = (usage_token & D3DSP_DCL_USAGE_MASK) >> D3DSP_DCL_USAGE_SHIFT;
shader_glsl_get_output_register_swizzle(reg, reg_mask); usage_idx = (usage_token & D3DSP_DCL_USAGEINDEX_MASK) >> D3DSP_DCL_USAGEINDEX_SHIFT;
shader_glsl_get_output_register_swizzle(register_token, reg_mask);
switch(i) {
switch(usage) {
case WINED3DSHADERDECLUSAGE_DIFFUSE:
shader_addline(buffer, "IN%lu%s = vec4(gl_Color)%s;\n", case D3DDECLUSAGE_COLOR:
regnum, reg_mask, reg_mask); if (usage_idx == 0)
break; shader_addline(buffer, "IN%lu%s = vec4(gl_Color)%s;\n",
i, reg_mask, reg_mask);
case WINED3DSHADERDECLUSAGE_SPECULAR: if (usage_idx == 1)
shader_addline(buffer, "IN%lu%s = vec4(gl_SecondaryColor)%s;\n", shader_addline(buffer, "IN%lu%s = vec4(gl_SecondaryColor)%s;\n",
regnum, reg_mask, reg_mask); i, reg_mask, reg_mask);
else
shader_addline(buffer, "IN%lu%s = vec4(unsupported_color_input)%s;\n",
i, reg_mask, reg_mask);
break; break;
case WINED3DSHADERDECLUSAGE_TEXCOORD0: case D3DDECLUSAGE_TEXCOORD:
case WINED3DSHADERDECLUSAGE_TEXCOORD1:
case WINED3DSHADERDECLUSAGE_TEXCOORD2:
case WINED3DSHADERDECLUSAGE_TEXCOORD3:
case WINED3DSHADERDECLUSAGE_TEXCOORD4:
case WINED3DSHADERDECLUSAGE_TEXCOORD5:
case WINED3DSHADERDECLUSAGE_TEXCOORD6:
case WINED3DSHADERDECLUSAGE_TEXCOORD7:
shader_addline(buffer, "IN%lu%s = vec4(gl_TexCoord[%lu])%s;\n", shader_addline(buffer, "IN%lu%s = vec4(gl_TexCoord[%lu])%s;\n",
regnum, reg_mask, i - WINED3DSHADERDECLUSAGE_TEXCOORD0, reg_mask ); i, reg_mask, usage_idx, reg_mask );
break; break;
case WINED3DSHADERDECLUSAGE_FOG: case D3DDECLUSAGE_FOG:
shader_addline(buffer, "IN%lu%s = vec4(gl_FogFragCoord)%s;\n", shader_addline(buffer, "IN%lu%s = vec4(gl_FogFragCoord)%s;\n",
regnum, reg_mask, reg_mask); i, reg_mask, reg_mask);
break; break;
default: default:
shader_addline(buffer, "IN%lu%s = vec4(unsupported_input)%s;\n", shader_addline(buffer, "IN%lu%s = vec4(unsupported_input)%s;\n",
regnum, reg_mask, reg_mask); i, reg_mask, reg_mask);
} }
} }
} }
...@@ -1447,57 +1432,54 @@ void pshader_glsl_input_pack( ...@@ -1447,57 +1432,54 @@ void pshader_glsl_input_pack(
void vshader_glsl_output_unpack( void vshader_glsl_output_unpack(
SHADER_BUFFER* buffer, SHADER_BUFFER* buffer,
DWORD* semantics_out) { semantic* semantics_out) {
unsigned int i; unsigned int i;
for (i = 0; i < WINED3DSHADERDECLUSAGE_MAX_USAGE; i++) { for (i = 0; i < MAX_REG_OUTPUT; i++) {
DWORD reg = semantics_out[i]; DWORD usage_token = semantics_out[i].usage;
unsigned int regnum = reg & D3DSP_REGNUM_MASK; DWORD register_token = semantics_out[i].reg;
DWORD usage, usage_idx;
char reg_mask[6]; char reg_mask[6];
/* Uninitialized */ /* Uninitialized */
if (!reg) continue; if (!usage_token) continue;
shader_glsl_get_output_register_swizzle(reg, reg_mask);
switch(i) { usage = (usage_token & D3DSP_DCL_USAGE_MASK) >> D3DSP_DCL_USAGE_SHIFT;
usage_idx = (usage_token & D3DSP_DCL_USAGEINDEX_MASK) >> D3DSP_DCL_USAGEINDEX_SHIFT;
shader_glsl_get_output_register_swizzle(register_token, reg_mask);
case WINED3DSHADERDECLUSAGE_DIFFUSE: switch(usage) {
shader_addline(buffer, "gl_FrontColor%s = OUT%lu%s;\n", reg_mask, regnum, reg_mask);
break;
case WINED3DSHADERDECLUSAGE_SPECULAR: case D3DDECLUSAGE_COLOR:
shader_addline(buffer, "gl_FrontSecondaryColor%s = OUT%lu%s;\n", reg_mask, regnum, reg_mask); if (usage_idx == 0)
shader_addline(buffer, "gl_FrontColor%s = OUT%lu%s;\n", reg_mask, i, reg_mask);
else if (usage_idx == 1)
shader_addline(buffer, "gl_FrontSecondaryColor%s = OUT%lu%s;\n", reg_mask, i, reg_mask);
else
shader_addline(buffer, "unsupported_color_output%s = OUT%lu%s;\n", reg_mask, i, reg_mask);
break; break;
case WINED3DSHADERDECLUSAGE_POSITION: case D3DDECLUSAGE_POSITION:
shader_addline(buffer, "gl_Position%s = OUT%lu%s;\n", reg_mask, regnum, reg_mask); shader_addline(buffer, "gl_Position%s = OUT%lu%s;\n", reg_mask, i, reg_mask);
break; break;
case WINED3DSHADERDECLUSAGE_TEXCOORD0: case D3DDECLUSAGE_TEXCOORD:
case WINED3DSHADERDECLUSAGE_TEXCOORD1:
case WINED3DSHADERDECLUSAGE_TEXCOORD2:
case WINED3DSHADERDECLUSAGE_TEXCOORD3:
case WINED3DSHADERDECLUSAGE_TEXCOORD4:
case WINED3DSHADERDECLUSAGE_TEXCOORD5:
case WINED3DSHADERDECLUSAGE_TEXCOORD6:
case WINED3DSHADERDECLUSAGE_TEXCOORD7:
shader_addline(buffer, "gl_TexCoord[%lu]%s = OUT%lu%s;\n", shader_addline(buffer, "gl_TexCoord[%lu]%s = OUT%lu%s;\n",
i - WINED3DSHADERDECLUSAGE_TEXCOORD0, reg_mask, regnum, reg_mask); usage_idx, reg_mask, i, reg_mask);
break; break;
case WINED3DSHADERDECLUSAGE_PSIZE: case WINED3DSHADERDECLUSAGE_PSIZE:
shader_addline(buffer, "gl_PointSize = OUT%lu.x;\n", regnum); shader_addline(buffer, "gl_PointSize = OUT%lu.x;\n", i);
break; break;
case WINED3DSHADERDECLUSAGE_FOG: case WINED3DSHADERDECLUSAGE_FOG:
shader_addline(buffer, "gl_FogFragCoord%s = OUT%lu%s;\n", reg_mask, regnum, reg_mask); shader_addline(buffer, "gl_FogFragCoord%s = OUT%lu%s;\n", reg_mask, i, reg_mask);
break; break;
default: default:
shader_addline(buffer, "unsupported_output%s = OUT%lu%s;\n", reg_mask, regnum, reg_mask); shader_addline(buffer, "unsupported_output%s = OUT%lu%s;\n", reg_mask, i, reg_mask);
} }
} }
} }
...@@ -609,6 +609,37 @@ static void vshader_set_limits( ...@@ -609,6 +609,37 @@ static void vshader_set_limits(
} }
} }
BOOL vshader_get_input(
IWineD3DVertexShader* iface,
BYTE usage_req, BYTE usage_idx_req,
unsigned int* regnum) {
IWineD3DVertexShaderImpl* This = (IWineD3DVertexShaderImpl*) iface;
int i;
for (i = 0; i < MAX_ATTRIBS; i++) {
DWORD usage_token = This->semantics_in[i].usage;
DWORD usage = (usage_token & D3DSP_DCL_USAGE_MASK) >> D3DSP_DCL_USAGE_SHIFT;
DWORD usage_idx = (usage_token & D3DSP_DCL_USAGEINDEX_MASK) >> D3DSP_DCL_USAGEINDEX_SHIFT;
if (usage_token && (usage == usage_req && usage_idx == usage_idx_req)) {
*regnum = i;
return TRUE;
}
}
return FALSE;
}
BOOL vshader_input_is_color(
IWineD3DVertexShader* iface,
unsigned int regnum) {
IWineD3DVertexShaderImpl* This = (IWineD3DVertexShaderImpl*) iface;
DWORD usage_token = This->semantics_in[regnum].usage;
DWORD usage = (usage_token & D3DSP_DCL_USAGE_MASK) >> D3DSP_DCL_USAGE_SHIFT;
return usage == D3DDECLUSAGE_COLOR;
}
/** Generate a vertex shader string using either GL_VERTEX_PROGRAM_ARB /** Generate a vertex shader string using either GL_VERTEX_PROGRAM_ARB
or GLSL and send it to the card */ or GLSL and send it to the card */
static VOID IWineD3DVertexShaderImpl_GenerateShader( static VOID IWineD3DVertexShaderImpl_GenerateShader(
......
...@@ -1283,6 +1283,11 @@ struct glsl_shader_prog_link { ...@@ -1283,6 +1283,11 @@ struct glsl_shader_prog_link {
#define MAX_CONST_I 16 #define MAX_CONST_I 16
#define MAX_CONST_B 16 #define MAX_CONST_B 16
typedef struct semantic {
DWORD usage;
DWORD reg;
} semantic;
typedef struct shader_reg_maps { typedef struct shader_reg_maps {
char texcoord[MAX_REG_TEXCRD]; /* pixel < 3.0 */ char texcoord[MAX_REG_TEXCRD]; /* pixel < 3.0 */
...@@ -1367,6 +1372,16 @@ extern const SHADER_OPCODE* shader_get_opcode( ...@@ -1367,6 +1372,16 @@ extern const SHADER_OPCODE* shader_get_opcode(
IWineD3DBaseShader *iface, IWineD3DBaseShader *iface,
const DWORD code); const DWORD code);
/* Vertex shader utility functions */
extern BOOL vshader_get_input(
IWineD3DVertexShader* iface,
BYTE usage_req, BYTE usage_idx_req,
unsigned int* regnum);
extern BOOL vshader_input_is_color(
IWineD3DVertexShader* iface,
unsigned int regnum);
/* ARB_[vertex/fragment]_program helper functions */ /* ARB_[vertex/fragment]_program helper functions */
extern void shader_arb_load_constants( extern void shader_arb_load_constants(
IWineD3DStateBlock* iface, IWineD3DStateBlock* iface,
...@@ -1437,12 +1452,12 @@ extern void pshader_glsl_texbem(SHADER_OPCODE_ARG* arg); ...@@ -1437,12 +1452,12 @@ extern void pshader_glsl_texbem(SHADER_OPCODE_ARG* arg);
extern void pshader_glsl_dp2add(SHADER_OPCODE_ARG* arg); extern void pshader_glsl_dp2add(SHADER_OPCODE_ARG* arg);
extern void pshader_glsl_input_pack( extern void pshader_glsl_input_pack(
SHADER_BUFFER* buffer, SHADER_BUFFER* buffer,
DWORD* semantics_out); semantic* semantics_out);
/** GLSL Vertex Shader Prototypes */ /** GLSL Vertex Shader Prototypes */
extern void vshader_glsl_output_unpack( extern void vshader_glsl_output_unpack(
SHADER_BUFFER* buffer, SHADER_BUFFER* buffer,
DWORD* semantics_out); semantic* semantics_out);
/***************************************************************************** /*****************************************************************************
* IDirect3DBaseShader implementation structure * IDirect3DBaseShader implementation structure
...@@ -1474,8 +1489,8 @@ typedef struct IWineD3DBaseShaderImpl { ...@@ -1474,8 +1489,8 @@ typedef struct IWineD3DBaseShaderImpl {
extern void shader_get_registers_used( extern void shader_get_registers_used(
IWineD3DBaseShader *iface, IWineD3DBaseShader *iface,
shader_reg_maps* reg_maps, shader_reg_maps* reg_maps,
DWORD* semantics_in, semantic* semantics_in,
DWORD* semantics_out, semantic* semantics_out,
CONST DWORD* pToken); CONST DWORD* pToken);
extern void shader_generate_glsl_declarations( extern void shader_generate_glsl_declarations(
...@@ -1558,8 +1573,8 @@ typedef struct IWineD3DVertexShaderImpl { ...@@ -1558,8 +1573,8 @@ typedef struct IWineD3DVertexShaderImpl {
DWORD usage; DWORD usage;
/* Vertex shader input and output semantics */ /* Vertex shader input and output semantics */
DWORD semantics_in [WINED3DSHADERDECLUSAGE_MAX_USAGE]; semantic semantics_in [MAX_ATTRIBS];
DWORD semantics_out [WINED3DSHADERDECLUSAGE_MAX_USAGE]; semantic semantics_out [MAX_REG_OUTPUT];
/* run time datas... */ /* run time datas... */
VSHADERDATA *data; VSHADERDATA *data;
...@@ -1589,7 +1604,7 @@ typedef struct IWineD3DPixelShaderImpl { ...@@ -1589,7 +1604,7 @@ typedef struct IWineD3DPixelShaderImpl {
IWineD3DDeviceImpl *wineD3DDevice; IWineD3DDeviceImpl *wineD3DDevice;
/* Pixel shader input semantics */ /* Pixel shader input semantics */
DWORD semantics_in [WINED3DSHADERDECLUSAGE_MAX_USAGE]; semantic semantics_in [MAX_REG_INPUT];
/* run time data */ /* run time data */
PSHADERDATA *data; PSHADERDATA *data;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment