blob: 038f2d82772a69e51d12e1cb46e6f1d791b3ced6 [file] [log] [blame]
daniel@transgaming.com32fdf822012-11-28 20:53:30 +00001//
2// Copyright (c) 2012 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// SwapChain11.cpp: Implements a back-end specific class for the D3D11 swap chain.
8
9#include "libGLESv2/renderer/SwapChain11.h"
10
11#include "common/debug.h"
12#include "libGLESv2/utilities.h"
13#include "libGLESv2/renderer/renderer11_utils.h"
14#include "libGLESv2/renderer/Renderer11.h"
15#include "libGLESv2/Context.h"
16#include "libGLESv2/main.h"
17
daniel@transgaming.com8dc8e272013-01-11 04:10:45 +000018#include "libGLESv2/renderer/shaders/compiled/passthrough11vs.h"
19#include "libGLESv2/renderer/shaders/compiled/passthrough11ps.h"
daniel@transgaming.come0970472012-11-28 21:05:07 +000020
daniel@transgaming.com32fdf822012-11-28 20:53:30 +000021namespace rx
22{
23
daniel@transgaming.come0970472012-11-28 21:05:07 +000024struct QuadVertex
25{
26 float x, y;
27 float u, v;
28};
29
30static void setVertex(QuadVertex* vertex, float x, float y, float u, float v)
31{
32 vertex->x = x;
33 vertex->y = y;
34 vertex->u = u;
35 vertex->v = v;
36}
37
daniel@transgaming.com32fdf822012-11-28 20:53:30 +000038SwapChain11::SwapChain11(Renderer11 *renderer, HWND window, HANDLE shareHandle,
39 GLenum backBufferFormat, GLenum depthBufferFormat)
40 : mRenderer(renderer), SwapChain(window, shareHandle, backBufferFormat, depthBufferFormat)
41{
42 mSwapChain = NULL;
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +000043 mBackBufferTexture = NULL;
44 mBackBufferRTView = NULL;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +000045 mOffscreenTexture = NULL;
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +000046 mOffscreenRTView = NULL;
daniel@transgaming.come0970472012-11-28 21:05:07 +000047 mOffscreenSRView = NULL;
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +000048 mDepthStencilTexture = NULL;
49 mDepthStencilDSView = NULL;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +000050 mWidth = -1;
51 mHeight = -1;
52}
53
54SwapChain11::~SwapChain11()
55{
56 release();
57}
58
59void SwapChain11::release()
60{
61 if (mSwapChain)
62 {
63 mSwapChain->Release();
64 mSwapChain = NULL;
65 }
66
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +000067 if (mBackBufferTexture)
daniel@transgaming.com32fdf822012-11-28 20:53:30 +000068 {
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +000069 mBackBufferTexture->Release();
70 mBackBufferTexture = NULL;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +000071 }
72
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +000073 if (mBackBufferRTView)
daniel@transgaming.com32fdf822012-11-28 20:53:30 +000074 {
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +000075 mBackBufferRTView->Release();
76 mBackBufferRTView = NULL;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +000077 }
78
79 if (mOffscreenTexture)
80 {
81 mOffscreenTexture->Release();
82 mOffscreenTexture = NULL;
83 }
84
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +000085 if (mOffscreenRTView)
86 {
87 mOffscreenRTView->Release();
88 mOffscreenRTView = NULL;
89 }
90
daniel@transgaming.come0970472012-11-28 21:05:07 +000091 if (mOffscreenSRView)
92 {
93 mOffscreenSRView->Release();
94 mOffscreenSRView = NULL;
95 }
96
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +000097 if (mDepthStencilTexture)
98 {
99 mDepthStencilTexture->Release();
100 mDepthStencilTexture = NULL;
101 }
102
103 if (mDepthStencilDSView)
104 {
105 mDepthStencilDSView->Release();
106 mDepthStencilDSView = NULL;
107 }
108
daniel@transgaming.come0970472012-11-28 21:05:07 +0000109 if (mQuadVB)
110 {
111 mQuadVB->Release();
112 mQuadVB = NULL;
113 }
114
115 if (mPassThroughSampler)
116 {
117 mPassThroughSampler->Release();
118 mPassThroughSampler = NULL;
119 }
120
121 if (mPassThroughIL)
122 {
123 mPassThroughIL->Release();
124 mPassThroughIL = NULL;
125 }
126
127 if (mPassThroughVS)
128 {
129 mPassThroughVS->Release();
130 mPassThroughVS = NULL;
131 }
132
133 if (mPassThroughPS)
134 {
135 mPassThroughPS->Release();
136 mPassThroughPS = NULL;
137 }
138
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000139 if (mWindow)
140 mShareHandle = NULL;
141}
142
143EGLint SwapChain11::reset(int backbufferWidth, int backbufferHeight, EGLint swapInterval)
144{
145 ID3D11Device *device = mRenderer->getDevice();
146
147 if (device == NULL)
148 {
149 return EGL_BAD_ACCESS;
150 }
151
152 // Release specific resources to free up memory for the new render target, while the
153 // old render target still exists for the purpose of preserving its contents.
154 if (mSwapChain)
155 {
156 mSwapChain->Release();
157 mSwapChain = NULL;
158 }
159
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000160 if (mBackBufferTexture)
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000161 {
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000162 mBackBufferTexture->Release();
163 mBackBufferTexture = NULL;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000164 }
165
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000166 if (mBackBufferRTView)
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000167 {
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000168 mBackBufferRTView->Release();
169 mBackBufferRTView = NULL;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000170 }
171
172 if (mOffscreenTexture)
173 {
174 mOffscreenTexture->Release();
175 mOffscreenTexture = NULL;
176 }
177
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000178 if (mOffscreenRTView) // TODO: Preserve the render target content
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000179 {
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000180 mOffscreenRTView->Release();
181 mOffscreenRTView = NULL;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000182 }
183
daniel@transgaming.come0970472012-11-28 21:05:07 +0000184 if (mOffscreenSRView)
185 {
186 mOffscreenSRView->Release();
187 mOffscreenSRView = NULL;
188 }
189
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000190 if (mDepthStencilTexture)
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000191 {
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000192 mDepthStencilTexture->Release();
193 mDepthStencilTexture = NULL;
194 }
195
196 if (mDepthStencilDSView)
197 {
198 mDepthStencilDSView->Release();
199 mDepthStencilDSView = NULL;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000200 }
201
202 HANDLE *pShareHandle = NULL;
203 if (!mWindow && mRenderer->getShareHandleSupport())
204 {
205 pShareHandle = &mShareHandle;
206 }
207
208 D3D11_TEXTURE2D_DESC offscreenTextureDesc = {0};
209 offscreenTextureDesc.Width = backbufferWidth;
210 offscreenTextureDesc.Height = backbufferHeight;
211 offscreenTextureDesc.Format = gl_d3d11::ConvertRenderbufferFormat(mBackBufferFormat);
212 offscreenTextureDesc.MipLevels = 1;
213 offscreenTextureDesc.ArraySize = 1;
214 offscreenTextureDesc.SampleDesc.Count = 1;
215 offscreenTextureDesc.SampleDesc.Quality = 0;
216 offscreenTextureDesc.Usage = D3D11_USAGE_DEFAULT;
daniel@transgaming.come0970472012-11-28 21:05:07 +0000217 offscreenTextureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000218 offscreenTextureDesc.CPUAccessFlags = 0;
219 offscreenTextureDesc.MiscFlags = 0; // D3D11_RESOURCE_MISC_SHARED
220
221 HRESULT result = device->CreateTexture2D(&offscreenTextureDesc, NULL, &mOffscreenTexture);
222
223 if (FAILED(result))
224 {
225 ERR("Could not create offscreen texture: %08lX", result);
226 release();
227
228 if (isDeviceLostError(result))
229 {
230 return EGL_CONTEXT_LOST;
231 }
232 else
233 {
234 return EGL_BAD_ALLOC;
235 }
236 }
237
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000238 result = device->CreateRenderTargetView(mOffscreenTexture, NULL, &mOffscreenRTView);
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000239 ASSERT(SUCCEEDED(result));
240
daniel@transgaming.come0970472012-11-28 21:05:07 +0000241 result = device->CreateShaderResourceView(mOffscreenTexture, NULL, &mOffscreenSRView);
242 ASSERT(SUCCEEDED(result));
243
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000244 if (mWindow)
245 {
246 IDXGIFactory *factory = mRenderer->getDxgiFactory();
247
248 DXGI_SWAP_CHAIN_DESC swapChainDesc = {0};
249 swapChainDesc.BufferCount = 2;
250 swapChainDesc.BufferDesc.Format = gl_d3d11::ConvertRenderbufferFormat(mBackBufferFormat);
daniel@transgaming.com567b9cf2012-11-28 21:04:46 +0000251 swapChainDesc.BufferDesc.Width = backbufferWidth;
252 swapChainDesc.BufferDesc.Height = backbufferHeight;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000253 swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
254 swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
255 swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
256 swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
257 swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
258 swapChainDesc.Flags = 0;
259 swapChainDesc.OutputWindow = mWindow;
260 swapChainDesc.SampleDesc.Count = 1;
261 swapChainDesc.SampleDesc.Quality = 0;
262 swapChainDesc.Windowed = TRUE;
263
264 result = factory->CreateSwapChain(device, &swapChainDesc, &mSwapChain);
265
266 if (FAILED(result))
267 {
268 ERR("Could not create additional swap chains or offscreen surfaces: %08lX", result);
269 release();
270
271 if (isDeviceLostError(result))
272 {
273 return EGL_CONTEXT_LOST;
274 }
275 else
276 {
277 return EGL_BAD_ALLOC;
278 }
279 }
280
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000281 result = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&mBackBufferTexture);
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000282 ASSERT(SUCCEEDED(result));
283
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000284 result = device->CreateRenderTargetView(mBackBufferTexture, NULL, &mBackBufferRTView);
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000285 ASSERT(SUCCEEDED(result));
286 }
287
288 if (mDepthBufferFormat != GL_NONE)
289 {
290 D3D11_TEXTURE2D_DESC depthStencilDesc = {0};
291 depthStencilDesc.Width = backbufferWidth;
292 depthStencilDesc.Height = backbufferHeight;
293 depthStencilDesc.Format = gl_d3d11::ConvertRenderbufferFormat(mDepthBufferFormat);
294 depthStencilDesc.MipLevels = 1;
295 depthStencilDesc.ArraySize = 1;
296 depthStencilDesc.SampleDesc.Count = 1;
297 depthStencilDesc.SampleDesc.Quality = 0;
298 depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
299 depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
300 depthStencilDesc.CPUAccessFlags = 0;
301 depthStencilDesc.MiscFlags = 0;
302
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000303 result = device->CreateTexture2D(&depthStencilDesc, NULL, &mDepthStencilTexture);
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000304
305 if (FAILED(result))
306 {
307 ERR("Could not create depthstencil surface for new swap chain: 0x%08X", result);
308 release();
309
310 if (isDeviceLostError(result))
311 {
312 return EGL_CONTEXT_LOST;
313 }
314 else
315 {
316 return EGL_BAD_ALLOC;
317 }
318 }
319
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000320 result = device->CreateDepthStencilView(mDepthStencilTexture, NULL, &mDepthStencilDSView);
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000321 ASSERT(SUCCEEDED(result));
322 }
323
daniel@transgaming.come0970472012-11-28 21:05:07 +0000324 D3D11_BUFFER_DESC vbDesc;
325 vbDesc.ByteWidth = sizeof(QuadVertex) * 4;
326 vbDesc.Usage = D3D11_USAGE_DYNAMIC;
327 vbDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
328 vbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
329 vbDesc.MiscFlags = 0;
330 vbDesc.StructureByteStride = 0;
331
332 result = device->CreateBuffer(&vbDesc, NULL, &mQuadVB);
333 ASSERT(SUCCEEDED(result));
334
335 D3D11_SAMPLER_DESC samplerDesc;
336 samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
337 samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
338 samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
339 samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
340 samplerDesc.MipLODBias = 0.0f;
341 samplerDesc.MaxAnisotropy = 0;
342 samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
343 samplerDesc.BorderColor[0] = 0.0f;
344 samplerDesc.BorderColor[1] = 0.0f;
345 samplerDesc.BorderColor[2] = 0.0f;
346 samplerDesc.BorderColor[3] = 0.0f;
347 samplerDesc.MinLOD = 0;
348 samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
349
350 result = device->CreateSamplerState(&samplerDesc, &mPassThroughSampler);
351 ASSERT(SUCCEEDED(result));
352
353 D3D11_INPUT_ELEMENT_DESC quadLayout[] =
354 {
355 { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
356 { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0 },
357 };
358
359 result = device->CreateInputLayout(quadLayout, 2, g_VS_Passthrough, sizeof(g_VS_Passthrough), &mPassThroughIL);
360 ASSERT(SUCCEEDED(result));
361
362 result = device->CreateVertexShader(g_VS_Passthrough, sizeof(g_VS_Passthrough), NULL, &mPassThroughVS);
363 ASSERT(SUCCEEDED(result));
364
365 result = device->CreatePixelShader(g_PS_Passthrough, sizeof(g_PS_Passthrough), NULL, &mPassThroughPS);
366 ASSERT(SUCCEEDED(result));
367
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000368 mWidth = backbufferWidth;
369 mHeight = backbufferHeight;
370
371 return EGL_SUCCESS;
372}
373
374// parameters should be validated/clamped by caller
375EGLint SwapChain11::swapRect(EGLint x, EGLint y, EGLint width, EGLint height)
376{
377 if (!mSwapChain)
378 {
379 return EGL_SUCCESS;
380 }
381
382 ID3D11Device *device = mRenderer->getDevice();
daniel@transgaming.come0970472012-11-28 21:05:07 +0000383 ID3D11DeviceContext *deviceContext = mRenderer->getDeviceContext();
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000384
daniel@transgaming.come0970472012-11-28 21:05:07 +0000385 // Set vertices
386 D3D11_MAPPED_SUBRESOURCE mappedResource;
387 HRESULT result = deviceContext->Map(mQuadVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
388 if (FAILED(result))
389 {
390 return EGL_BAD_ACCESS;
391 }
392
393 QuadVertex *vertices = static_cast<QuadVertex*>(mappedResource.pData);
394
395 // Create a quad in homogeneous coordinates
396 float x1 = (x / mWidth) * 2.0f - 1.0f;
397 float y1 = ((mHeight - y - height) / mHeight) * 2.0f - 1.0f;
398 float x2 = ((x + width) / mWidth) * 2.0f - 1.0f;
399 float y2 = ((mHeight - y) / mHeight) * 2.0f - 1.0f;
400
401 float u1 = x / float(mWidth);
402 float v1 = y / float(mHeight);
403 float u2 = (x + width) / float(mWidth);
404 float v2 = (y + height) / float(mHeight);
405
406 setVertex(&vertices[0], x1, y1, u1, v1);
407 setVertex(&vertices[1], x1, y2, u1, v2);
408 setVertex(&vertices[2], x2, y1, u2, v1);
409 setVertex(&vertices[3], x2, y2, u2, v2);
410
411 deviceContext->Unmap(mQuadVB, 0);
412
413 static UINT stride = sizeof(QuadVertex);
414 static UINT startIdx = 0;
415 deviceContext->IASetVertexBuffers(0, 1, &mQuadVB, &stride, &startIdx);
416
417 // Apply state
418 deviceContext->OMSetDepthStencilState(NULL, 0xFFFFFFFF);
419
420 static const float blendFactor[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
421 deviceContext->OMSetBlendState(NULL, blendFactor, 0xFFFFFFF);
422
423 deviceContext->RSSetState(NULL);
424
425 // Apply shaders
426 deviceContext->IASetInputLayout(mPassThroughIL);
427 deviceContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
428 deviceContext->VSSetShader(mPassThroughVS, NULL, 0);
429 deviceContext->PSSetShader(mPassThroughPS, NULL, 0);
430
431 // Apply render targets
432 deviceContext->OMSetRenderTargets(1, &mBackBufferRTView, NULL);
433
daniel@transgaming.com9799a2f2013-01-11 04:09:27 +0000434 // Set the viewport
435 D3D11_VIEWPORT viewport;
436 viewport.TopLeftX = 0;
437 viewport.TopLeftY = 0;
438 viewport.Width = mWidth;
439 viewport.Height = mHeight;
440 viewport.MinDepth = 0.0f;
441 viewport.MaxDepth = 1.0f;
442 deviceContext->RSSetViewports(1, &viewport);
443
daniel@transgaming.come0970472012-11-28 21:05:07 +0000444 // Apply textures
445 deviceContext->PSSetShaderResources(0, 1, &mOffscreenSRView);
446 deviceContext->PSSetSamplers(0, 1, &mPassThroughSampler);
447
448 // Draw
449 deviceContext->Draw(4, 0);
450 mSwapChain->Present(0, 0);
451
452 // Unbind
453 static ID3D11ShaderResourceView *const nullSRV = NULL;
454 deviceContext->PSSetShaderResources(0, 1, &nullSRV);
455
456 static ID3D11RenderTargetView *const nullRTV = NULL;
457 deviceContext->OMSetRenderTargets(1, &nullRTV, NULL);
458
459 // Mark context and renderer dirty flags
460 gl::Context *glContext = static_cast<gl::Context*>(glGetCurrentContext());
461 if (glContext)
462 {
463 glContext->markAllStateDirty();
464 }
465 mRenderer->markAllStateDirty();
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000466
467 return EGL_SUCCESS;
468}
469
470// Increments refcount on view.
471// caller must Release() the returned view
472ID3D11RenderTargetView *SwapChain11::getRenderTarget()
473{
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000474 if (mOffscreenRTView)
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000475 {
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000476 mOffscreenRTView->AddRef();
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000477 }
478
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000479 return mOffscreenRTView;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000480}
481
482// Increments refcount on view.
483// caller must Release() the returned view
484ID3D11DepthStencilView *SwapChain11::getDepthStencil()
485{
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000486 if (mDepthStencilDSView)
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000487 {
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000488 mDepthStencilDSView->AddRef();
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000489 }
490
daniel@transgaming.comc8c70ad2012-11-28 21:04:37 +0000491 return mDepthStencilDSView;
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000492}
493
494// Increments refcount on texture.
495// caller must Release() the returned texture
496ID3D11Texture2D *SwapChain11::getOffscreenTexture()
497{
498 if (mOffscreenTexture)
499 {
500 mOffscreenTexture->AddRef();
501 }
502
503 return mOffscreenTexture;
504}
505
daniel@transgaming.comd733bb82012-11-28 20:53:40 +0000506SwapChain11 *SwapChain11::makeSwapChain11(SwapChain *swapChain)
507{
508 ASSERT(dynamic_cast<rx::SwapChain11*>(swapChain) != NULL);
509 return static_cast<rx::SwapChain11*>(swapChain);
daniel@transgaming.com32fdf822012-11-28 20:53:30 +0000510}
daniel@transgaming.comd733bb82012-11-28 20:53:40 +0000511
512}
513