2012-04-21 21:13:50 +00:00
|
|
|
/* RetroArch - A frontend for libretro.
|
2012-04-14 18:33:56 +00:00
|
|
|
* Copyright (C) 2010-2012 - Hans-Kristian Arntzen
|
|
|
|
* Copyright (C) 2011-2012 - Daniel De Matteis
|
|
|
|
*
|
2012-04-21 21:13:50 +00:00
|
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
2012-04-14 18:33:56 +00:00
|
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
|
|
*
|
2012-04-21 21:13:50 +00:00
|
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
2012-04-14 18:33:56 +00:00
|
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
|
|
*
|
2012-04-21 21:31:57 +00:00
|
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
2012-04-14 18:33:56 +00:00
|
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "shader_hlsl.h"
|
|
|
|
#ifdef _XBOX
|
|
|
|
#include <xtl.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
struct hlsl_program
|
|
|
|
{
|
|
|
|
IDirect3DVertexShader9 *vprg;
|
|
|
|
IDirect3DPixelShader9 *fprg;
|
|
|
|
D3DXHANDLE vid_size_f;
|
|
|
|
D3DXHANDLE tex_size_f;
|
|
|
|
D3DXHANDLE out_size_f;
|
2012-04-15 14:34:09 +00:00
|
|
|
D3DXHANDLE frame_cnt_f;
|
|
|
|
D3DXHANDLE frame_dir_f;
|
2012-04-14 18:33:56 +00:00
|
|
|
D3DXHANDLE vid_size_v;
|
|
|
|
D3DXHANDLE tex_size_v;
|
|
|
|
D3DXHANDLE out_size_v;
|
2012-04-15 14:34:09 +00:00
|
|
|
D3DXHANDLE frame_cnt_v;
|
|
|
|
D3DXHANDLE frame_dir_v;
|
2012-04-15 15:50:07 +00:00
|
|
|
D3DXHANDLE mvp;
|
2012-04-14 20:44:46 +00:00
|
|
|
LPD3DXCONSTANTTABLE v_ctable;
|
|
|
|
LPD3DXCONSTANTTABLE f_ctable;
|
2012-04-17 03:23:54 +00:00
|
|
|
XMMATRIX mvp_val; /* TODO: Move to D3DXMATRIX here */
|
2012-04-14 18:33:56 +00:00
|
|
|
};
|
|
|
|
|
2012-04-16 16:11:20 +00:00
|
|
|
static IDirect3DDevice9 *d3d_device_ptr;
|
2012-04-21 21:25:32 +00:00
|
|
|
static struct hlsl_program prg[RARCH_HLSL_MAX_SHADERS] = {0};
|
2012-04-14 18:33:56 +00:00
|
|
|
static bool hlsl_active = false;
|
2012-04-14 19:45:43 +00:00
|
|
|
static unsigned active_index = 0;
|
2012-05-06 19:57:01 +00:00
|
|
|
static unsigned hlsl_shader_num = 0;
|
2012-04-14 19:45:43 +00:00
|
|
|
|
2012-04-16 16:11:20 +00:00
|
|
|
static const char *stock_hlsl_program =
|
2012-04-14 19:45:43 +00:00
|
|
|
"void main_vertex "
|
|
|
|
"( "
|
2012-05-06 19:57:01 +00:00
|
|
|
" float4 position : POSITION, "
|
|
|
|
" float4 color : COLOR, "
|
|
|
|
" float4 texCoord : TEXCOORD0, "
|
|
|
|
" uniform float4x4 modelViewProj, "
|
2012-04-14 19:45:43 +00:00
|
|
|
" out float4 oPosition : POSITION, "
|
2012-05-06 19:57:01 +00:00
|
|
|
" out float4 oColor : COLOR, "
|
2012-04-14 19:45:43 +00:00
|
|
|
" out float2 otexCoord : TEXCOORD "
|
|
|
|
") "
|
|
|
|
"{ "
|
2012-05-06 19:57:01 +00:00
|
|
|
" oPosition = mul(modelViewProj, position); "
|
|
|
|
" oColor = color; "
|
2012-04-14 19:45:43 +00:00
|
|
|
" otexCoord = texCoord; "
|
|
|
|
"} "
|
2012-04-16 16:11:20 +00:00
|
|
|
" "
|
2012-04-14 19:45:43 +00:00
|
|
|
"struct output "
|
2012-04-16 16:11:20 +00:00
|
|
|
"{ "
|
2012-04-14 19:45:43 +00:00
|
|
|
" float4 color: COLOR; "
|
|
|
|
"}; "
|
2012-04-16 16:11:20 +00:00
|
|
|
" "
|
2012-04-14 19:45:43 +00:00
|
|
|
"struct input "
|
|
|
|
"{ "
|
|
|
|
" float2 video_size; "
|
|
|
|
" float2 texture_size; "
|
2012-04-16 16:11:20 +00:00
|
|
|
" float2 output_size; "
|
2012-05-06 19:57:01 +00:00
|
|
|
" float frame_count; "
|
|
|
|
" float frame_direction; "
|
|
|
|
" float frame_rotation; "
|
2012-04-14 19:45:43 +00:00
|
|
|
"}; "
|
2012-04-16 16:11:20 +00:00
|
|
|
" "
|
2012-04-14 19:45:43 +00:00
|
|
|
"output main_fragment(float2 texCoord : TEXCOORD0, "
|
2012-05-06 19:57:01 +00:00
|
|
|
"uniform sampler2D decal : TEXUNIT0, uniform input IN) "
|
2012-04-14 19:45:43 +00:00
|
|
|
"{ "
|
|
|
|
" output OUT; "
|
2012-05-06 19:57:01 +00:00
|
|
|
" OUT.color = tex2D(decal, texCoord); "
|
2012-04-14 19:45:43 +00:00
|
|
|
" return OUT; "
|
|
|
|
"} ";
|
2012-04-14 18:33:56 +00:00
|
|
|
|
|
|
|
void hlsl_set_proj_matrix(XMMATRIX rotation_value)
|
|
|
|
{
|
2012-04-14 19:45:43 +00:00
|
|
|
if (hlsl_active)
|
2012-04-15 15:50:07 +00:00
|
|
|
prg[active_index].mvp_val = rotation_value;
|
2012-04-14 18:33:56 +00:00
|
|
|
}
|
|
|
|
|
2012-04-15 18:02:51 +00:00
|
|
|
#define set_param_2f(param, xy, constanttable) \
|
2012-04-16 16:11:20 +00:00
|
|
|
if (param) constanttable->SetFloatArray(d3d_device_ptr, param, xy, 2)
|
2012-04-16 02:21:49 +00:00
|
|
|
#define set_param_1f(param, x, constanttable) \
|
2012-04-18 03:01:17 +00:00
|
|
|
if (param) constanttable->SetFloat(d3d_device_ptr, param, x)
|
2012-04-15 18:02:51 +00:00
|
|
|
|
|
|
|
void hlsl_set_params(unsigned width, unsigned height,
|
|
|
|
unsigned tex_width, unsigned tex_height,
|
2012-04-16 02:21:49 +00:00
|
|
|
unsigned out_width, unsigned out_height,
|
|
|
|
unsigned frame_count)
|
2012-04-14 18:33:56 +00:00
|
|
|
{
|
|
|
|
if (!hlsl_active)
|
|
|
|
return;
|
|
|
|
|
2012-04-16 16:11:20 +00:00
|
|
|
const float ori_size[2] = { (float)width, (float)height };
|
|
|
|
const float tex_size[2] = { (float)tex_width, (float)tex_height };
|
|
|
|
const float out_size[2] = { (float)out_width, (float)out_height };
|
2012-04-18 00:57:46 +00:00
|
|
|
float frame_cnt = frame_count;
|
|
|
|
|
|
|
|
prg[active_index].f_ctable->SetDefaults(d3d_device_ptr);
|
|
|
|
prg[active_index].v_ctable->SetDefaults(d3d_device_ptr);
|
2012-04-15 18:02:51 +00:00
|
|
|
|
|
|
|
set_param_2f(prg[active_index].vid_size_f, ori_size, prg[active_index].f_ctable);
|
|
|
|
set_param_2f(prg[active_index].tex_size_f, tex_size, prg[active_index].f_ctable);
|
|
|
|
set_param_2f(prg[active_index].out_size_f, out_size, prg[active_index].f_ctable);
|
2012-04-18 00:57:46 +00:00
|
|
|
set_param_1f(prg[active_index].frame_cnt_f, frame_cnt, prg[active_index].f_ctable);
|
2012-04-17 19:27:47 +00:00
|
|
|
set_param_1f(prg[active_index].frame_dir_f, g_extern.frame_is_reverse ? -1.0 : 1.0,prg[active_index].f_ctable);
|
2012-04-15 18:02:51 +00:00
|
|
|
|
|
|
|
set_param_2f(prg[active_index].vid_size_v, ori_size, prg[active_index].v_ctable);
|
|
|
|
set_param_2f(prg[active_index].tex_size_v, tex_size, prg[active_index].v_ctable);
|
|
|
|
set_param_2f(prg[active_index].out_size_v, out_size, prg[active_index].v_ctable);
|
2012-04-18 00:57:46 +00:00
|
|
|
set_param_1f(prg[active_index].frame_cnt_v, frame_cnt, prg[active_index].v_ctable);
|
2012-04-17 19:27:47 +00:00
|
|
|
set_param_1f(prg[active_index].frame_dir_v, g_extern.frame_is_reverse ? -1.0 : 1.0,prg[active_index].v_ctable);
|
2012-04-15 18:02:51 +00:00
|
|
|
|
2012-04-17 03:23:54 +00:00
|
|
|
/* TODO: Move to D3DXMATRIX here */
|
2012-05-06 19:57:01 +00:00
|
|
|
if(prg[active_index].mvp)
|
|
|
|
prg[active_index].v_ctable->SetMatrix(d3d_device_ptr, prg[active_index].mvp, (D3DXMATRIX*)&prg[active_index].mvp_val);
|
2012-04-14 18:33:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool load_program(unsigned index, const char *prog, bool path_is_file)
|
|
|
|
{
|
2012-05-28 12:05:28 +00:00
|
|
|
HRESULT ret, ret_fp, ret_vp;
|
2012-04-14 18:33:56 +00:00
|
|
|
ID3DXBuffer *listing_f = NULL;
|
|
|
|
ID3DXBuffer *listing_v = NULL;
|
|
|
|
ID3DXBuffer *code_f = NULL;
|
|
|
|
ID3DXBuffer *code_v = NULL;
|
|
|
|
|
|
|
|
if (path_is_file)
|
|
|
|
{
|
2012-04-16 16:11:20 +00:00
|
|
|
ret_fp = D3DXCompileShaderFromFile(prog, NULL, NULL,
|
2012-04-17 19:27:47 +00:00
|
|
|
"main_fragment", "ps_3_0", 0, &code_f, &listing_f, &prg[index].f_ctable);
|
2012-04-16 16:11:20 +00:00
|
|
|
ret_vp = D3DXCompileShaderFromFile(prog, NULL, NULL,
|
2012-04-17 19:27:47 +00:00
|
|
|
"main_vertex", "vs_3_0", 0, &code_v, &listing_v, &prg[index].v_ctable);
|
2012-04-14 18:33:56 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-04-14 20:19:39 +00:00
|
|
|
/* TODO - crashes currently - to do with 'end of line' of stock shader */
|
2012-04-16 16:11:20 +00:00
|
|
|
ret_fp = D3DXCompileShader(prog, (UINT)strlen(prog), NULL, NULL,
|
2012-04-17 19:27:47 +00:00
|
|
|
"main_fragment", "ps_3_0", 0, &code_f, &listing_f, &prg[index].f_ctable );
|
2012-04-16 16:11:20 +00:00
|
|
|
ret_vp = D3DXCompileShader(prog, (UINT)strlen(prog), NULL, NULL,
|
2012-04-17 19:27:47 +00:00
|
|
|
"main_vertex", "vs_3_0", 0, &code_v, &listing_v, &prg[index].v_ctable );
|
2012-04-14 18:33:56 +00:00
|
|
|
}
|
|
|
|
|
2012-05-28 12:05:28 +00:00
|
|
|
if (ret_fp < 0 || ret_vp < 0 || listing_v || listing_f)
|
2012-04-14 18:33:56 +00:00
|
|
|
{
|
2012-04-21 21:25:32 +00:00
|
|
|
RARCH_ERR("HLSL error:\n");
|
2012-04-14 18:33:56 +00:00
|
|
|
if(listing_f)
|
2012-04-21 21:25:32 +00:00
|
|
|
RARCH_ERR("Fragment:\n%s\n", (char*)listing_f->GetBufferPointer());
|
2012-04-14 18:33:56 +00:00
|
|
|
if(listing_v)
|
2012-04-21 21:25:32 +00:00
|
|
|
RARCH_ERR("Vertex:\n%s\n", (char*)listing_v->GetBufferPointer());
|
2012-04-14 18:33:56 +00:00
|
|
|
|
|
|
|
ret = false;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2012-04-17 03:12:03 +00:00
|
|
|
d3d_device_ptr->CreatePixelShader((const DWORD*)code_f->GetBufferPointer(), &prg[index].fprg);
|
|
|
|
d3d_device_ptr->CreateVertexShader((const DWORD*)code_v->GetBufferPointer(), &prg[index].vprg);
|
2012-04-14 18:33:56 +00:00
|
|
|
code_f->Release();
|
|
|
|
code_v->Release();
|
|
|
|
|
|
|
|
end:
|
2012-04-16 16:11:20 +00:00
|
|
|
if (listing_f)
|
2012-04-15 14:34:09 +00:00
|
|
|
listing_f->Release();
|
2012-04-16 16:11:20 +00:00
|
|
|
if (listing_v)
|
2012-04-15 14:34:09 +00:00
|
|
|
listing_v->Release();
|
2012-04-14 18:33:56 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool load_stock(void)
|
|
|
|
{
|
|
|
|
if (!load_program(0, stock_hlsl_program, false))
|
|
|
|
{
|
2012-04-21 21:25:32 +00:00
|
|
|
RARCH_ERR("Failed to compile passthrough shader, is something wrong with your environment?\n");
|
2012-04-14 18:33:56 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-05-21 16:01:51 +00:00
|
|
|
static void set_program_attributes(unsigned i)
|
|
|
|
{
|
|
|
|
prg[i].vid_size_f = prg[i].f_ctable->GetConstantByName(NULL, "$IN.video_size");
|
|
|
|
prg[i].tex_size_f = prg[i].f_ctable->GetConstantByName(NULL, "$IN.texture_size");
|
|
|
|
prg[i].out_size_f = prg[i].f_ctable->GetConstantByName(NULL, "$IN.output_size");
|
|
|
|
prg[i].frame_cnt_f = prg[i].f_ctable->GetConstantByName(NULL, "$IN.frame_count");
|
|
|
|
prg[i].frame_dir_f = prg[i].f_ctable->GetConstantByName(NULL, "$IN.frame_direction");
|
|
|
|
prg[i].vid_size_v = prg[i].v_ctable->GetConstantByName(NULL, "$IN.video_size");
|
|
|
|
prg[i].tex_size_v = prg[i].v_ctable->GetConstantByName(NULL, "$IN.texture_size");
|
|
|
|
prg[i].out_size_v = prg[i].v_ctable->GetConstantByName(NULL, "$IN.output_size");
|
|
|
|
prg[i].frame_cnt_v = prg[i].v_ctable->GetConstantByName(NULL, "$IN.frame_count");
|
|
|
|
prg[i].frame_dir_v = prg[i].v_ctable->GetConstantByName(NULL, "$IN.frame_direction");
|
|
|
|
prg[i].mvp = prg[i].v_ctable->GetConstantByName(NULL, "$modelViewProj");
|
2012-05-23 21:36:42 +00:00
|
|
|
prg[i].mvp_val = XMMatrixIdentity();
|
2012-05-21 16:01:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool hlsl_load_shader(unsigned index, const char *path)
|
|
|
|
{
|
|
|
|
bool retval = true;
|
|
|
|
|
|
|
|
if (!hlsl_active || index == 0)
|
|
|
|
retval = false;
|
|
|
|
|
2012-05-23 21:27:09 +00:00
|
|
|
// FIXME: This could cause corruption issues if prg[index] == prg[0]
|
|
|
|
// (Set earlier if path == NULL).
|
2012-05-21 16:01:51 +00:00
|
|
|
if(retval)
|
|
|
|
{
|
|
|
|
if (path)
|
|
|
|
{
|
|
|
|
if (load_program(index, path, true))
|
|
|
|
{
|
|
|
|
set_program_attributes(index);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Always make sure we have a valid shader.
|
|
|
|
prg[index] = prg[0];
|
|
|
|
retval = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
prg[index] = prg[0];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
goto end; // if retval is false, skip to end label
|
|
|
|
|
|
|
|
hlsl_active = true;
|
2012-05-24 07:39:03 +00:00
|
|
|
active_index = index;
|
2012-05-21 16:01:51 +00:00
|
|
|
d3d_device_ptr->SetVertexShader(prg[index].vprg);
|
|
|
|
d3d_device_ptr->SetPixelShader(prg[index].fprg);
|
|
|
|
end:
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2012-04-14 18:33:56 +00:00
|
|
|
static bool load_plain(const char *path)
|
|
|
|
{
|
|
|
|
if (!load_stock())
|
|
|
|
return false;
|
|
|
|
|
2012-04-21 21:25:32 +00:00
|
|
|
RARCH_LOG("Loading HLSL file: %s\n", path);
|
2012-04-14 18:33:56 +00:00
|
|
|
|
2012-05-06 19:57:01 +00:00
|
|
|
if (!load_program(1, path, true))
|
2012-04-14 18:33:56 +00:00
|
|
|
return false;
|
|
|
|
|
2012-05-06 19:57:01 +00:00
|
|
|
if (*g_settings.video.second_pass_shader && g_settings.video.render_to_texture)
|
|
|
|
{
|
|
|
|
if (!load_program(2, g_settings.video.second_pass_shader, true))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
hlsl_shader_num = 2;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
prg[2] = prg[0];
|
|
|
|
hlsl_shader_num = 1;
|
|
|
|
}
|
|
|
|
|
2012-04-14 18:33:56 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hlsl_deinit_progs(void)
|
2012-04-17 03:12:03 +00:00
|
|
|
{
|
2012-05-06 21:40:29 +00:00
|
|
|
for (unsigned i = 1; i < RARCH_HLSL_MAX_SHADERS; i++)
|
2012-04-17 03:12:03 +00:00
|
|
|
{
|
2012-05-06 21:40:29 +00:00
|
|
|
if (prg[i].fprg && prg[i].fprg != prg[0].fprg)
|
2012-04-17 03:12:03 +00:00
|
|
|
prg[i].fprg->Release();
|
2012-05-06 21:40:29 +00:00
|
|
|
if (prg[i].vprg && prg[i].vprg != prg[0].vprg)
|
2012-04-17 03:12:03 +00:00
|
|
|
prg[i].vprg->Release();
|
2012-05-06 21:40:29 +00:00
|
|
|
|
2012-05-07 03:34:41 +00:00
|
|
|
prg[i].fprg = NULL;
|
|
|
|
prg[i].vprg = NULL;
|
2012-04-17 03:12:03 +00:00
|
|
|
}
|
2012-05-06 21:40:29 +00:00
|
|
|
|
|
|
|
if (prg[0].fprg)
|
|
|
|
prg[0].fprg->Release();
|
|
|
|
if (prg[0].vprg)
|
|
|
|
prg[0].vprg->Release();
|
2012-05-07 03:34:41 +00:00
|
|
|
prg[0].fprg = NULL;
|
|
|
|
prg[0].vprg = NULL;
|
2012-04-17 03:12:03 +00:00
|
|
|
}
|
2012-04-14 18:33:56 +00:00
|
|
|
|
|
|
|
static void hlsl_deinit_state(void)
|
|
|
|
{
|
|
|
|
hlsl_active = false;
|
2012-05-06 21:40:29 +00:00
|
|
|
hlsl_deinit_progs();
|
|
|
|
memset(prg, 0, sizeof(prg));
|
2012-04-14 18:33:56 +00:00
|
|
|
|
2012-04-14 20:05:56 +00:00
|
|
|
d3d_device_ptr = NULL;
|
2012-04-14 18:33:56 +00:00
|
|
|
}
|
|
|
|
|
2012-04-14 19:45:43 +00:00
|
|
|
static bool load_preset(const char *path)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-04-14 20:05:56 +00:00
|
|
|
bool hlsl_init(const char *path, IDirect3DDevice9 * device_ptr)
|
2012-04-14 18:33:56 +00:00
|
|
|
{
|
2012-04-16 16:11:20 +00:00
|
|
|
if (!device_ptr)
|
2012-04-15 16:13:24 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
d3d_device_ptr = device_ptr;
|
2012-04-14 20:05:56 +00:00
|
|
|
|
2012-04-14 18:33:56 +00:00
|
|
|
if (strstr(path, ".cgp"))
|
|
|
|
{
|
|
|
|
if (!load_preset(path))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!load_plain(path))
|
|
|
|
return false;
|
|
|
|
}
|
2012-05-06 19:57:01 +00:00
|
|
|
for(unsigned i = 1; i <= hlsl_shader_num; i++)
|
|
|
|
set_program_attributes(i);
|
2012-04-14 18:33:56 +00:00
|
|
|
|
2012-05-06 19:57:01 +00:00
|
|
|
active_index = 1;
|
|
|
|
d3d_device_ptr->SetVertexShader(prg[active_index].vprg);
|
|
|
|
d3d_device_ptr->SetPixelShader(prg[active_index].fprg);
|
2012-04-15 14:34:09 +00:00
|
|
|
|
2012-04-14 18:33:56 +00:00
|
|
|
hlsl_active = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-04-14 20:05:56 +00:00
|
|
|
void hlsl_use(unsigned index)
|
2012-04-14 18:33:56 +00:00
|
|
|
{
|
2012-05-06 19:57:01 +00:00
|
|
|
if (hlsl_active)
|
|
|
|
{
|
|
|
|
active_index = index;
|
|
|
|
d3d_device_ptr->SetVertexShader(prg[index].vprg);
|
|
|
|
d3d_device_ptr->SetPixelShader(prg[index].fprg);
|
|
|
|
}
|
2012-04-14 18:33:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Full deinit.
|
|
|
|
void hlsl_deinit(void)
|
|
|
|
{
|
|
|
|
if (!hlsl_active)
|
|
|
|
return;
|
|
|
|
|
|
|
|
hlsl_deinit_state();
|
|
|
|
}
|
2012-04-16 16:11:20 +00:00
|
|
|
|