2013-04-18 03:29:41 +00:00
|
|
|
// Copyright 2013 Dolphin Emulator Project
|
|
|
|
// Licensed under GPLv2
|
|
|
|
// Refer to the license.txt file included.
|
2010-06-09 01:37:08 +00:00
|
|
|
|
|
|
|
#include "Common.h"
|
|
|
|
#include "MemoryUtil.h"
|
|
|
|
|
|
|
|
#include <VideoCommon.h>
|
|
|
|
|
|
|
|
#include "BPMemLoader.h"
|
|
|
|
#include "HwRasterizer.h"
|
|
|
|
#include "NativeVertexFormat.h"
|
|
|
|
#include "DebugUtil.h"
|
|
|
|
|
|
|
|
#define TEMP_SIZE (1024*1024*4)
|
|
|
|
|
|
|
|
namespace HwRasterizer
|
|
|
|
{
|
2012-12-26 16:33:45 +00:00
|
|
|
float efbHalfWidth;
|
|
|
|
float efbHalfHeight;
|
|
|
|
bool hasTexture;
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2013-01-30 05:24:51 +00:00
|
|
|
u8 *temp;
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-17 20:54:20 +00:00
|
|
|
// Programs
|
|
|
|
static GLuint colProg, texProg, clearProg;
|
2012-12-26 16:33:45 +00:00
|
|
|
|
2012-12-17 20:54:20 +00:00
|
|
|
// Color
|
|
|
|
static GLint col_apos = -1, col_atex = -1;
|
|
|
|
// Tex
|
|
|
|
static GLint tex_apos = -1, tex_atex = -1, tex_utex = -1;
|
|
|
|
// Clear shader
|
|
|
|
static GLint clear_apos = -1, clear_ucol = -1;
|
|
|
|
|
|
|
|
void CreateShaders()
|
|
|
|
{
|
|
|
|
// Color Vertices
|
|
|
|
static const char *fragcolText =
|
|
|
|
"varying " PREC " vec4 TexCoordOut;\n"
|
|
|
|
"void main() {\n"
|
|
|
|
" gl_FragColor = TexCoordOut;\n"
|
|
|
|
"}\n";
|
|
|
|
// Texture Vertices
|
|
|
|
static const char *fragtexText =
|
|
|
|
"varying " PREC " vec4 TexCoordOut;\n"
|
|
|
|
"uniform " TEXTYPE " Texture;\n"
|
|
|
|
"void main() {\n"
|
2012-12-26 16:33:45 +00:00
|
|
|
" gl_FragColor = " TEXFUNC "(Texture, TexCoordOut.xy);\n"
|
2012-12-17 20:54:20 +00:00
|
|
|
"}\n";
|
|
|
|
// Clear shader
|
2013-01-30 05:24:51 +00:00
|
|
|
static const char *fragclearText =
|
2012-12-30 03:50:07 +00:00
|
|
|
"uniform " PREC " vec4 Color;\n"
|
2012-12-17 20:54:20 +00:00
|
|
|
"void main() {\n"
|
|
|
|
" gl_FragColor = Color;\n"
|
|
|
|
"}\n";
|
|
|
|
// Generic passthrough vertice shaders
|
|
|
|
static const char *vertShaderText =
|
|
|
|
"attribute vec4 pos;\n"
|
|
|
|
"attribute vec4 TexCoordIn;\n "
|
|
|
|
"varying vec4 TexCoordOut;\n "
|
|
|
|
"void main() {\n"
|
2012-12-26 16:33:45 +00:00
|
|
|
" gl_Position = pos;\n"
|
2012-12-17 20:54:20 +00:00
|
|
|
" TexCoordOut = TexCoordIn;\n"
|
|
|
|
"}\n";
|
2013-01-30 05:24:51 +00:00
|
|
|
static const char *vertclearText =
|
2012-12-17 20:54:20 +00:00
|
|
|
"attribute vec4 pos;\n"
|
|
|
|
"void main() {\n"
|
|
|
|
" gl_Position = pos;\n"
|
|
|
|
"}\n";
|
|
|
|
|
|
|
|
// Color Program
|
|
|
|
colProg = OpenGL_CompileProgram(vertShaderText, fragcolText);
|
|
|
|
|
|
|
|
// Texture Program
|
|
|
|
texProg = OpenGL_CompileProgram(vertShaderText, fragtexText);
|
|
|
|
|
|
|
|
// Clear Program
|
|
|
|
clearProg = OpenGL_CompileProgram(vertclearText, fragclearText);
|
|
|
|
|
|
|
|
// Color attributes
|
|
|
|
col_apos = glGetAttribLocation(colProg, "pos");
|
2013-01-30 05:24:51 +00:00
|
|
|
col_atex = glGetAttribLocation(colProg, "TexCoordIn");
|
2012-12-17 20:54:20 +00:00
|
|
|
// Texture attributes
|
|
|
|
tex_apos = glGetAttribLocation(texProg, "pos");
|
2013-01-30 05:24:51 +00:00
|
|
|
tex_atex = glGetAttribLocation(texProg, "TexCoordIn");
|
|
|
|
tex_utex = glGetUniformLocation(texProg, "Texture");
|
2012-12-17 20:54:20 +00:00
|
|
|
// Clear attributes
|
|
|
|
clear_apos = glGetAttribLocation(clearProg, "pos");
|
|
|
|
clear_ucol = glGetUniformLocation(clearProg, "Color");
|
|
|
|
}
|
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
void Init()
|
|
|
|
{
|
|
|
|
efbHalfWidth = EFB_WIDTH / 2.0f;
|
|
|
|
efbHalfHeight = 480 / 2.0f;
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
temp = (u8*)AllocateMemoryPages(TEMP_SIZE);
|
|
|
|
}
|
2012-12-17 20:54:20 +00:00
|
|
|
void Shutdown()
|
|
|
|
{
|
|
|
|
glDeleteProgram(colProg);
|
|
|
|
glDeleteProgram(texProg);
|
|
|
|
glDeleteProgram(clearProg);
|
|
|
|
}
|
|
|
|
void Prepare()
|
|
|
|
{
|
|
|
|
//legacy multitexturing: select texture channel only.
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4-byte pixel alignment
|
|
|
|
#ifndef USE_GLES
|
|
|
|
glShadeModel(GL_SMOOTH);
|
|
|
|
glDisable(GL_BLEND);
|
|
|
|
glClearDepth(1.0f);
|
|
|
|
glEnable(GL_SCISSOR_TEST);
|
|
|
|
glDisable(GL_LIGHTING);
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
|
|
glLoadIdentity();
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
glLoadIdentity();
|
|
|
|
|
2013-01-30 05:24:51 +00:00
|
|
|
glClientActiveTexture(GL_TEXTURE0);
|
2012-12-17 20:54:20 +00:00
|
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
|
|
glEnable(GL_TEXTURE_RECTANGLE_ARB);
|
|
|
|
glStencilFunc(GL_ALWAYS, 0, 0);
|
|
|
|
glDisable(GL_STENCIL_TEST);
|
|
|
|
#endif
|
|
|
|
// used by hw rasterizer if it enables blending and depth test
|
|
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
glDepthFunc(GL_LEQUAL);
|
|
|
|
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
|
|
CreateShaders();
|
|
|
|
GL_REPORT_ERRORD();
|
|
|
|
}
|
|
|
|
static float width, height;
|
2012-12-26 16:33:45 +00:00
|
|
|
void LoadTexture()
|
|
|
|
{
|
|
|
|
FourTexUnits &texUnit = bpmem.tex[0];
|
|
|
|
u32 imageAddr = texUnit.texImage3[0].image_base;
|
2012-12-17 20:54:20 +00:00
|
|
|
// Texture Rectangle uses pixel coordinates
|
|
|
|
// While GLES uses texture coordinates
|
|
|
|
#ifdef USE_GLES
|
|
|
|
width = texUnit.texImage0[0].width;
|
|
|
|
height = texUnit.texImage0[0].height;
|
|
|
|
#else
|
|
|
|
width = 1;
|
|
|
|
height = 1;
|
|
|
|
#endif
|
2012-12-26 16:33:45 +00:00
|
|
|
TexCacheEntry &cacheEntry = textures[imageAddr];
|
|
|
|
cacheEntry.Update();
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-17 20:54:20 +00:00
|
|
|
glBindTexture(TEX2D, cacheEntry.texture);
|
|
|
|
glTexParameteri(TEX2D, GL_TEXTURE_MAG_FILTER, texUnit.texMode0[0].mag_filter ? GL_LINEAR : GL_NEAREST);
|
|
|
|
glTexParameteri(TEX2D, GL_TEXTURE_MIN_FILTER, (texUnit.texMode0[0].min_filter >= 4) ? GL_LINEAR : GL_NEAREST);
|
|
|
|
GL_REPORT_ERRORD();
|
2012-12-26 16:33:45 +00:00
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
void BeginTriangles()
|
|
|
|
{
|
|
|
|
// disabling depth test sometimes allows more things to be visible
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
glEnable(GL_BLEND);
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
hasTexture = bpmem.tevorders[0].enable0;
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
if (hasTexture)
|
2013-01-30 05:24:51 +00:00
|
|
|
LoadTexture();
|
2012-12-26 16:33:45 +00:00
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
void EndTriangles()
|
|
|
|
{
|
|
|
|
glBindTexture(TEX2D, 0);
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
|
|
glDisable(GL_BLEND);
|
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
void DrawColorVertex(OutputVertexData *v0, OutputVertexData *v1, OutputVertexData *v2)
|
|
|
|
{
|
2012-12-17 20:54:20 +00:00
|
|
|
float x0 = (v0->screenPosition.x / efbHalfWidth) - 1.0f;
|
|
|
|
float y0 = 1.0f - (v0->screenPosition.y / efbHalfHeight);
|
|
|
|
float z0 = v0->screenPosition.z / (float)0x00ffffff;
|
|
|
|
|
|
|
|
float x1 = (v1->screenPosition.x / efbHalfWidth) - 1.0f;
|
|
|
|
float y1 = 1.0f - (v1->screenPosition.y / efbHalfHeight);
|
|
|
|
float z1 = v1->screenPosition.z / (float)0x00ffffff;
|
2012-12-26 16:33:45 +00:00
|
|
|
|
2012-12-17 20:54:20 +00:00
|
|
|
float x2 = (v2->screenPosition.x / efbHalfWidth) - 1.0f;
|
|
|
|
float y2 = 1.0f - (v2->screenPosition.y / efbHalfHeight);
|
|
|
|
float z2 = v2->screenPosition.z / (float)0x00ffffff;
|
2012-12-26 16:33:45 +00:00
|
|
|
|
2012-12-17 20:54:20 +00:00
|
|
|
float r0 = v0->color[0][OutputVertexData::RED_C] / 255.0f;
|
2013-01-30 05:24:51 +00:00
|
|
|
float g0 = v0->color[0][OutputVertexData::GRN_C] / 255.0f;
|
2012-12-17 20:54:20 +00:00
|
|
|
float b0 = v0->color[0][OutputVertexData::BLU_C] / 255.0f;
|
|
|
|
|
|
|
|
float r1 = v1->color[0][OutputVertexData::RED_C] / 255.0f;
|
2013-01-30 05:24:51 +00:00
|
|
|
float g1 = v1->color[0][OutputVertexData::GRN_C] / 255.0f;
|
2012-12-17 20:54:20 +00:00
|
|
|
float b1 = v1->color[0][OutputVertexData::BLU_C] / 255.0f;
|
|
|
|
|
|
|
|
float r2 = v2->color[0][OutputVertexData::RED_C] / 255.0f;
|
2013-01-30 05:24:51 +00:00
|
|
|
float g2 = v2->color[0][OutputVertexData::GRN_C] / 255.0f;
|
2012-12-17 20:54:20 +00:00
|
|
|
float b2 = v2->color[0][OutputVertexData::BLU_C] / 255.0f;
|
|
|
|
|
|
|
|
static const GLfloat verts[3][3] = {
|
|
|
|
{ x0, y0, z0 },
|
|
|
|
{ x1, y1, z1 },
|
|
|
|
{ x2, y2, z2 }
|
|
|
|
};
|
|
|
|
static const GLfloat col[3][4] = {
|
2012-12-26 16:33:45 +00:00
|
|
|
{ r0, g0, b0, 1.0f },
|
|
|
|
{ r1, g1, b1, 1.0f },
|
|
|
|
{ r2, g2, b2, 1.0f }
|
2012-12-17 20:54:20 +00:00
|
|
|
};
|
|
|
|
{
|
|
|
|
glUseProgram(colProg);
|
|
|
|
glEnableVertexAttribArray(col_apos);
|
|
|
|
glEnableVertexAttribArray(col_atex);
|
|
|
|
|
|
|
|
glVertexAttribPointer(col_apos, 3, GL_FLOAT, GL_FALSE, 0, verts);
|
|
|
|
glVertexAttribPointer(col_atex, 4, GL_FLOAT, GL_FALSE, 0, col);
|
|
|
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
|
|
glDisableVertexAttribArray(col_atex);
|
|
|
|
glDisableVertexAttribArray(col_apos);
|
|
|
|
}
|
|
|
|
GL_REPORT_ERRORD();
|
2012-12-26 16:33:45 +00:00
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
void DrawTextureVertex(OutputVertexData *v0, OutputVertexData *v1, OutputVertexData *v2)
|
|
|
|
{
|
2012-12-17 20:54:20 +00:00
|
|
|
float x0 = (v0->screenPosition.x / efbHalfWidth) - 1.0f;
|
|
|
|
float y0 = 1.0f - (v0->screenPosition.y / efbHalfHeight);
|
|
|
|
float z0 = v0->screenPosition.z;
|
|
|
|
|
|
|
|
float x1 = (v1->screenPosition.x / efbHalfWidth) - 1.0f;
|
|
|
|
float y1 = 1.0f - (v1->screenPosition.y / efbHalfHeight);
|
|
|
|
float z1 = v1->screenPosition.z;
|
2012-12-26 16:33:45 +00:00
|
|
|
|
2012-12-17 20:54:20 +00:00
|
|
|
float x2 = (v2->screenPosition.x / efbHalfWidth) - 1.0f;
|
|
|
|
float y2 = 1.0f - (v2->screenPosition.y / efbHalfHeight);
|
|
|
|
float z2 = v2->screenPosition.z;
|
|
|
|
|
|
|
|
float s0 = v0->texCoords[0].x / width;
|
|
|
|
float t0 = v0->texCoords[0].y / height;
|
|
|
|
|
|
|
|
float s1 = v1->texCoords[0].x / width;
|
|
|
|
float t1 = v1->texCoords[0].y / height;
|
|
|
|
|
|
|
|
float s2 = v2->texCoords[0].x / width;
|
2012-12-26 16:33:45 +00:00
|
|
|
float t2 = v2->texCoords[0].y / height;
|
2012-12-17 20:54:20 +00:00
|
|
|
|
|
|
|
static const GLfloat verts[3][3] = {
|
|
|
|
{ x0, y0, z0 },
|
|
|
|
{ x1, y1, z1 },
|
|
|
|
{ x2, y2, z2 }
|
|
|
|
};
|
|
|
|
static const GLfloat tex[3][2] = {
|
|
|
|
{ s0, t0 },
|
|
|
|
{ s1, t1 },
|
|
|
|
{ s2, t2 }
|
|
|
|
};
|
|
|
|
{
|
|
|
|
glUseProgram(texProg);
|
|
|
|
glEnableVertexAttribArray(tex_apos);
|
|
|
|
glEnableVertexAttribArray(tex_atex);
|
|
|
|
|
|
|
|
glVertexAttribPointer(tex_apos, 3, GL_FLOAT, GL_FALSE, 0, verts);
|
|
|
|
glVertexAttribPointer(tex_atex, 2, GL_FLOAT, GL_FALSE, 0, tex);
|
|
|
|
glUniform1i(tex_utex, 0);
|
|
|
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
|
|
|
glDisableVertexAttribArray(tex_atex);
|
|
|
|
glDisableVertexAttribArray(tex_apos);
|
|
|
|
}
|
|
|
|
GL_REPORT_ERRORD();
|
2012-12-26 16:33:45 +00:00
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
void DrawTriangleFrontFace(OutputVertexData *v0, OutputVertexData *v1, OutputVertexData *v2)
|
|
|
|
{
|
2012-12-17 20:54:20 +00:00
|
|
|
if (hasTexture)
|
|
|
|
DrawTextureVertex(v0, v1, v2);
|
|
|
|
else
|
|
|
|
DrawColorVertex(v0, v1, v2);
|
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
void Clear()
|
|
|
|
{
|
|
|
|
u8 r = (bpmem.clearcolorAR & 0x00ff);
|
|
|
|
u8 g = (bpmem.clearcolorGB & 0xff00) >> 8;
|
|
|
|
u8 b = (bpmem.clearcolorGB & 0x00ff);
|
|
|
|
u8 a = (bpmem.clearcolorAR & 0xff00) >> 8;
|
|
|
|
|
|
|
|
GLfloat left = (GLfloat)bpmem.copyTexSrcXY.x / efbHalfWidth - 1.0f;
|
|
|
|
GLfloat top = 1.0f - (GLfloat)bpmem.copyTexSrcXY.y / efbHalfHeight;
|
|
|
|
GLfloat right = (GLfloat)(left + bpmem.copyTexSrcWH.x + 1) / efbHalfWidth - 1.0f;
|
|
|
|
GLfloat bottom = 1.0f - (GLfloat)(top + bpmem.copyTexSrcWH.y + 1) / efbHalfHeight;
|
|
|
|
GLfloat depth = (GLfloat)bpmem.clearZValue / (GLfloat)0x00ffffff;
|
2012-12-17 20:54:20 +00:00
|
|
|
static const GLfloat verts[4][3] = {
|
|
|
|
{ left, top, depth },
|
|
|
|
{ right, top, depth },
|
|
|
|
{ right, bottom, depth },
|
|
|
|
{ left, bottom, depth }
|
|
|
|
};
|
|
|
|
{
|
|
|
|
glUseProgram(clearProg);
|
|
|
|
glVertexAttribPointer(clear_apos, 3, GL_FLOAT, GL_FALSE, 0, verts);
|
|
|
|
glUniform4f(clear_ucol, r, g, b, a);
|
|
|
|
glEnableVertexAttribArray(col_apos);
|
|
|
|
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
|
|
|
glDisableVertexAttribArray(col_apos);
|
|
|
|
}
|
|
|
|
GL_REPORT_ERRORD();
|
2012-12-26 16:33:45 +00:00
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
TexCacheEntry::TexCacheEntry()
|
|
|
|
{
|
|
|
|
Create();
|
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
void TexCacheEntry::Create()
|
|
|
|
{
|
|
|
|
FourTexUnits &texUnit = bpmem.tex[0];
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
texImage0.hex = texUnit.texImage0[0].hex;
|
|
|
|
texImage1.hex = texUnit.texImage1[0].hex;
|
|
|
|
texImage2.hex = texUnit.texImage2[0].hex;
|
|
|
|
texImage3.hex = texUnit.texImage3[0].hex;
|
|
|
|
texTlut.hex = texUnit.texTlut[0].hex;
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2013-01-30 05:24:51 +00:00
|
|
|
int image_width = texImage0.width;
|
|
|
|
int image_height = texImage0.height;
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2013-01-30 05:24:51 +00:00
|
|
|
DebugUtil::GetTextureBGRA(temp, 0, 0, image_width, image_height);
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
glGenTextures(1, (GLuint *)&texture);
|
2012-12-17 20:54:20 +00:00
|
|
|
glBindTexture(TEX2D, texture);
|
|
|
|
#ifdef USE_GLES
|
2013-01-30 05:24:51 +00:00
|
|
|
glTexImage2D(TEX2D, 0, GL_RGBA, (GLsizei)image_width, (GLsizei)image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp);
|
2012-12-17 20:54:20 +00:00
|
|
|
#else
|
2013-01-30 05:24:51 +00:00
|
|
|
glTexImage2D(TEX2D, 0, GL_RGBA8, (GLsizei)image_width, (GLsizei)image_height, 0, GL_BGRA, GL_UNSIGNED_BYTE, temp);
|
2012-12-17 20:54:20 +00:00
|
|
|
#endif
|
|
|
|
GL_REPORT_ERRORD();
|
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
void TexCacheEntry::Destroy()
|
|
|
|
{
|
|
|
|
if (texture == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
glDeleteTextures(1, &texture);
|
|
|
|
texture = 0;
|
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
|
2012-12-26 16:33:45 +00:00
|
|
|
void TexCacheEntry::Update()
|
|
|
|
{
|
|
|
|
FourTexUnits &texUnit = bpmem.tex[0];
|
|
|
|
|
|
|
|
// extra checks cause textures to be reloaded much more
|
|
|
|
if (texUnit.texImage0[0].hex != texImage0.hex ||
|
2013-04-14 03:54:02 +00:00
|
|
|
// texUnit.texImage1[0].hex != texImage1.hex ||
|
|
|
|
// texUnit.texImage2[0].hex != texImage2.hex ||
|
|
|
|
texUnit.texImage3[0].hex != texImage3.hex ||
|
|
|
|
texUnit.texTlut[0].hex != texTlut.hex)
|
2012-12-26 16:33:45 +00:00
|
|
|
{
|
|
|
|
Destroy();
|
|
|
|
Create();
|
|
|
|
}
|
|
|
|
}
|
2010-06-09 01:37:08 +00:00
|
|
|
}
|
|
|
|
|