diff --git a/WickedEngine/CommonInclude.h b/WickedEngine/CommonInclude.h index 1bd2f5057..198eb653f 100644 --- a/WickedEngine/CommonInclude.h +++ b/WickedEngine/CommonInclude.h @@ -1,6 +1,9 @@ #ifndef WICKEDENGINE_COMMONINCLUDE_H #define WICKEDENGINE_COMMONINCLUDE_H +// NOTE: +// Do not include engine features in this file! + #include #include #include diff --git a/WickedEngine/Editor.cpp b/WickedEngine/Editor.cpp index 1385d54c1..a7b870b8b 100644 --- a/WickedEngine/Editor.cpp +++ b/WickedEngine/Editor.cpp @@ -20,7 +20,7 @@ int __editorVersion = 0; using namespace wiGraphicsTypes; - +using namespace wiRectPacker; Editor::Editor() { @@ -777,8 +777,10 @@ void EditorComponent::FixedUpdate() if (wiInputManager::GetInstance()->press(VK_LBUTTON)) { // if not water, put a decal instead: + static int decalselector = 0; + decalselector = (decalselector + 1) % 2; Decal* decal = new Decal(hovered.position, XMFLOAT3(4,4,4), wiRenderer::getCamera()->rotation, - wiHelper::GetOriginalWorkingDirectory() + "images/leaf.png"); + wiHelper::GetOriginalWorkingDirectory() + (decalselector == 0 ? "images/leaf.png" : "images/blood1.png")); decal->attachTo(hovered.object); wiRenderer::PutDecal(decal); } diff --git a/WickedEngine/ResourceMapping.h b/WickedEngine/ResourceMapping.h index 588875525..6f9b3f458 100644 --- a/WickedEngine/ResourceMapping.h +++ b/WickedEngine/ResourceMapping.h @@ -31,30 +31,31 @@ #define TEXSLOT_ENV0 8 #define TEXSLOT_ENV1 9 #define TEXSLOT_ENV2 10 +#define TEXSLOT_DECALATLAS 11 -#define TEXSLOT_SHADOWARRAY_2D 11 -#define TEXSLOT_SHADOWARRAY_CUBE 12 +#define TEXSLOT_SHADOWARRAY_2D 12 +#define TEXSLOT_SHADOWARRAY_CUBE 13 -#define TEXSLOT_ONDEMAND0 13 -#define TEXSLOT_ONDEMAND1 14 -#define TEXSLOT_ONDEMAND2 15 -#define TEXSLOT_ONDEMAND3 16 -#define TEXSLOT_ONDEMAND4 17 -#define TEXSLOT_ONDEMAND5 18 -#define TEXSLOT_ONDEMAND6 19 -#define TEXSLOT_ONDEMAND7 20 -#define TEXSLOT_ONDEMAND8 21 -#define TEXSLOT_ONDEMAND9 22 +#define TEXSLOT_ONDEMAND0 14 +#define TEXSLOT_ONDEMAND1 15 +#define TEXSLOT_ONDEMAND2 16 +#define TEXSLOT_ONDEMAND3 17 +#define TEXSLOT_ONDEMAND4 18 +#define TEXSLOT_ONDEMAND5 19 +#define TEXSLOT_ONDEMAND6 20 +#define TEXSLOT_ONDEMAND7 21 +#define TEXSLOT_ONDEMAND8 22 +#define TEXSLOT_ONDEMAND9 23 #define TEXSLOT_ONDEMAND_COUNT (TEXSLOT_ONDEMAND9 - TEXSLOT_ONDEMAND0 + 1) -#define TEXSLOT_LIGHTGRID 23 +#define TEXSLOT_LIGHTGRID 24 #define TEXSLOT_COUNT TEXSLOT_LIGHTGRID #define SBSLOT_BONE 0 -#define SBSLOT_TILEFRUSTUMS 23 -#define SBSLOT_LIGHTINDEXLIST 24 -#define SBSLOT_LIGHTARRAY 25 +#define SBSLOT_TILEFRUSTUMS 24 +#define SBSLOT_LIGHTINDEXLIST 25 +#define SBSLOT_LIGHTARRAY 26 /////////////////////////// diff --git a/WickedEngine/WickedEngine.h b/WickedEngine/WickedEngine.h index e9d41b9ea..1664d99c9 100644 --- a/WickedEngine/WickedEngine.h +++ b/WickedEngine/WickedEngine.h @@ -1,6 +1,11 @@ #ifndef WICKED_ENGINE #define WICKED_ENGINE +// NOTE: +// The purpose of this file is to expose all engine features. +// It should be included in the engine's implementing project not the engine itself! +// It should be included in the precompiled header if available. + #include "CommonInclude.h" #include "wiVersion.h" @@ -50,6 +55,7 @@ #include "wiTranslator.h" #include "wiArchive.h" #include "wiSpinLock.h" +#include "wiRectPacker.h" #include "RenderableComponent.h" #include "Renderable2DComponent.h" diff --git a/WickedEngine/WickedEngineEditor.exe b/WickedEngine/WickedEngineEditor.exe index 18829e6fa..a105a1867 100644 Binary files a/WickedEngine/WickedEngineEditor.exe and b/WickedEngine/WickedEngineEditor.exe differ diff --git a/WickedEngine/WickedEngineEditor.vcxproj b/WickedEngine/WickedEngineEditor.vcxproj index 9c6b5d4a2..3a0f5c5da 100644 --- a/WickedEngine/WickedEngineEditor.vcxproj +++ b/WickedEngine/WickedEngineEditor.vcxproj @@ -203,6 +203,7 @@ + true diff --git a/WickedEngine/WickedEngineEditor.vcxproj.filters b/WickedEngine/WickedEngineEditor.vcxproj.filters index 60a7185f4..5d65f298d 100644 --- a/WickedEngine/WickedEngineEditor.vcxproj.filters +++ b/WickedEngine/WickedEngineEditor.vcxproj.filters @@ -230,6 +230,9 @@ images + + images + diff --git a/WickedEngine/WickedEngine_SHARED.vcxitems b/WickedEngine/WickedEngine_SHARED.vcxitems index 4a1ef9215..3dcdefb0f 100644 --- a/WickedEngine/WickedEngine_SHARED.vcxitems +++ b/WickedEngine/WickedEngine_SHARED.vcxitems @@ -332,6 +332,7 @@ + @@ -667,6 +668,7 @@ + diff --git a/WickedEngine/WickedEngine_SHARED.vcxitems.filters b/WickedEngine/WickedEngine_SHARED.vcxitems.filters index 0a63c77da..bbfd2731e 100644 --- a/WickedEngine/WickedEngine_SHARED.vcxitems.filters +++ b/WickedEngine/WickedEngine_SHARED.vcxitems.filters @@ -1083,6 +1083,9 @@ ENGINE\Helpers + + ENGINE\Helpers + @@ -1862,6 +1865,9 @@ ENGINE\Scripting\LuaBindings + + ENGINE\Helpers + diff --git a/WickedEngine/globals.hlsli b/WickedEngine/globals.hlsli index 518cd5736..82d2e784d 100644 --- a/WickedEngine/globals.hlsli +++ b/WickedEngine/globals.hlsli @@ -17,6 +17,7 @@ TEXTURECUBE(texture_env1, float4, TEXSLOT_ENV1) TEXTURECUBE(texture_env2, float4, TEXSLOT_ENV2) TEXTURE2DARRAY(texture_shadowarray_2d, float, TEXSLOT_SHADOWARRAY_2D) TEXTURECUBEARRAY(texture_shadowarray_cube, float, TEXSLOT_SHADOWARRAY_CUBE) +TEXTURE2D(texture_decalatlas, float4, TEXSLOT_DECALATLAS) TEXTURE2D(texture_0, float4, TEXSLOT_ONDEMAND0) TEXTURE2D(texture_1, float4, TEXSLOT_ONDEMAND1) TEXTURE2D(texture_2, float4, TEXSLOT_ONDEMAND2) diff --git a/WickedEngine/grassPS_tiledforward.hlsl b/WickedEngine/grassPS_tiledforward.hlsl index d380c500c..458fc6756 100644 --- a/WickedEngine/grassPS_tiledforward.hlsl +++ b/WickedEngine/grassPS_tiledforward.hlsl @@ -1,3 +1,4 @@ +#define DISABLE_DECALS #include "grassHF_GS.hlsli" #include "grassHF_PS.hlsli" #include "ditherHF.hlsli" diff --git a/WickedEngine/images/blood1.png b/WickedEngine/images/blood1.png new file mode 100644 index 000000000..63da0fc7a Binary files /dev/null and b/WickedEngine/images/blood1.png differ diff --git a/WickedEngine/lightCullingCS.hlsl b/WickedEngine/lightCullingCS.hlsl index 307a54117..0b7a7af1a 100644 --- a/WickedEngine/lightCullingCS.hlsl +++ b/WickedEngine/lightCullingCS.hlsl @@ -104,8 +104,8 @@ void main(ComputeShaderInput IN) if (IN.groupIndex == 0) { - float3 minAABB = ScreenToView(float4(float2(IN.groupID.x, IN.groupID.y + 1) * BLOCK_SIZE, fMinDepth, 1.0f)); - float3 maxAABB = ScreenToView(float4(float2(IN.groupID.x + 1, IN.groupID.y) * BLOCK_SIZE, fMaxDepth, 1.0f)); + float3 minAABB = ScreenToView(float4(float2(IN.groupID.x, IN.groupID.y + 1) * BLOCK_SIZE, fMinDepth, 1.0f)).xyz; + float3 maxAABB = ScreenToView(float4(float2(IN.groupID.x + 1, IN.groupID.y) * BLOCK_SIZE, fMaxDepth, 1.0f)).xyz; GroupAABB.c = (minAABB + maxAABB)*0.5f; GroupAABB.e = abs(maxAABB - GroupAABB.c); @@ -170,7 +170,7 @@ void main(ComputeShaderInput IN) // Add light to light list for opaque geometry. o_AppendLight(i); #ifdef DEBUG_TILEDLIGHTCULLING - //InterlockedAdd(_counter.x, 1); + InterlockedAdd(_counter.x, 1); #endif } } @@ -184,6 +184,30 @@ void main(ComputeShaderInput IN) o_AppendLight(i); } break; + case 100:/*DECAL*/ + { + Sphere sphere = { light.positionVS.xyz, light.range }; + if (SphereInsideFrustum(sphere, GroupFrustum, nearClipVS, maxDepthVS)) + { + // Add decal to light list for transparent geometry. + t_AppendLight(i); + +#ifdef DEBUG_TILEDLIGHTCULLING + InterlockedAdd(_counter.z, 1); +#endif + + if (SphereInsideFrustum(sphere, GroupFrustum, minDepthVS, maxDepthVS)) + { + // Add decal to light list for opaque geometry. + o_AppendLight(i); + +#ifdef DEBUG_TILEDLIGHTCULLING + InterlockedAdd(_counter.x, 1); +#endif + } + } + } + break; } } diff --git a/WickedEngine/lightingHF.hlsli b/WickedEngine/lightingHF.hlsli index 9670fa1c1..95dbdcf1c 100644 --- a/WickedEngine/lightingHF.hlsli +++ b/WickedEngine/lightingHF.hlsli @@ -24,6 +24,8 @@ struct LightArrayType float coneAngle; float coneAngleCos; // -- + float4 texMulAdd; + // -- float4x4 shadowMat[3]; }; diff --git a/WickedEngine/objectHF.hlsli b/WickedEngine/objectHF.hlsli index 4969f0f91..5e50449fe 100644 --- a/WickedEngine/objectHF.hlsli +++ b/WickedEngine/objectHF.hlsli @@ -119,7 +119,7 @@ inline void DirectionalLight(in float3 N, in float3 V, in float3 P, in float3 f0 } -inline void TiledLighting(in float2 pixel, in float3 N, in float3 V, in float3 P, in float3 f0, in float3 albedo, in float roughness, +inline void TiledLighting(in float2 pixel, in float3 N, in float3 V, in float3 P, in float3 f0, inout float3 albedo, in float roughness, inout float3 diffuse, out float3 specular) { uint2 tileIndex = uint2(floor(pixel / BLOCK_SIZE)); @@ -137,7 +137,7 @@ inline void TiledLighting(in float2 pixel, in float3 N, in float3 V, in float3 P float3 L = light.positionWS - P; float lightDistance = length(L); - if (light.type > 0 && lightDistance > light.range) + if (light.type > 0 && light.type != 100 && lightDistance > light.range) continue; L /= lightDistance; @@ -159,6 +159,26 @@ inline void TiledLighting(in float2 pixel, in float3 N, in float3 V, in float3 P result = SpotLight(light, L, lightDistance, N, V, P, roughness, f0); } break; +#ifndef DISABLE_DECALS + case 100/*DECAL*/: + { + float3 clipSpace = mul(float4(P, 1), light.shadowMat[0]).xyz; + float3 projTex = clipSpace.xyz*float3(0.5f, -0.5f, 0.5f) + 0.5f; + [branch] + if ((saturate(projTex.x) == projTex.x) && (saturate(projTex.y) == projTex.y) && (saturate(projTex.z) == projTex.z)) + { + // can't do mipmapping here because of the variable length loop :( + float4 decalColor = texture_decalatlas.SampleLevel(sampler_linear_clamp, projTex.xy*light.texMulAdd.xy + light.texMulAdd.zw, 0); + float3 edgeBlend = clipSpace.xyz; + edgeBlend = saturate(abs(edgeBlend)); + decalColor.a *= 1 - pow(max(max(edgeBlend.x, edgeBlend.y), edgeBlend.z), 8); + decalColor *= light.color; + albedo.rgb = lerp(albedo.rgb, decalColor.rgb, decalColor.a); + } + } + break; +#endif + default:break; } diffuse += max(0.0f, result.diffuse); diff --git a/WickedEngine/objectPS_tiledforward_water.hlsl b/WickedEngine/objectPS_tiledforward_water.hlsl index 20824322a..43b45ad97 100644 --- a/WickedEngine/objectPS_tiledforward_water.hlsl +++ b/WickedEngine/objectPS_tiledforward_water.hlsl @@ -1,3 +1,4 @@ +#define DISABLE_DECALS #define DIRECTIONALLIGHT_SOFT #define DISABLE_ALPHATEST #include "objectHF.hlsli" diff --git a/WickedEngine/qGrassPS_tiledforward.hlsl b/WickedEngine/qGrassPS_tiledforward.hlsl index 369c4b290..fca576dd5 100644 --- a/WickedEngine/qGrassPS_tiledforward.hlsl +++ b/WickedEngine/qGrassPS_tiledforward.hlsl @@ -1,3 +1,4 @@ +#define DISABLE_DECALS #include "grassHF_GS.hlsli" #include "grassHF_PS.hlsli" #include "ditherHF.hlsli" diff --git a/WickedEngine/wiEnums.h b/WickedEngine/wiEnums.h index 165b09c65..6fd4695a8 100644 --- a/WickedEngine/wiEnums.h +++ b/WickedEngine/wiEnums.h @@ -255,4 +255,4 @@ enum BSTYPES BSTYPE_ADDITIVE, BSTYPE_COLORWRITEDISABLE, BSTYPE_LAST -}; \ No newline at end of file +}; diff --git a/WickedEngine/wiGraphicsDevice.h b/WickedEngine/wiGraphicsDevice.h index 9e6cbe5e7..44d53b09a 100644 --- a/WickedEngine/wiGraphicsDevice.h +++ b/WickedEngine/wiGraphicsDevice.h @@ -127,6 +127,7 @@ namespace wiGraphicsTypes virtual void Dispatch(UINT threadGroupCountX, UINT threadGroupCountY, UINT threadGroupCountZ, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) = 0; virtual void GenerateMips(Texture* texture, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) = 0; virtual void CopyTexture2D(Texture2D* pDst, const Texture2D* pSrc, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) = 0; + virtual void CopyTexture2D_Region(Texture2D* pDst, UINT dstMip, UINT dstX, UINT dstY, const Texture2D* pSrc, UINT srcMip, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) = 0; virtual void MSAAResolve(Texture2D* pDst, const Texture2D* pSrc, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) = 0; virtual void UpdateBuffer(GPUBuffer* buffer, const void* data, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE, int dataSize = -1) = 0; virtual GPUBuffer* DownloadBuffer(GPUBuffer* buffer, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) = 0; diff --git a/WickedEngine/wiGraphicsDevice_DX11.cpp b/WickedEngine/wiGraphicsDevice_DX11.cpp index cea7505c4..4f1dd6393 100644 --- a/WickedEngine/wiGraphicsDevice_DX11.cpp +++ b/WickedEngine/wiGraphicsDevice_DX11.cpp @@ -2521,6 +2521,11 @@ void GraphicsDevice_DX11::CopyTexture2D(Texture2D* pDst, const Texture2D* pSrc, { deviceContexts[threadID]->CopyResource(pDst->texture2D_DX11, pSrc->texture2D_DX11); } +void GraphicsDevice_DX11::CopyTexture2D_Region(Texture2D* pDst, UINT dstMip, UINT dstX, UINT dstY, const Texture2D* pSrc, UINT srcMip, GRAPHICSTHREAD threadID) +{ + deviceContexts[threadID]->CopySubresourceRegion(pDst->texture2D_DX11, D3D11CalcSubresource(dstMip, 0, pDst->GetDesc().MipLevels), dstX, dstY, 0, + pSrc->texture2D_DX11, D3D11CalcSubresource(srcMip, 0, pSrc->GetDesc().MipLevels), nullptr); +} void GraphicsDevice_DX11::MSAAResolve(Texture2D* pDst, const Texture2D* pSrc, GRAPHICSTHREAD threadID) { deviceContexts[threadID]->ResolveSubresource(pDst->texture2D_DX11, 0, pSrc->texture2D_DX11, 0, _ConvertFormat(pDst->desc.Format)); diff --git a/WickedEngine/wiGraphicsDevice_DX11.h b/WickedEngine/wiGraphicsDevice_DX11.h index 2e7798e84..740f29976 100644 --- a/WickedEngine/wiGraphicsDevice_DX11.h +++ b/WickedEngine/wiGraphicsDevice_DX11.h @@ -121,6 +121,7 @@ namespace wiGraphicsTypes virtual void Dispatch(UINT threadGroupCountX, UINT threadGroupCountY, UINT threadGroupCountZ, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) override; virtual void GenerateMips(Texture* texture, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) override; virtual void CopyTexture2D(Texture2D* pDst, const Texture2D* pSrc, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) override; + virtual void CopyTexture2D_Region(Texture2D* pDst, UINT dstMip, UINT dstX, UINT dstY, const Texture2D* pSrc, UINT srcMip, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) override; virtual void MSAAResolve(Texture2D* pDst, const Texture2D* pSrc, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) override; virtual void UpdateBuffer(GPUBuffer* buffer, const void* data, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE, int dataSize = -1) override; virtual GPUBuffer* DownloadBuffer(GPUBuffer* buffer, GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE) override; diff --git a/WickedEngine/wiGraphicsResource.h b/WickedEngine/wiGraphicsResource.h index 204a14375..79137bb2f 100644 --- a/WickedEngine/wiGraphicsResource.h +++ b/WickedEngine/wiGraphicsResource.h @@ -255,7 +255,7 @@ namespace wiGraphicsTypes Texture2D(); virtual ~Texture2D(); - Texture2DDesc GetDesc() { return desc; } + Texture2DDesc GetDesc() const { return desc; } }; diff --git a/WickedEngine/wiLoader.cpp b/WickedEngine/wiLoader.cpp index 34908ca6d..e5877fe40 100644 --- a/WickedEngine/wiLoader.cpp +++ b/WickedEngine/wiLoader.cpp @@ -3594,6 +3594,8 @@ Decal::Decal(const XMFLOAT3& tra, const XMFLOAT3& sca, const XMFLOAT4& rot, cons life = -2; //persistent fadeStart=0; + + atlasMulAdd = XMFLOAT4(1, 1, 0, 0); } Decal::~Decal() { wiResourceManager::GetGlobal()->del(texName); @@ -3629,6 +3631,10 @@ void Decal::UpdateDecal() life -= wiRenderer::GetGameSpeed(); } } +float Decal::GetOpacity() const +{ + return wiMath::Clamp((life <= -2 ? 1 : life < fadeStart ? life / fadeStart : 1), 0, 1); +} void Decal::Serialize(wiArchive& archive) { Cullable::Serialize(archive); diff --git a/WickedEngine/wiLoader.h b/WickedEngine/wiLoader.h index bc7c8dea9..24bd2cae3 100644 --- a/WickedEngine/wiLoader.h +++ b/WickedEngine/wiLoader.h @@ -771,6 +771,7 @@ struct Decal : public Cullable, public Transform wiGraphicsTypes::Texture2D* texture,*normal; XMFLOAT3 front; float life,fadeStart; + XMFLOAT4 atlasMulAdd; Decal(const XMFLOAT3& tra=XMFLOAT3(0,0,0), const XMFLOAT3& sca=XMFLOAT3(1,1,1), const XMFLOAT4& rot=XMFLOAT4(0,0,0,1), const string& tex="", const string& nor=""); virtual ~Decal(); @@ -779,6 +780,7 @@ struct Decal : public Cullable, public Transform void addNormal(const string& nor); virtual void UpdateTransform(); void UpdateDecal(); + float GetOpacity() const; void Serialize(wiArchive& archive); }; struct WorldInfo{ diff --git a/WickedEngine/wiRectPacker.cpp b/WickedEngine/wiRectPacker.cpp new file mode 100644 index 000000000..0875cfe22 --- /dev/null +++ b/WickedEngine/wiRectPacker.cpp @@ -0,0 +1,351 @@ +#pragma once +#include "wiRectPacker.h" +#include +#include + +using namespace std; + +namespace wiRectPacker +{ + + bool area(rect_xywhf* a, rect_xywhf* b) { + return a->area() > b->area(); + } + + bool perimeter(rect_xywhf* a, rect_xywhf* b) { + return a->perimeter() > b->perimeter(); + } + + bool max_side(rect_xywhf* a, rect_xywhf* b) { + return std::max(a->w, a->h) > std::max(b->w, b->h); + } + + bool max_width(rect_xywhf* a, rect_xywhf* b) { + return a->w > b->w; + } + + bool max_height(rect_xywhf* a, rect_xywhf* b) { + return a->h > b->h; + } + + + // just add another comparing function name to cmpf to perform another packing attempt + // more functions == slower but probably more efficient cases covered and hence less area wasted + + bool(*cmpf[])(rect_xywhf*, rect_xywhf*) = { + area, + perimeter, + max_side, + max_width, + max_height + }; + + // if you find the algorithm running too slow you may double this factor to increase speed but also decrease efficiency + // 1 == most efficient, slowest + // efficiency may be still satisfying at 64 or even 256 with nice speedup + + int discard_step = 128; + + /* + + For every sorting function, algorithm will perform packing attempts beginning with a bin with width and height equal to max_side, + and decreasing its dimensions if it finds out that rectangles did actually fit, increasing otherwise. + Although, it's doing that in sort of binary search manner, so for every comparing function it will perform at most log2(max_side) packing attempts looking for the smallest possible bin size. + discard_step = 128 means that the algorithm will break of the searching loop if the rectangles fit but "it may be possible to fit them in a bin smaller by 128" + the bigger the value, the sooner the algorithm will finish but the rectangles will be packed less tightly. + use discard_step = 1 for maximum tightness. + + the algorithm was based on http://www.blackpawn.com/texts/lightmaps/default.html + the algorithm reuses the node tree so it doesn't reallocate them between searching attempts + + */ + + /*************************************************************************** CHAOS BEGINS HERE */ + + struct node { + struct pnode { + node* pn; + bool fill; + + pnode() : fill(false), pn(0) {} + void set(int l, int t, int r, int b) { + if (!pn) pn = new node(rect_ltrb(l, t, r, b)); + else { + (*pn).rc = rect_ltrb(l, t, r, b); + (*pn).id = false; + } + fill = true; + } + }; + + pnode c[2]; + rect_ltrb rc; + bool id; + node(rect_ltrb rc = rect_ltrb()) : id(false), rc(rc) {} + + void reset(const rect_wh& r) { + id = false; + rc = rect_ltrb(0, 0, r.w, r.h); + delcheck(); + } + + node* insert(rect_xywhf& img) { + if (c[0].pn && c[0].fill) { + node* newn; + if (newn = c[0].pn->insert(img)) return newn; + return c[1].pn->insert(img); + } + + if (id) return 0; + int f = img.fits(rect_xywh(rc)); + + switch (f) { + case 0: return 0; + case 1: img.flipped = false; break; + case 2: img.flipped = true; break; + case 3: id = true; img.flipped = false; return this; + case 4: id = true; img.flipped = true; return this; + } + + int iw = (img.flipped ? img.h : img.w), ih = (img.flipped ? img.w : img.h); + + if (rc.w() - iw > rc.h() - ih) { + c[0].set(rc.l, rc.t, rc.l + iw, rc.b); + c[1].set(rc.l + iw, rc.t, rc.r, rc.b); + } + else { + c[0].set(rc.l, rc.t, rc.r, rc.t + ih); + c[1].set(rc.l, rc.t + ih, rc.r, rc.b); + } + + return c[0].pn->insert(img); + } + + void delcheck() { + if (c[0].pn) { c[0].fill = false; c[0].pn->delcheck(); } + if (c[1].pn) { c[1].fill = false; c[1].pn->delcheck(); } + } + + ~node() { + if (c[0].pn) delete c[0].pn; + if (c[1].pn) delete c[1].pn; + } + }; + + rect_wh _rect2D(rect_xywhf* const * v, int n, int max_s, vector& succ, vector& unsucc) { + node root; + + const int funcs = (sizeof(cmpf) / sizeof(bool(*)(rect_xywhf*, rect_xywhf*))); + + rect_xywhf** order[funcs]; + + for (int f = 0; f < funcs; ++f) { + order[f] = new rect_xywhf*[n]; + memcpy(order[f], v, sizeof(rect_xywhf*) * n); + sort(order[f], order[f] + n, cmpf[f]); + } + + rect_wh min_bin = rect_wh(max_s, max_s); + int min_func = -1, best_func = 0, best_area = 0, _area = 0, step, fit, i; + + bool fail = false; + + for (int f = 0; f < funcs; ++f) { + v = order[f]; + step = min_bin.w / 2; + root.reset(min_bin); + + while (true) { + if (root.rc.w() > min_bin.w) { + if (min_func > -1) break; + _area = 0; + + root.reset(min_bin); + for (i = 0; i < n; ++i) + if (root.insert(*v[i])) + _area += v[i]->area(); + + fail = true; + break; + } + + fit = -1; + + for (i = 0; i < n; ++i) + if (!root.insert(*v[i])) { + fit = 1; + break; + } + + if (fit == -1 && step <= discard_step) + break; + + root.reset(rect_wh(root.rc.w() + fit*step, root.rc.h() + fit*step)); + + step /= 2; + if (!step) + step = 1; + } + + if (!fail && (min_bin.area() >= root.rc.area())) { + min_bin = rect_wh(root.rc); + min_func = f; + } + + else if (fail && (_area > best_area)) { + best_area = _area; + best_func = f; + } + fail = false; + } + + v = order[min_func == -1 ? best_func : min_func]; + + int clip_x = 0, clip_y = 0; + node* ret; + + root.reset(min_bin); + + for (i = 0; i < n; ++i) { + if (ret = root.insert(*v[i])) { + v[i]->x = ret->rc.l; + v[i]->y = ret->rc.t; + + if (v[i]->flipped) { + v[i]->flipped = false; + v[i]->flip(); + } + + clip_x = std::max(clip_x, ret->rc.r); + clip_y = std::max(clip_y, ret->rc.b); + + succ.push_back(v[i]); + } + else { + unsucc.push_back(v[i]); + + v[i]->flipped = false; + } + } + + for (int f = 0; f < funcs; ++f) + delete[] order[f]; + + return rect_wh(clip_x, clip_y); + } + + + bool pack(rect_xywhf* const * v, int n, int max_s, vector& bins) { + rect_wh _rect(max_s, max_s); + + for (int i = 0; i < n; ++i) + if (!v[i]->fits(_rect)) return false; + + vector vec[2], *p[2] = { vec, vec + 1 }; + vec[0].resize(n); + vec[1].clear(); + memcpy(&vec[0][0], v, sizeof(rect_xywhf*)*n); + + bin* b = 0; + + while (true) { + bins.push_back(bin()); + b = &bins[bins.size() - 1]; + + b->size = _rect2D(&((*p[0])[0]), p[0]->size(), max_s, b->rects, *p[1]); + b->rects.shrink_to_fit(); + p[0]->clear(); + + if (!p[1]->size()) break; + + std::swap(p[0], p[1]); + } + + return true; + } + + + rect_wh::rect_wh(const rect_ltrb& rr) : w(rr.w()), h(rr.h()) {} + rect_wh::rect_wh(const rect_xywh& rr) : w(rr.w), h(rr.h) {} + rect_wh::rect_wh(int w, int h) : w(w), h(h) {} + + int rect_wh::fits(const rect_wh& r) const { + if (w == r.w && h == r.h) return 3; + if (h == r.w && w == r.h) return 4; + if (w <= r.w && h <= r.h) return 1; + if (h <= r.w && w <= r.h) return 2; + return 0; + } + + rect_ltrb::rect_ltrb() : l(0), t(0), r(0), b(0) {} + rect_ltrb::rect_ltrb(int l, int t, int r, int b) : l(l), t(t), r(r), b(b) {} + + int rect_ltrb::w() const { + return r - l; + } + + int rect_ltrb::h() const { + return b - t; + } + + int rect_ltrb::area() const { + return w()*h(); + } + + int rect_ltrb::perimeter() const { + return 2 * w() + 2 * h(); + } + + void rect_ltrb::w(int ww) { + r = l + ww; + } + + void rect_ltrb::h(int hh) { + b = t + hh; + } + + rect_xywh::rect_xywh() : x(0), y(0) {} + rect_xywh::rect_xywh(const rect_ltrb& rc) : x(rc.l), y(rc.t) { b(rc.b); r(rc.r); } + rect_xywh::rect_xywh(int x, int y, int w, int h) : x(x), y(y), rect_wh(w, h) {} + + rect_xywh::operator rect_ltrb() { + rect_ltrb rr(x, y, 0, 0); + rr.w(w); rr.h(h); + return rr; + } + + int rect_xywh::r() const { + return x + w; + }; + + int rect_xywh::b() const { + return y + h; + } + + void rect_xywh::r(int right) { + w = right - x; + } + + void rect_xywh::b(int bottom) { + h = bottom - y; + } + + int rect_wh::area() { + return w*h; + } + + int rect_wh::perimeter() { + return 2 * w + 2 * h; + } + + + rect_xywhf::rect_xywhf(const rect_ltrb& rr) : rect_xywh(rr), flipped(false) {} + rect_xywhf::rect_xywhf(int x, int y, int width, int height) : rect_xywh(x, y, width, height), flipped(false) {} + rect_xywhf::rect_xywhf() : flipped(false) {} + + void rect_xywhf::flip() { + flipped = !flipped; + std::swap(w, h); + } + +} diff --git a/WickedEngine/wiRectPacker.h b/WickedEngine/wiRectPacker.h new file mode 100644 index 000000000..cfe86baf8 --- /dev/null +++ b/WickedEngine/wiRectPacker.h @@ -0,0 +1,90 @@ +#pragma once +#include + +// NOTE: +// This is based on the rectpack2D library hosted here: https://github.com/TeamHypersomnia/rectpack2D + +/* of your interest: + +1. rect_xywhf - structure representing your rectangle object +members: +int x, y, w, h; +bool flipped; + +2. bin - structure representing resultant bin object +3. bool pack(rect_xywhf* const * v, int n, int max_side, std::vector& bins) - actual packing function +Arguments: +input/output: v - pointer to array of pointers to your rectangles (const here means that the pointers will point to the same rectangles after the call) +input: n - rectangles count + +input: max_side - maximum bins' side - algorithm works with square bins (in the end it may trim them to rectangular form). +for the algorithm to finish faster, pass a reasonable value (unreasonable would be passing 1 000 000 000 for packing 4 50x50 rectangles). +output: bins - vector to which the function will push_back() created bins, each of them containing vector to pointers of rectangles from "v" belonging to that particular bin. +Every bin also keeps information about its width and height of course, none of the dimensions is bigger than max_side. + +returns true on success, false if one of the rectangles' dimension was bigger than max_side + +You want to your rectangles representing your textures/glyph objects with GL_MAX_TEXTURE_SIZE as max_side, +then for each bin iterate through its rectangles, typecast each one to your own structure (or manually add userdata) and then memcpy its pixel contents (rotated by 90 degrees if "flipped" rect_xywhf's member is true) +to the array representing your texture atlas to the place specified by the rectangle, then finally upload it with glTexImage2D. + +Algorithm doesn't create any new rectangles. +You just pass an array of pointers - rectangles' x/y/w/h/flipped are modified in place. +There is a vector of pointers for every resultant bin to let you know which ones belong to the particular bin. +The algorithm may swap the w and h fields for the sake of better fitting, the flag "flipped" will be set to true whenever this occurs. + +For description how to tune the algorithm and how it actually works see the .cpp file. + + +*/ + +namespace wiRectPacker +{ + + struct rect_ltrb; + struct rect_xywh; + + struct rect_wh { + rect_wh(const rect_ltrb&); + rect_wh(const rect_xywh&); + rect_wh(int w = 0, int h = 0); + int w, h, area(), perimeter(), + fits(const rect_wh& bigger) const; // 0 - no, 1 - yes, 2 - flipped, 3 - perfectly, 4 perfectly flipped + }; + + // rectangle implementing left/top/right/bottom behaviour + + struct rect_ltrb { + rect_ltrb(); + rect_ltrb(int left, int top, int right, int bottom); + int l, t, r, b, w() const, h() const, area() const, perimeter() const; + void w(int), h(int); + }; + + struct rect_xywh : public rect_wh { + rect_xywh(); + rect_xywh(const rect_ltrb&); + rect_xywh(int x, int y, int width, int height); + operator rect_ltrb(); + + int x, y, r() const, b() const; + void r(int), b(int); + }; + + struct rect_xywhf : public rect_xywh { + rect_xywhf(const rect_ltrb&); + rect_xywhf(int x, int y, int width, int height); + rect_xywhf(); + void flip(); + bool flipped; + }; + + + struct bin { + rect_wh size; + std::vector rects; + }; + + bool pack(rect_xywhf* const * v, int n, int max_side, std::vector& bins); + +} diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index 41419db06..71fb508bb 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -23,6 +23,8 @@ #include "wiGraphicsDevice_DX11.h" #include "wiTranslator.h" #include "lightCullingCSInterop.h" +#include "wiRectPacker.h" +#include "wiBackLog.h" using namespace wiGraphicsTypes; @@ -1409,6 +1411,7 @@ void wiRenderer::UpdatePerFrameData() culling.culledLights.clear(); culling.culledLight_count = 0; culling.culledEmittedParticleSystems.clear(); + culling.culledDecals.clear(); if (spTree != nullptr) { @@ -1506,6 +1509,20 @@ void wiRenderer::UpdatePerFrameData() std::sort(culling.culledEmittedParticleSystems.begin(), culling.culledEmittedParticleSystems.end(), [&](const wiEmittedParticle* a, const wiEmittedParticle* b) { return wiMath::DistanceSquared(camera->translation, a->bounding_box->getCenter()) > wiMath::DistanceSquared(camera->translation,b->bounding_box->getCenter()); }); + + for (Model* model : GetScene().models) + { + if (model->decals.empty()) + continue; + + for (Decal* decal : model->decals) + { + if ((decal->texture || decal->normal) && getCamera()->frustum.CheckBox(decal->bounds)) + { + x.second.culledDecals.push_back(decal); + } + } + } } } refCam->Reflect(cam, waterPlane.getXMFLOAT4()); @@ -1644,6 +1661,8 @@ void wiRenderer::UpdateRenderData(GRAPHICSTHREAD threadID) const FrameCulling& mainCameraCulling = frameCullings[getCamera()]; + + ManageDecalAtlas(threadID); // Fill Light Array with lights in the frustum { @@ -1703,11 +1722,30 @@ void wiRenderer::UpdateRenderData(GRAPHICSTHREAD threadID) lightCounter++; if (lightCounter == MAX_LIGHTS) { - assert(0 && "Maximum Lightcount exceeded for a single tiled lightculling pass! Please redefine MAX_LIGHTS to fit!"); lightCounter--; break; } } + if (lightCounter < MAX_LIGHTS) + { + for (Decal* decal : mainCameraCulling.culledDecals) + { + XMStoreFloat3(&lightArray[lightCounter].posVS, XMVector3TransformCoord(XMLoadFloat3(&decal->translation), viewMatrix)); + lightArray[lightCounter].distance = max(decal->scale.x, max(decal->scale.y, decal->scale.z)) * 2; + lightArray[lightCounter].shadowMatrix[0] = XMMatrixTranspose(XMMatrixInverse(nullptr, XMLoadFloat4x4(&decal->world))); + lightArray[lightCounter].texMulAdd = decal->atlasMulAdd; + lightArray[lightCounter].col = XMFLOAT4(1, 1, 1, decal->GetOpacity()); + lightArray[lightCounter].type = 100; + + lightCounter++; + if (lightCounter == MAX_LIGHTS) + { + lightCounter--; + break; + } + } + } + assert(lightCounter < MAX_LIGHTS && "Maximum Lightcount exceeded for a single tiled lightculling pass! Please redefine MAX_LIGHTS to fit!"); GetDevice()->UpdateBuffer(resourceBuffers[RBTYPE_LIGHTARRAY], lightArray, threadID, (int)(sizeof(LightArrayType)*lightCounter)); } @@ -1817,7 +1855,8 @@ void wiRenderer::OcclusionCulling_Read() GetDevice()->EventEnd(); } } -void wiRenderer::UpdateImages(){ +void wiRenderer::UpdateImages() +{ for (wiSprite* x : images) x->Update(GameSpeed); for (wiSprite* x : waterRipples) @@ -1826,12 +1865,13 @@ void wiRenderer::UpdateImages(){ ManageImages(); ManageWaterRipples(); } -void wiRenderer::ManageImages(){ - while( - !images.empty() && - (images.front()->effects.opacity <= 0 + FLT_EPSILON || images.front()->effects.fade==1) - ) - images.pop_front(); +void wiRenderer::ManageImages() +{ + while (!images.empty() && + (images.front()->effects.opacity <= 0 + FLT_EPSILON || images.front()->effects.fade == 1)) + { + images.pop_front(); + } } void wiRenderer::PutDecal(Decal* decal) { @@ -3492,41 +3532,43 @@ void wiRenderer::DrawSun(GRAPHICSTHREAD threadID) void wiRenderer::DrawDecals(Camera* camera, GRAPHICSTHREAD threadID) { + GraphicsDevice* device = GetDevice(); + bool boundCB = false; for (Model* model : GetScene().models) { if (model->decals.empty()) continue; - GetDevice()->EventBegin(L"Decals", threadID); + device->EventBegin(L"Decals", threadID); if (!boundCB) { boundCB = true; - GetDevice()->BindConstantBufferPS(constantBuffers[CBTYPE_DECAL], CB_GETBINDSLOT(DecalCB),threadID); + device->BindConstantBufferPS(constantBuffers[CBTYPE_DECAL], CB_GETBINDSLOT(DecalCB),threadID); } - //BindResourcePS(depth, 1, threadID); - GetDevice()->BindVS(vertexShaders[VSTYPE_DECAL], threadID); - GetDevice()->BindPS(pixelShaders[PSTYPE_DECAL], threadID); - GetDevice()->BindRasterizerState(rasterizers[RSTYPE_BACK], threadID); - GetDevice()->BindBlendState(blendStates[BSTYPE_TRANSPARENT], threadID); - GetDevice()->BindDepthStencilState(depthStencils[DSSTYPE_STENCILREAD_MATCH], STENCILREF::STENCILREF_DEFAULT, threadID); - GetDevice()->BindVertexLayout(nullptr, threadID); - GetDevice()->BindPrimitiveTopology(PRIMITIVETOPOLOGY::TRIANGLELIST, threadID); + device->BindVS(vertexShaders[VSTYPE_DECAL], threadID); + device->BindPS(pixelShaders[PSTYPE_DECAL], threadID); + device->BindRasterizerState(rasterizers[RSTYPE_BACK], threadID); + device->BindBlendState(blendStates[BSTYPE_TRANSPARENT], threadID); + device->BindDepthStencilState(depthStencils[DSSTYPE_STENCILREAD_MATCH], STENCILREF::STENCILREF_DEFAULT, threadID); + device->BindVertexLayout(nullptr, threadID); + device->BindPrimitiveTopology(PRIMITIVETOPOLOGY::TRIANGLELIST, threadID); - for (Decal* decal : model->decals) { + for (Decal* decal : model->decals) + { if ((decal->texture || decal->normal) && camera->frustum.CheckBox(decal->bounds)) { - GetDevice()->BindResourcePS(decal->texture, TEXSLOT_ONDEMAND0, threadID); - GetDevice()->BindResourcePS(decal->normal, TEXSLOT_ONDEMAND1, threadID); + device->BindResourcePS(decal->texture, TEXSLOT_ONDEMAND0, threadID); + device->BindResourcePS(decal->normal, TEXSLOT_ONDEMAND1, threadID); XMMATRIX decalWorld = XMLoadFloat4x4(&decal->world); MiscCB dcbvs; dcbvs.mTransform =XMMatrixTranspose(decalWorld*camera->GetViewProjection()); - GetDevice()->UpdateBuffer(constantBuffers[CBTYPE_MISC], &dcbvs, threadID); + device->UpdateBuffer(constantBuffers[CBTYPE_MISC], &dcbvs, threadID); DecalCB dcbps; dcbps.mDecalVP = XMMatrixTranspose(XMMatrixInverse(nullptr, decalWorld)); @@ -3536,17 +3578,17 @@ void wiRenderer::DrawDecals(Camera* camera, GRAPHICSTHREAD threadID) if (decal->normal != nullptr) dcbps.hasTexNor |= 0x0000010; XMStoreFloat3(&dcbps.eye, camera->GetEye()); - dcbps.opacity = wiMath::Clamp((decal->life <= -2 ? 1 : decal->life < decal->fadeStart ? decal->life / decal->fadeStart : 1), 0, 1); + dcbps.opacity = decal->GetOpacity(); dcbps.front = decal->front; - GetDevice()->UpdateBuffer(constantBuffers[CBTYPE_DECAL], &dcbps, threadID); + device->UpdateBuffer(constantBuffers[CBTYPE_DECAL], &dcbps, threadID); - GetDevice()->Draw(36, threadID); + device->Draw(36, threadID); } } - GetDevice()->EventEnd(threadID); + device->EventEnd(threadID); } } @@ -3668,7 +3710,7 @@ void wiRenderer::ComputeTiledLightCulling(GRAPHICSTHREAD threadID) dispatchParams.numThreadGroups[0] = (UINT)ceilf(dispatchParams.numThreads[0] / (float)BLOCK_SIZE); dispatchParams.numThreadGroups[1] = (UINT)ceilf(dispatchParams.numThreads[1] / (float)BLOCK_SIZE); dispatchParams.numThreadGroups[2] = 1; - dispatchParams.value0 = frameCullings[getCamera()].culledLight_count; // light count (forward_list does not have size()) + dispatchParams.value0 = frameCullings[getCamera()].culledLight_count + (UINT)frameCullings[getCamera()].culledDecals.size(); // light count (forward_list does not have size()) device->UpdateBuffer(constantBuffers[CBTYPE_DISPATCHPARAMS], &dispatchParams, threadID); device->BindConstantBufferCS(constantBuffers[CBTYPE_DISPATCHPARAMS], CB_GETBINDSLOT(DispatchParamsCB), threadID); } @@ -3834,6 +3876,83 @@ void wiRenderer::ResolveMSAADepthBuffer(Texture2D* dst, Texture2D* src, GRAPHICS GetDevice()->EventEnd(); } +void wiRenderer::ManageDecalAtlas(GRAPHICSTHREAD threadID) +{ + GraphicsDevice* device = GetDevice(); + + static Texture2D* atlasTexture = nullptr; + + for (Model* model : GetScene().models) + { + if (model->decals.empty()) + continue; + + for (Decal* decal : model->decals) + { + using namespace wiRectPacker; + static map storedTextures; + + if (storedTextures.find(decal->texture) == storedTextures.end()) + { + // we need to pack this decal texture into the atlas + rect_xywhf newRect = rect_xywhf(0, 0, decal->texture->GetDesc().Width, decal->texture->GetDesc().Height); + storedTextures[decal->texture] = newRect; + + rect_xywhf** out_rects = new rect_xywhf*[storedTextures.size()]; + int i = 0; + for (auto& it : storedTextures) + { + out_rects[i] = &it.second; + i++; + } + + vector bins; + if (pack(out_rects, (int)storedTextures.size(), 16384, bins)) + { + assert(bins.size() == 1 && "Decal atlas packing into single texture failed!"); + + SAFE_DELETE(atlasTexture); + + Texture2DDesc desc; + ZeroMemory(&desc, sizeof(desc)); + desc.Width = (UINT)bins[0].size.w; + desc.Height = (UINT)bins[0].size.h; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = FORMAT_B8G8R8A8_UNORM; // png decals are loaded into this format! todo: DXT! + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = USAGE_DEFAULT; + desc.BindFlags = BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = 0; + desc.MiscFlags = 0; + + device->CreateTexture2D(&desc, nullptr, &atlasTexture); + + for (auto& it : storedTextures) + { + device->CopyTexture2D_Region(atlasTexture, 0, it.second.x, it.second.y, it.first, 0, threadID); + } + } + else + { + wiBackLog::post("Decal atlas packing failed!"); + } + } + + rect_xywhf rect = storedTextures[decal->texture]; + Texture2DDesc desc = atlasTexture->GetDesc(); + decal->atlasMulAdd = XMFLOAT4((float)rect.w / (float)desc.Width, (float)rect.h / (float)desc.Height, (float)rect.x / (float)desc.Width, (float)rect.y / (float)desc.Height); + } + + } + + if (atlasTexture != nullptr) + { + device->BindResourcePS(atlasTexture, TEXSLOT_DECALATLAS, threadID); + } +} + void wiRenderer::UpdateWorldCB(GRAPHICSTHREAD threadID) { static WorldCB prevcb[GRAPHICSTHREAD_COUNT]; diff --git a/WickedEngine/wiRenderer.h b/WickedEngine/wiRenderer.h index d34b394c0..3adbf1530 100644 --- a/WickedEngine/wiRenderer.h +++ b/WickedEngine/wiRenderer.h @@ -251,6 +251,7 @@ public: int shadowMap_index; float coneAngle; float coneAngleCos; + XMFLOAT4 texMulAdd; XMMATRIX shadowMatrix[3]; STRUCTUREDBUFFER_SETBINDSLOT(SBSLOT_LIGHTARRAY) @@ -364,6 +365,7 @@ public: CulledList culledLights; UINT culledLight_count; // because forward_list doesn't have size() vector culledEmittedParticleSystems; + list culledDecals; }; static unordered_map frameCullings; @@ -409,6 +411,8 @@ public: static void ComputeTiledLightCulling(GRAPHICSTHREAD threadID); static void ResolveMSAADepthBuffer(wiGraphicsTypes::Texture2D* dst, wiGraphicsTypes::Texture2D* src, GRAPHICSTHREAD threadID); + + static void ManageDecalAtlas(GRAPHICSTHREAD threadID); static XMVECTOR GetSunPosition(); static XMFLOAT4 GetSunColor(); diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 1a89f3225..c3fe2488a 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -7,7 +7,7 @@ namespace wiVersion // minor features, major updates const int minor = 9; // minor bug fixes, alterations, refactors, updates - const int revision = 51; + const int revision = 52; long GetVersion()