blob: 13ff4ca61e341e70780495331a51fff66725618e [file] [log] [blame]
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +00001//
2// Copyright (c) 2002-2010 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6
7// Blit.cpp: Surface copy utility class.
8
daniel@transgaming.combbf56f72010-04-20 18:52:13 +00009#include "libGLESv2/Blit.h"
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000010
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000011#include "common/debug.h"
12
daniel@transgaming.combbf56f72010-04-20 18:52:13 +000013#include "libGLESv2/main.h"
14
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000015namespace
16{
apatrick@chromium.org99fbbe82012-07-19 19:11:24 +000017#include "libGLESv2/shaders/standardvs.h"
18#include "libGLESv2/shaders/flipyvs.h"
19#include "libGLESv2/shaders/passthroughps.h"
20#include "libGLESv2/shaders/luminanceps.h"
21#include "libGLESv2/shaders/componentmaskps.h"
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000022
apatrick@chromium.org99fbbe82012-07-19 19:11:24 +000023const BYTE* const g_shaderCode[] =
24{
25 g_vs20_standardvs,
26 g_vs20_flipyvs,
27 g_ps20_passthroughps,
28 g_ps20_luminanceps,
29 g_ps20_componentmaskps
30};
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000031
apatrick@chromium.org99fbbe82012-07-19 19:11:24 +000032const size_t g_shaderSize[] =
33{
34 sizeof(g_vs20_standardvs),
35 sizeof(g_vs20_flipyvs),
36 sizeof(g_ps20_passthroughps),
37 sizeof(g_ps20_luminanceps),
38 sizeof(g_ps20_componentmaskps)
39};
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000040}
41
42namespace gl
43{
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000044Blit::Blit(Context *context)
daniel@transgaming.com1ddd1dd2010-05-11 02:29:34 +000045 : mContext(context), mQuadVertexBuffer(NULL), mQuadVertexDeclaration(NULL), mSavedRenderTarget(NULL), mSavedDepthStencil(NULL), mSavedStateBlock(NULL)
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000046{
47 initGeometry();
48 memset(mCompiledShaders, 0, sizeof(mCompiledShaders));
49}
50
51Blit::~Blit()
52{
daniel@transgaming.com1ddd1dd2010-05-11 02:29:34 +000053 if (mSavedStateBlock) mSavedStateBlock->Release();
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000054 if (mQuadVertexBuffer) mQuadVertexBuffer->Release();
55 if (mQuadVertexDeclaration) mQuadVertexDeclaration->Release();
56
57 for (int i = 0; i < SHADER_COUNT; i++)
58 {
59 if (mCompiledShaders[i])
60 {
61 mCompiledShaders[i]->Release();
62 }
63 }
64}
65
66void Blit::initGeometry()
67{
68 static const float quad[] =
69 {
70 -1, -1,
71 -1, 1,
72 1, -1,
73 1, 1
74 };
75
76 IDirect3DDevice9 *device = getDevice();
77
daniel@transgaming.comaa614602011-04-28 16:20:58 +000078 HRESULT result = device->CreateVertexBuffer(sizeof(quad), D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &mQuadVertexBuffer, NULL);
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000079
daniel@transgaming.comaa614602011-04-28 16:20:58 +000080 if (FAILED(result))
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000081 {
daniel@transgaming.comaa614602011-04-28 16:20:58 +000082 ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000083 return error(GL_OUT_OF_MEMORY);
84 }
85
daniel@transgaming.comaa614602011-04-28 16:20:58 +000086 void *lockPtr = NULL;
87 result = mQuadVertexBuffer->Lock(0, 0, &lockPtr, 0);
88
89 if (FAILED(result) || lockPtr == NULL)
90 {
91 ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
92 return error(GL_OUT_OF_MEMORY);
93 }
94
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +000095 memcpy(lockPtr, quad, sizeof(quad));
96 mQuadVertexBuffer->Unlock();
97
98 static const D3DVERTEXELEMENT9 elements[] =
99 {
100 { 0, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
101 D3DDECL_END()
102 };
103
daniel@transgaming.comaa614602011-04-28 16:20:58 +0000104 result = device->CreateVertexDeclaration(elements, &mQuadVertexDeclaration);
105
106 if (FAILED(result))
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000107 {
daniel@transgaming.comaa614602011-04-28 16:20:58 +0000108 ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000109 return error(GL_OUT_OF_MEMORY);
110 }
111}
112
113template <class D3DShaderType>
114bool Blit::setShader(ShaderId source, const char *profile,
apatrick@chromium.org3cfd7222012-07-13 22:36:58 +0000115 D3DShaderType *(egl::Display::*createShader)(const DWORD *, size_t length),
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000116 HRESULT (WINAPI IDirect3DDevice9::*setShader)(D3DShaderType*))
117{
apatrick@chromium.org3cfd7222012-07-13 22:36:58 +0000118 egl::Display *display = getDisplay();
119 IDirect3DDevice9 *device = display->getDevice();
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000120
121 D3DShaderType *shader;
122
123 if (mCompiledShaders[source] != NULL)
124 {
125 shader = static_cast<D3DShaderType*>(mCompiledShaders[source]);
126 }
127 else
128 {
apatrick@chromium.org99fbbe82012-07-19 19:11:24 +0000129 const BYTE* shaderCode = g_shaderCode[source];
130 size_t shaderSize = g_shaderSize[source];
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000131
apatrick@chromium.org99fbbe82012-07-19 19:11:24 +0000132 shader = (display->*createShader)(reinterpret_cast<const DWORD*>(shaderCode), shaderSize);
133 if (!shader)
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000134 {
apatrick@chromium.org99fbbe82012-07-19 19:11:24 +0000135 ERR("Failed to create shader for blit operation");
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000136 return false;
137 }
138
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000139 mCompiledShaders[source] = shader;
140 }
141
142 HRESULT hr = (device->*setShader)(shader);
143
144 if (FAILED(hr))
145 {
apatrick@chromium.org99fbbe82012-07-19 19:11:24 +0000146 ERR("Failed to set shader for blit operation");
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000147 return false;
148 }
149
150 return true;
151}
152
153bool Blit::setVertexShader(ShaderId shader)
154{
apatrick@chromium.org99fbbe82012-07-19 19:11:24 +0000155 return setShader<IDirect3DVertexShader9>(shader, "vs_2_0", &egl::Display::createVertexShader, &IDirect3DDevice9::SetVertexShader);
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000156}
157
158bool Blit::setPixelShader(ShaderId shader)
159{
apatrick@chromium.org99fbbe82012-07-19 19:11:24 +0000160 return setShader<IDirect3DPixelShader9>(shader, "ps_2_0", &egl::Display::createPixelShader, &IDirect3DDevice9::SetPixelShader);
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000161}
162
daniel@transgaming.com8fd99e22010-04-20 18:52:00 +0000163RECT Blit::getSurfaceRect(IDirect3DSurface9 *surface) const
164{
165 D3DSURFACE_DESC desc;
166 surface->GetDesc(&desc);
167
168 RECT rect;
169 rect.left = 0;
170 rect.top = 0;
171 rect.right = desc.Width;
172 rect.bottom = desc.Height;
173
174 return rect;
175}
176
177bool Blit::boxFilter(IDirect3DSurface9 *source, IDirect3DSurface9 *dest)
178{
179 IDirect3DTexture9 *texture = copySurfaceToTexture(source, getSurfaceRect(source));
180 if (!texture)
181 {
182 return false;
183 }
184
185 IDirect3DDevice9 *device = getDevice();
daniel@transgaming.com1ddd1dd2010-05-11 02:29:34 +0000186
187 saveState();
188
daniel@transgaming.com8fd99e22010-04-20 18:52:00 +0000189 device->SetTexture(0, texture);
190 device->SetRenderTarget(0, dest);
191
192 setVertexShader(SHADER_VS_STANDARD);
193 setPixelShader(SHADER_PS_PASSTHROUGH);
194
195 setCommonBlitState();
196 device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
197 device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
198
199 setViewport(getSurfaceRect(dest), 0, 0);
200
201 render();
202
203 texture->Release();
204
daniel@transgaming.com1ddd1dd2010-05-11 02:29:34 +0000205 restoreState();
206
daniel@transgaming.com8fd99e22010-04-20 18:52:00 +0000207 return true;
208}
209
daniel@transgaming.comeef864a2011-04-22 11:33:27 +0000210bool Blit::copy(IDirect3DSurface9 *source, const RECT &sourceRect, GLenum destFormat, GLint xoffset, GLint yoffset, IDirect3DSurface9 *dest)
211{
212 IDirect3DDevice9 *device = getDevice();
213
214 D3DSURFACE_DESC sourceDesc;
215 D3DSURFACE_DESC destDesc;
216 source->GetDesc(&sourceDesc);
217 dest->GetDesc(&destDesc);
218
219 if (sourceDesc.Format == destDesc.Format && destDesc.Usage & D3DUSAGE_RENDERTARGET) // Can use StretchRect
220 {
221 RECT destRect = {xoffset, yoffset, xoffset + (sourceRect.right - sourceRect.left), yoffset + (sourceRect.bottom - sourceRect.top)};
222 HRESULT result = device->StretchRect(source, &sourceRect, dest, &destRect, D3DTEXF_POINT);
223
224 if (FAILED(result))
225 {
226 ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
227 return error(GL_OUT_OF_MEMORY, false);
228 }
229 }
230 else
231 {
232 return formatConvert(source, sourceRect, destFormat, xoffset, yoffset, dest);
233 }
234
235 return true;
236}
237
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000238bool Blit::formatConvert(IDirect3DSurface9 *source, const RECT &sourceRect, GLenum destFormat, GLint xoffset, GLint yoffset, IDirect3DSurface9 *dest)
239{
240 IDirect3DTexture9 *texture = copySurfaceToTexture(source, sourceRect);
241 if (!texture)
242 {
243 return false;
244 }
245
246 IDirect3DDevice9 *device = getDevice();
247
daniel@transgaming.com1ddd1dd2010-05-11 02:29:34 +0000248 saveState();
249
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000250 device->SetTexture(0, texture);
251 device->SetRenderTarget(0, dest);
252
daniel@transgaming.com8fd99e22010-04-20 18:52:00 +0000253 setViewport(sourceRect, xoffset, yoffset);
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000254
255 setCommonBlitState();
256 if (setFormatConvertShaders(destFormat))
257 {
258 render();
259 }
260
261 texture->Release();
262
daniel@transgaming.com1ddd1dd2010-05-11 02:29:34 +0000263 restoreState();
264
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000265 return true;
266}
267
268bool Blit::setFormatConvertShaders(GLenum destFormat)
269{
270 bool okay = setVertexShader(SHADER_VS_STANDARD);
271
272 switch (destFormat)
273 {
274 default: UNREACHABLE();
275 case GL_RGBA:
daniel@transgaming.coma9198d92010-08-08 04:49:56 +0000276 case GL_BGRA_EXT:
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000277 case GL_RGB:
278 case GL_ALPHA:
279 okay = okay && setPixelShader(SHADER_PS_COMPONENTMASK);
280 break;
281
282 case GL_LUMINANCE:
283 case GL_LUMINANCE_ALPHA:
284 okay = okay && setPixelShader(SHADER_PS_LUMINANCE);
285 break;
286 }
287
288 if (!okay)
289 {
290 return false;
291 }
292
293 enum { X = 0, Y = 1, Z = 2, W = 3 };
294
295 // The meaning of this constant depends on the shader that was selected.
296 // See the shader assembly code above for details.
297 float psConst0[4] = { 0, 0, 0, 0 };
298
299 switch (destFormat)
300 {
301 default: UNREACHABLE();
302 case GL_RGBA:
daniel@transgaming.coma9198d92010-08-08 04:49:56 +0000303 case GL_BGRA_EXT:
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000304 psConst0[X] = 1;
305 psConst0[Z] = 1;
306 break;
307
308 case GL_RGB:
309 psConst0[X] = 1;
310 psConst0[W] = 1;
311 break;
312
313 case GL_ALPHA:
314 psConst0[Z] = 1;
315 break;
316
317 case GL_LUMINANCE:
318 psConst0[Y] = 1;
319 break;
320
321 case GL_LUMINANCE_ALPHA:
322 psConst0[X] = 1;
323 break;
324 }
325
326 getDevice()->SetPixelShaderConstantF(0, psConst0, 1);
327
328 return true;
329}
330
331IDirect3DTexture9 *Blit::copySurfaceToTexture(IDirect3DSurface9 *surface, const RECT &sourceRect)
332{
daniel@transgaming.comd36c6a02010-08-31 12:15:09 +0000333 if (!surface)
334 {
335 return NULL;
336 }
337
daniel@transgaming.comae072af2010-05-05 18:47:28 +0000338 egl::Display *display = getDisplay();
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000339 IDirect3DDevice9 *device = getDevice();
340
341 D3DSURFACE_DESC sourceDesc;
342 surface->GetDesc(&sourceDesc);
343
344 // Copy the render target into a texture
345 IDirect3DTexture9 *texture;
daniel@transgaming.com8fd99e22010-04-20 18:52:00 +0000346 HRESULT result = device->CreateTexture(sourceRect.right - sourceRect.left, sourceRect.bottom - sourceRect.top, 1, D3DUSAGE_RENDERTARGET, sourceDesc.Format, D3DPOOL_DEFAULT, &texture, NULL);
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000347
348 if (FAILED(result))
349 {
350 ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
351 return error(GL_OUT_OF_MEMORY, (IDirect3DTexture9*)NULL);
352 }
353
354 IDirect3DSurface9 *textureSurface;
355 result = texture->GetSurfaceLevel(0, &textureSurface);
356
357 if (FAILED(result))
358 {
359 ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
360 texture->Release();
361 return error(GL_OUT_OF_MEMORY, (IDirect3DTexture9*)NULL);
362 }
363
daniel@transgaming.comae072af2010-05-05 18:47:28 +0000364 display->endScene();
daniel@transgaming.com4c5142c2010-10-15 17:58:27 +0000365 result = device->StretchRect(surface, &sourceRect, textureSurface, NULL, D3DTEXF_NONE);
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000366
367 textureSurface->Release();
368
369 if (FAILED(result))
370 {
371 ASSERT(result == D3DERR_OUTOFVIDEOMEMORY || result == E_OUTOFMEMORY);
372 texture->Release();
373 return error(GL_OUT_OF_MEMORY, (IDirect3DTexture9*)NULL);
374 }
375
376 return texture;
377}
378
daniel@transgaming.com8fd99e22010-04-20 18:52:00 +0000379void Blit::setViewport(const RECT &sourceRect, GLint xoffset, GLint yoffset)
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000380{
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000381 IDirect3DDevice9 *device = getDevice();
382
383 D3DVIEWPORT9 vp;
384 vp.X = xoffset;
385 vp.Y = yoffset;
386 vp.Width = sourceRect.right - sourceRect.left;
daniel@transgaming.com8fd99e22010-04-20 18:52:00 +0000387 vp.Height = sourceRect.bottom - sourceRect.top;
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000388 vp.MinZ = 0.0f;
389 vp.MaxZ = 1.0f;
390 device->SetViewport(&vp);
391
392 float halfPixelAdjust[4] = { -1.0f/vp.Width, 1.0f/vp.Height, 0, 0 };
393 device->SetVertexShaderConstantF(0, halfPixelAdjust, 1);
394}
395
396void Blit::setCommonBlitState()
397{
398 IDirect3DDevice9 *device = getDevice();
399
400 device->SetDepthStencilSurface(NULL);
401
402 device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
403 device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
404 device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
405 device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
406 device->SetRenderState(D3DRS_CLIPPLANEENABLE, 0);
407 device->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALPHA | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_RED);
408 device->SetRenderState(D3DRS_SRGBWRITEENABLE, FALSE);
409 device->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE);
410
411 device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
412 device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
413 device->SetSamplerState(0, D3DSAMP_SRGBTEXTURE, FALSE);
daniel@transgaming.com8fd99e22010-04-20 18:52:00 +0000414 device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
415 device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
daniel@transgaming.com81655a72010-05-20 19:18:17 +0000416
daniel@transgaming.com74d760b2010-11-03 12:27:18 +0000417 RECT scissorRect = {0}; // Scissoring is disabled for flipping, but we need this to capture and restore the old rectangle
418 device->SetScissorRect(&scissorRect);
daniel@transgaming.com8ca9c6e2012-01-27 15:38:54 +0000419
420 for(int i = 0; i < MAX_VERTEX_ATTRIBS; i++)
421 {
422 device->SetStreamSourceFreq(i, 1);
423 }
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000424}
425
426void Blit::render()
427{
daniel@transgaming.comae072af2010-05-05 18:47:28 +0000428 egl::Display *display = getDisplay();
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000429 IDirect3DDevice9 *device = getDevice();
430
431 HRESULT hr = device->SetStreamSource(0, mQuadVertexBuffer, 0, 2 * sizeof(float));
432 hr = device->SetVertexDeclaration(mQuadVertexDeclaration);
433
daniel@transgaming.comae072af2010-05-05 18:47:28 +0000434 display->startScene();
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000435 hr = device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000436}
437
daniel@transgaming.com1ddd1dd2010-05-11 02:29:34 +0000438void Blit::saveState()
439{
440 IDirect3DDevice9 *device = getDevice();
441
442 HRESULT hr;
443
444 device->GetDepthStencilSurface(&mSavedDepthStencil);
445 device->GetRenderTarget(0, &mSavedRenderTarget);
446
447 if (mSavedStateBlock == NULL)
448 {
449 hr = device->BeginStateBlock();
450 ASSERT(SUCCEEDED(hr) || hr == D3DERR_OUTOFVIDEOMEMORY || hr == E_OUTOFMEMORY);
451
452 setCommonBlitState();
453
454 static const float dummyConst[4] = { 0, 0, 0, 0 };
455
456 device->SetVertexShader(NULL);
457 device->SetVertexShaderConstantF(0, dummyConst, 1);
458 device->SetPixelShader(NULL);
459 device->SetPixelShaderConstantF(0, dummyConst, 1);
460
461 D3DVIEWPORT9 dummyVp;
462 dummyVp.X = 0;
463 dummyVp.Y = 0;
464 dummyVp.Width = 1;
465 dummyVp.Height = 1;
466 dummyVp.MinZ = 0;
467 dummyVp.MaxZ = 1;
468
469 device->SetViewport(&dummyVp);
470
471 device->SetTexture(0, NULL);
472
473 device->SetStreamSource(0, mQuadVertexBuffer, 0, 0);
474
475 device->SetVertexDeclaration(mQuadVertexDeclaration);
476
477 hr = device->EndStateBlock(&mSavedStateBlock);
478 ASSERT(SUCCEEDED(hr) || hr == D3DERR_OUTOFVIDEOMEMORY || hr == E_OUTOFMEMORY);
479 }
480
481 ASSERT(mSavedStateBlock != NULL);
482
483 if (mSavedStateBlock != NULL)
484 {
485 hr = mSavedStateBlock->Capture();
486 ASSERT(SUCCEEDED(hr));
487 }
488}
489
490void Blit::restoreState()
491{
492 IDirect3DDevice9 *device = getDevice();
493
494 device->SetDepthStencilSurface(mSavedDepthStencil);
495 if (mSavedDepthStencil != NULL)
496 {
497 mSavedDepthStencil->Release();
498 mSavedDepthStencil = NULL;
499 }
500
501 device->SetRenderTarget(0, mSavedRenderTarget);
502 if (mSavedRenderTarget != NULL)
503 {
504 mSavedRenderTarget->Release();
505 mSavedRenderTarget = NULL;
506 }
507
508 ASSERT(mSavedStateBlock != NULL);
509
510 if (mSavedStateBlock != NULL)
511 {
512 mSavedStateBlock->Apply();
513 }
514}
515
daniel@transgaming.comb8c28ed2010-04-13 03:26:32 +0000516}