From c3065ff4b1c9774cd68ce1f5235ac3733fc7ccac Mon Sep 17 00:00:00 2001 From: Turanszki Janos Date: Fri, 10 Nov 2017 00:54:31 +0000 Subject: [PATCH 01/10] ocean simulator port started --- Editor/Editor.cpp | 19 + WickedEngine/WickedEngine.h | 1 + WickedEngine/WickedEngine_SHADERS.vcxproj | 22 + .../WickedEngine_SHADERS.vcxproj.filters | 21 + WickedEngine/WickedEngine_SHARED.vcxitems | 4 + .../WickedEngine_SHARED.vcxitems.filters | 12 + WickedEngine/fft_512x512_c2c_CS.hlsl | 180 ++++++ WickedEngine/fft_512x512_c2c_v2_CS.hlsl | 3 + WickedEngine/oceanGradientFoldingPS.hlsl | 33 + WickedEngine/oceanHF.hlsli | 64 ++ WickedEngine/oceanQuadVS.hlsl | 12 + WickedEngine/oceanSimulatorCS.hlsl | 86 +++ WickedEngine/oceanUpdateDisplacementPS.hlsl | 18 + WickedEngine/wiFFTGenerator.cpp | 233 +++++++ WickedEngine/wiFFTGenerator.h | 68 ++ WickedEngine/wiGraphicsDevice_DX11.cpp | 2 +- WickedEngine/wiOceanSimulator.cpp | 612 ++++++++++++++++++ WickedEngine/wiOceanSimulator.h | 119 ++++ models/Sample/textures/water_bump.dds | Bin 0 -> 349652 bytes 19 files changed, 1508 insertions(+), 1 deletion(-) create mode 100644 WickedEngine/fft_512x512_c2c_CS.hlsl create mode 100644 WickedEngine/fft_512x512_c2c_v2_CS.hlsl create mode 100644 WickedEngine/oceanGradientFoldingPS.hlsl create mode 100644 WickedEngine/oceanHF.hlsli create mode 100644 WickedEngine/oceanQuadVS.hlsl create mode 100644 WickedEngine/oceanSimulatorCS.hlsl create mode 100644 WickedEngine/oceanUpdateDisplacementPS.hlsl create mode 100644 WickedEngine/wiFFTGenerator.cpp create mode 100644 WickedEngine/wiFFTGenerator.h create mode 100644 WickedEngine/wiOceanSimulator.cpp create mode 100644 WickedEngine/wiOceanSimulator.h create mode 100644 models/Sample/textures/water_bump.dds diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 5d5e6e4fb..16a13946c 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -208,6 +208,9 @@ void ResetHistory(); wiArchive* AdvanceHistory(); void ConsumeHistoryOperation(bool undo); +#include "wiOceanSimulator.h" +OceanSimulator* ocean; + void EditorComponent::ChangeRenderPath(RENDERPATH path) { SAFE_DELETE(renderPath); @@ -303,6 +306,17 @@ void EditorComponent::Load() { __super::Load(); + OceanParameter params; + params.choppy_scale = 1; + params.dmap_dim = 64; + params.patch_length = 1000; + params.time_scale = 1; + params.wave_amplitude = 1.0f; + params.wind_dependency = 0.5f; + params.wind_dir = XMFLOAT2(1, 1); + params.wind_speed = 100; + ocean = new OceanSimulator(params); + translator = new wiTranslator; translator->enabled = false; @@ -1308,6 +1322,8 @@ void EditorComponent::Update(float dt) } void EditorComponent::Render() { + ocean->updateDisplacementMap(1.0f); + // hover box { if (hovered.object != nullptr) @@ -1521,6 +1537,9 @@ void EditorComponent::Compose() } + + wiImage::Draw(ocean->getD3D11DisplacementMap(), wiImageEffects(100, 100, 100, 100), GRAPHICSTHREAD_IMMEDIATE); + wiImage::Draw(ocean->getD3D11GradientMap(), wiImageEffects(200, 100, 100, 100), GRAPHICSTHREAD_IMMEDIATE); } void EditorComponent::Unload() { diff --git a/WickedEngine/WickedEngine.h b/WickedEngine/WickedEngine.h index d815a984d..2da68da3a 100644 --- a/WickedEngine/WickedEngine.h +++ b/WickedEngine/WickedEngine.h @@ -56,6 +56,7 @@ #include "wiSpinLock.h" #include "wiRectPacker.h" #include "wiProfiler.h" +#include "wiOceanSimulator.h" #include "RenderableComponent.h" #include "Renderable2DComponent.h" diff --git a/WickedEngine/WickedEngine_SHADERS.vcxproj b/WickedEngine/WickedEngine_SHADERS.vcxproj index 97efc1bdf..117279127 100644 --- a/WickedEngine/WickedEngine_SHADERS.vcxproj +++ b/WickedEngine/WickedEngine_SHADERS.vcxproj @@ -32,6 +32,7 @@ + @@ -182,6 +183,14 @@ Vertex + + Compute + 5.0 + + + Compute + 5.0 + Pixel @@ -442,6 +451,19 @@ Vertex + + Pixel + + + Vertex + + + Compute + 5.0 + + + Pixel + Pixel diff --git a/WickedEngine/WickedEngine_SHADERS.vcxproj.filters b/WickedEngine/WickedEngine_SHADERS.vcxproj.filters index 0e14625f6..9d994e897 100644 --- a/WickedEngine/WickedEngine_SHADERS.vcxproj.filters +++ b/WickedEngine/WickedEngine_SHADERS.vcxproj.filters @@ -115,6 +115,9 @@ HF + + HF + @@ -648,6 +651,24 @@ CS + + CS + + + CS + + + CS + + + VS + + + PS + + + PS + diff --git a/WickedEngine/WickedEngine_SHARED.vcxitems b/WickedEngine/WickedEngine_SHARED.vcxitems index a4019a06b..21b08991c 100644 --- a/WickedEngine/WickedEngine_SHARED.vcxitems +++ b/WickedEngine/WickedEngine_SHARED.vcxitems @@ -240,6 +240,7 @@ + @@ -333,6 +334,7 @@ + @@ -511,6 +513,7 @@ + @@ -671,6 +674,7 @@ + diff --git a/WickedEngine/WickedEngine_SHARED.vcxitems.filters b/WickedEngine/WickedEngine_SHARED.vcxitems.filters index 5a07e96aa..bf7044b98 100644 --- a/WickedEngine/WickedEngine_SHARED.vcxitems.filters +++ b/WickedEngine/WickedEngine_SHARED.vcxitems.filters @@ -1104,6 +1104,12 @@ ENGINE\Graphics\GPUMapping + + ENGINE\Graphics + + + ENGINE\Graphics + @@ -1889,6 +1895,12 @@ ENGINE\Components + + ENGINE\Graphics + + + ENGINE\Graphics + diff --git a/WickedEngine/fft_512x512_c2c_CS.hlsl b/WickedEngine/fft_512x512_c2c_CS.hlsl new file mode 100644 index 000000000..841d66ad0 --- /dev/null +++ b/WickedEngine/fft_512x512_c2c_CS.hlsl @@ -0,0 +1,180 @@ +// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. +// +// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED +// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS +// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA +// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR +// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS +// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY +// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, +// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// Please direct any bugs or questions to SDKFeedback@nvidia.com + +#define COS_PI_4_16 0.70710678118654752440084436210485f +#define TWIDDLE_1_8 COS_PI_4_16, -COS_PI_4_16 +#define TWIDDLE_3_8 -COS_PI_4_16, -COS_PI_4_16 + +#define COHERENCY_GRANULARITY 128 + +cbuffer cbChangePerCall +{ + uint thread_count; + uint ostride; + uint istride; + uint pstride; + float phase_base; +}; + + +void FT2(inout float2 a, inout float2 b) +{ + float t; + + t = a.x; + a.x += b.x; + b.x = t - b.x; + + t = a.y; + a.y += b.y; + b.y = t - b.y; +} + +void CMUL_forward(inout float2 a, float bx, float by) +{ + float t = a.x; + a.x = t * bx - a.y * by; + a.y = t * by + a.y * bx; +} + +void UPD_forward(inout float2 a, inout float2 b) +{ + float A = a.x; + float B = b.y; + + a.x += b.y; + b.y = a.y + b.x; + a.y -= b.x; + b.x = A - B; +} + +void FFT_forward_4(inout float2 D[8]) +{ + FT2(D[0], D[2]); + FT2(D[1], D[3]); + FT2(D[0], D[1]); + + UPD_forward(D[2], D[3]); +} + +void FFT_forward_8(inout float2 D[8]) +{ + FT2(D[0], D[4]); + FT2(D[1], D[5]); + FT2(D[2], D[6]); + FT2(D[3], D[7]); + + UPD_forward(D[4], D[6]); + UPD_forward(D[5], D[7]); + + CMUL_forward(D[5], TWIDDLE_1_8); + CMUL_forward(D[7], TWIDDLE_3_8); + + FFT_forward_4(D); + FT2(D[4], D[5]); + FT2(D[6], D[7]); +} + +void TWIDDLE(inout float2 d, float phase) +{ + float tx, ty; + + sincos(phase, ty, tx); + float t = d.x; + d.x = t * tx - d.y * ty; + d.y = t * ty + d.y * tx; +} + +void TWIDDLE_8(inout float2 D[8], float phase) +{ + TWIDDLE(D[4], 1 * phase); + TWIDDLE(D[2], 2 * phase); + TWIDDLE(D[6], 3 * phase); + TWIDDLE(D[1], 4 * phase); + TWIDDLE(D[5], 5 * phase); + TWIDDLE(D[3], 6 * phase); + TWIDDLE(D[7], 7 * phase); +} + +StructuredBuffer g_SrcData : register(t0); +RWStructuredBuffer g_DstData : register(u0); + +#ifndef FFT_V2 + +[numthreads(COHERENCY_GRANULARITY, 1, 1)] +void main(uint3 thread_id : SV_DispatchThreadID) +{ + if (thread_id.x >= thread_count) + return; + + // Fetch 8 complex numbers + float2 D[8]; + + uint i; + uint imod = thread_id & (istride - 1); + uint iaddr = ((thread_id - imod) << 3) + imod; + for (i = 0; i < 8; i++) + D[i] = g_SrcData[iaddr + i * istride]; + + // Math + FFT_forward_8(D); + uint p = thread_id & (istride - pstride); + float phase = phase_base * (float)p; + TWIDDLE_8(D, phase); + + // Store the result + uint omod = thread_id & (ostride - 1); + uint oaddr = ((thread_id - omod) << 3) + omod; + g_DstData[oaddr + 0 * ostride] = D[0]; + g_DstData[oaddr + 1 * ostride] = D[4]; + g_DstData[oaddr + 2 * ostride] = D[2]; + g_DstData[oaddr + 3 * ostride] = D[6]; + g_DstData[oaddr + 4 * ostride] = D[1]; + g_DstData[oaddr + 5 * ostride] = D[5]; + g_DstData[oaddr + 6 * ostride] = D[3]; + g_DstData[oaddr + 7 * ostride] = D[7]; +} + +#else + +[numthreads(COHERENCY_GRANULARITY, 1, 1)] +void main(uint3 thread_id : SV_DispatchThreadID) +{ + if (thread_id.x >= thread_count) + return; + + // Fetch 8 complex numbers + uint i; + float2 D[8]; + uint iaddr = thread_id << 3; + for (i = 0; i < 8; i++) + D[i] = g_SrcData[iaddr + i]; + + // Math + FFT_forward_8(D); + + // Store the result + uint omod = thread_id & (ostride - 1); + uint oaddr = ((thread_id - omod) << 3) + omod; + g_DstData[oaddr + 0 * ostride] = D[0]; + g_DstData[oaddr + 1 * ostride] = D[4]; + g_DstData[oaddr + 2 * ostride] = D[2]; + g_DstData[oaddr + 3 * ostride] = D[6]; + g_DstData[oaddr + 4 * ostride] = D[1]; + g_DstData[oaddr + 5 * ostride] = D[5]; + g_DstData[oaddr + 6 * ostride] = D[3]; + g_DstData[oaddr + 7 * ostride] = D[7]; +} + +#endif // FFT_V2 diff --git a/WickedEngine/fft_512x512_c2c_v2_CS.hlsl b/WickedEngine/fft_512x512_c2c_v2_CS.hlsl new file mode 100644 index 000000000..c2ad805cf --- /dev/null +++ b/WickedEngine/fft_512x512_c2c_v2_CS.hlsl @@ -0,0 +1,3 @@ +#define FFT_V2 + +#include "fft_512x512_c2c_CS.hlsl" diff --git a/WickedEngine/oceanGradientFoldingPS.hlsl b/WickedEngine/oceanGradientFoldingPS.hlsl new file mode 100644 index 000000000..2908024b8 --- /dev/null +++ b/WickedEngine/oceanGradientFoldingPS.hlsl @@ -0,0 +1,33 @@ +#include "oceanHF.hlsli" + +// Displacement -> Normal, Folding +float4 main(VS_QUAD_OUTPUT In) : SV_Target +{ + // Sample neighbour texels + float2 one_texel = float2(1.0f / (float)g_OutWidth, 1.0f / (float)g_OutHeight); + + float2 tc_left = float2(In.TexCoord.x - one_texel.x, In.TexCoord.y); + float2 tc_right = float2(In.TexCoord.x + one_texel.x, In.TexCoord.y); + float2 tc_back = float2(In.TexCoord.x, In.TexCoord.y - one_texel.y); + float2 tc_front = float2(In.TexCoord.x, In.TexCoord.y + one_texel.y); + + float3 displace_left = g_samplerDisplacementMap.Sample(LinearSampler, tc_left).xyz; + float3 displace_right = g_samplerDisplacementMap.Sample(LinearSampler, tc_right).xyz; + float3 displace_back = g_samplerDisplacementMap.Sample(LinearSampler, tc_back).xyz; + float3 displace_front = g_samplerDisplacementMap.Sample(LinearSampler, tc_front).xyz; + + // Do not store the actual normal value. Using gradient instead, which preserves two differential values. + float2 gradient = { -(displace_right.z - displace_left.z), -(displace_front.z - displace_back.z) }; + + + // Calculate Jacobian corelation from the partial differential of height field + float2 Dx = (displace_right.xy - displace_left.xy) * g_ChoppyScale * g_GridLen; + float2 Dy = (displace_front.xy - displace_back.xy) * g_ChoppyScale * g_GridLen; + float J = (1.0f + Dx.x) * (1.0f + Dy.y) - Dx.y * Dy.x; + + // Practical subsurface scale calculation: max[0, (1 - J) + Amplitude * (2 * Coverage - 1)]. + float fold = max(1.0f - J, 0); + + // Output + return float4(gradient, 0, fold); +} diff --git a/WickedEngine/oceanHF.hlsli b/WickedEngine/oceanHF.hlsli new file mode 100644 index 000000000..fed79e654 --- /dev/null +++ b/WickedEngine/oceanHF.hlsli @@ -0,0 +1,64 @@ +#ifndef _OCEAN_SIMULATOR_HF_ +#define _OCEAN_SIMULATOR_HF_ + +// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. +// +// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED +// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS +// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA +// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR +// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS +// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY +// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, +// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// Please direct any bugs or questions to SDKFeedback@nvidia.com + +//---------------------------------------- Vertex Shaders ------------------------------------------ +struct VS_QUAD_OUTPUT +{ + float4 Position : SV_POSITION; // vertex position + float2 TexCoord : TEXCOORD0; // vertex texture coords +}; + +VS_QUAD_OUTPUT QuadVS(float4 vPos : POSITION) +{ + VS_QUAD_OUTPUT Output; + + Output.Position = vPos; + Output.TexCoord.x = 0.5f + vPos.x * 0.5f; + Output.TexCoord.y = 0.5f - vPos.y * 0.5f; + + return Output; +} + +//----------------------------------------- Pixel Shaders ------------------------------------------ + +// Textures and sampling states +Texture2D g_samplerDisplacementMap : register(t0); + +SamplerState LinearSampler : register(s0); + +// Constants +cbuffer cbImmutable : register(b0) +{ + uint g_ActualDim; + uint g_InWidth; + uint g_OutWidth; + uint g_OutHeight; + uint g_DxAddressOffset; + uint g_DyAddressOffset; +}; + +cbuffer cbChangePerFrame : register(b1) +{ + float g_Time; + float g_ChoppyScale; + float g_GridLen; +}; + +// The following three should contains only real numbers. But we have only C2C FFT now. +StructuredBuffer g_InputDxyz : register(t0); + +#endif // _OCEAN_SIMULATOR_HF_ diff --git a/WickedEngine/oceanQuadVS.hlsl b/WickedEngine/oceanQuadVS.hlsl new file mode 100644 index 000000000..1578c0c98 --- /dev/null +++ b/WickedEngine/oceanQuadVS.hlsl @@ -0,0 +1,12 @@ +#include "oceanHF.hlsli" + +VS_QUAD_OUTPUT main(float4 vPos : POSITION) +{ + VS_QUAD_OUTPUT Output; + + Output.Position = vPos; + Output.TexCoord.x = 0.5f + vPos.x * 0.5f; + Output.TexCoord.y = 0.5f - vPos.y * 0.5f; + + return Output; +} diff --git a/WickedEngine/oceanSimulatorCS.hlsl b/WickedEngine/oceanSimulatorCS.hlsl new file mode 100644 index 000000000..54d5043d9 --- /dev/null +++ b/WickedEngine/oceanSimulatorCS.hlsl @@ -0,0 +1,86 @@ +// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. +// +// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED +// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS +// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA +// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR +// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS +// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY +// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, +// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// Please direct any bugs or questions to SDKFeedback@nvidia.com + +#define PI 3.1415926536f +#define BLOCK_SIZE_X 16 +#define BLOCK_SIZE_Y 16 + +cbuffer cbImmutable : register(b0) +{ + uint g_ActualDim; + uint g_InWidth; + uint g_OutWidth; + uint g_OutHeight; + uint g_DtxAddressOffset; + uint g_DtyAddressOffset; +}; + +cbuffer cbChangePerFrame : register(b1) +{ + float g_Time; + float g_ChoppyScale; +}; + +StructuredBuffer g_InputH0 : register(t0); +StructuredBuffer g_InputOmega : register(t1); +RWStructuredBuffer g_OutputHt : register(u0); + + +//---------------------------------------- Compute Shaders ----------------------------------------- + +// Pre-FFT data preparation: + +// Notice: In CS5.0, we can output up to 8 RWBuffers but in CS4.x only one output buffer is allowed, +// that way we have to allocate one big buffer and manage the offsets manually. The restriction is +// not caused by NVIDIA GPUs and does not present on NVIDIA GPUs when using other computing APIs like +// CUDA and OpenCL. + +// H(0) -> H(t) +[numthreads(BLOCK_SIZE_X, BLOCK_SIZE_Y, 1)] +void main(uint3 DTid : SV_DispatchThreadID) +{ + int in_index = DTid.y * g_InWidth + DTid.x; + int in_mindex = (g_ActualDim - DTid.y) * g_InWidth + (g_ActualDim - DTid.x); + int out_index = DTid.y * g_OutWidth + DTid.x; + + // H(0) -> H(t) + float2 h0_k = g_InputH0[in_index]; + float2 h0_mk = g_InputH0[in_mindex]; + float sin_v, cos_v; + sincos(g_InputOmega[in_index] * g_Time, sin_v, cos_v); + + float2 ht; + ht.x = (h0_k.x + h0_mk.x) * cos_v - (h0_k.y + h0_mk.y) * sin_v; + ht.y = (h0_k.x - h0_mk.x) * sin_v + (h0_k.y - h0_mk.y) * cos_v; + + // H(t) -> Dx(t), Dy(t) + float kx = DTid.x - g_ActualDim * 0.5f; + float ky = DTid.y - g_ActualDim * 0.5f; + float sqr_k = kx * kx + ky * ky; + float rsqr_k = 0; + if (sqr_k > 1e-12f) + rsqr_k = 1 / sqrt(sqr_k); + //float rsqr_k = 1 / sqrtf(kx * kx + ky * ky); + kx *= rsqr_k; + ky *= rsqr_k; + float2 dt_x = float2(ht.y * kx, -ht.x * kx); + float2 dt_y = float2(ht.y * ky, -ht.x * ky); + + if ((DTid.x < g_OutWidth) && (DTid.y < g_OutHeight)) + { + g_OutputHt[out_index] = ht; + g_OutputHt[out_index + g_DtxAddressOffset] = dt_x; + g_OutputHt[out_index + g_DtyAddressOffset] = dt_y; + } +} diff --git a/WickedEngine/oceanUpdateDisplacementPS.hlsl b/WickedEngine/oceanUpdateDisplacementPS.hlsl new file mode 100644 index 000000000..c65aad138 --- /dev/null +++ b/WickedEngine/oceanUpdateDisplacementPS.hlsl @@ -0,0 +1,18 @@ +#include "oceanHF.hlsli" + +// Post-FFT data wrap up: Dx, Dy, Dz -> Displacement +float4 main(VS_QUAD_OUTPUT In) : SV_Target +{ + uint index_x = (uint)(In.TexCoord.x * (float)g_OutWidth); + uint index_y = (uint)(In.TexCoord.y * (float)g_OutHeight); + uint addr = g_OutWidth * index_y + index_x; + + // cos(pi * (m1 + m2)) + int sign_correction = ((index_x + index_y) & 1) ? -1 : 1; + + float dx = g_InputDxyz[addr + g_DxAddressOffset].x * sign_correction * g_ChoppyScale; + float dy = g_InputDxyz[addr + g_DyAddressOffset].x * sign_correction * g_ChoppyScale; + float dz = g_InputDxyz[addr].x * sign_correction; + + return float4(dx, dy, dz, 1); +} diff --git a/WickedEngine/wiFFTGenerator.cpp b/WickedEngine/wiFFTGenerator.cpp new file mode 100644 index 000000000..4bd3717fc --- /dev/null +++ b/WickedEngine/wiFFTGenerator.cpp @@ -0,0 +1,233 @@ +// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. +// +// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED +// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS +// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA +// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR +// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS +// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY +// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, +// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// Please direct any bugs or questions to SDKFeedback@nvidia.com + +#include +#include +#include + +#include "wiFFTGenerator.h" +#include "wiResourceManager.h" +#include "wiRenderer.h" + +using namespace wiGraphicsTypes; + +static const GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE; // todo: make dynamic + +void radix008A(CSFFT512x512_Plan* fft_plan, + GPUUnorderedResource* pUAV_Dst, + GPUResource* pSRV_Src, + UINT thread_count, + UINT istride) +{ + // Setup execution configuration + UINT grid = thread_count / COHERENCY_GRANULARITY; + + GraphicsDevice* device = wiRenderer::GetDevice(); + + // Buffers + GPUResource* cs_srvs[1] = { pSRV_Src }; + device->BindResourcesCS(cs_srvs, 0, 1, threadID); + + GPUUnorderedResource* cs_uavs[1] = { pUAV_Dst }; + device->BindUnorderedAccessResourcesCS(cs_uavs, 0, 1, threadID); + + // Shader + if (istride > 1) + device->BindCS(fft_plan->pRadix008A_CS, threadID); + else + device->BindCS(fft_plan->pRadix008A_CS2, threadID); + + // Execute + device->Dispatch(grid, 1, 1, threadID); + + // Unbind resource + device->UnBindResources(0, 1, threadID); + device->UnBindUnorderedAccessResources(0, 1, threadID); +} + +void fft_512x512_c2c(CSFFT512x512_Plan* fft_plan, + GPUUnorderedResource* pUAV_Dst, + GPUResource* pSRV_Dst, + GPUResource* pSRV_Src) +{ + const UINT thread_count = fft_plan->slices * (512 * 512) / 8; + GPUUnorderedResource* pUAV_Tmp = fft_plan->pUAV_Tmp; + GPUResource* pSRV_Tmp = fft_plan->pSRV_Tmp; + GraphicsDevice* device = wiRenderer::GetDevice(); + GPUBuffer* cs_cbs; + + UINT istride = 512 * 512 / 8; + cs_cbs = fft_plan->pRadix008A_CB[0]; + device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + radix008A(fft_plan, pUAV_Tmp, pSRV_Src, thread_count, istride); + + istride /= 8; + cs_cbs = fft_plan->pRadix008A_CB[1]; + device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride); + + istride /= 8; + cs_cbs = fft_plan->pRadix008A_CB[2]; + device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + radix008A(fft_plan, pUAV_Tmp, pSRV_Dst, thread_count, istride); + + istride /= 8; + cs_cbs = fft_plan->pRadix008A_CB[3]; + device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride); + + istride /= 8; + cs_cbs = fft_plan->pRadix008A_CB[4]; + device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + radix008A(fft_plan, pUAV_Tmp, pSRV_Dst, thread_count, istride); + + istride /= 8; + cs_cbs = fft_plan->pRadix008A_CB[5]; + device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride); +} + +void create_cbuffers_512x512(CSFFT512x512_Plan* plan, GraphicsDevice* device, UINT slices) +{ + // Create 6 cbuffers for 512x512 transform. + + GPUBufferDesc cb_desc; + cb_desc.Usage = USAGE_IMMUTABLE; + cb_desc.BindFlags = BIND_CONSTANT_BUFFER; + cb_desc.CPUAccessFlags = 0; + cb_desc.MiscFlags = 0; + cb_desc.ByteWidth = 32;//sizeof(float) * 5; + cb_desc.StructureByteStride = 0; + + SubresourceData cb_data; + cb_data.SysMemPitch = 0; + cb_data.SysMemSlicePitch = 0; + + struct CB_Structure + { + UINT thread_count; + UINT ostride; + UINT istride; + UINT pstride; + float phase_base; + }; + + for (int i = 0; i < ARRAYSIZE(plan->pRadix008A_CB); ++i) + { + plan->pRadix008A_CB[i] = new GPUBuffer; + } + + // Buffer 0 + const UINT thread_count = slices * (512 * 512) / 8; + UINT ostride = 512 * 512 / 8; + UINT istride = ostride; + double phase_base = -TWO_PI / (512.0 * 512.0); + + CB_Structure cb_data_buf0 = { thread_count, ostride, istride, 512, (float)phase_base }; + cb_data.pSysMem = &cb_data_buf0; + + device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[0]); + assert(plan->pRadix008A_CB[0]); + + // Buffer 1 + istride /= 8; + phase_base *= 8.0; + + CB_Structure cb_data_buf1 = { thread_count, ostride, istride, 512, (float)phase_base }; + cb_data.pSysMem = &cb_data_buf1; + + device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[1]); + assert(plan->pRadix008A_CB[1]); + + // Buffer 2 + istride /= 8; + phase_base *= 8.0; + + CB_Structure cb_data_buf2 = { thread_count, ostride, istride, 512, (float)phase_base }; + cb_data.pSysMem = &cb_data_buf2; + + device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[2]); + assert(plan->pRadix008A_CB[2]); + + // Buffer 3 + istride /= 8; + phase_base *= 8.0; + ostride /= 512; + + CB_Structure cb_data_buf3 = { thread_count, ostride, istride, 1, (float)phase_base }; + cb_data.pSysMem = &cb_data_buf3; + + device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[3]); + assert(plan->pRadix008A_CB[3]); + + // Buffer 4 + istride /= 8; + phase_base *= 8.0; + + CB_Structure cb_data_buf4 = { thread_count, ostride, istride, 1, (float)phase_base }; + cb_data.pSysMem = &cb_data_buf4; + + device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[4]); + assert(plan->pRadix008A_CB[4]); + + // Buffer 5 + istride /= 8; + phase_base *= 8.0; + + CB_Structure cb_data_buf5 = { thread_count, ostride, istride, 1, (float)phase_base }; + cb_data.pSysMem = &cb_data_buf5; + + device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[5]); + assert(plan->pRadix008A_CB[5]); +} + +void fft512x512_create_plan(CSFFT512x512_Plan* plan, UINT slices) +{ + GraphicsDevice* device = wiRenderer::GetDevice(); + + plan->slices = slices; + + plan->pRadix008A_CS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "fft_512x512_c2c_CS.cso", wiResourceManager::COMPUTESHADER)); + plan->pRadix008A_CS2 = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "fft_512x512_c2c_v2_CS.cso", wiResourceManager::COMPUTESHADER)); + + + // Constants + // Create 6 cbuffers for 512x512 transform + create_cbuffers_512x512(plan, device, slices); + + // Temp buffer + GPUBufferDesc buf_desc; + buf_desc.ByteWidth = sizeof(float) * 2 * (512 * slices) * 512; + buf_desc.Usage = USAGE_DEFAULT; + buf_desc.BindFlags = BIND_UNORDERED_ACCESS | BIND_SHADER_RESOURCE; + buf_desc.CPUAccessFlags = 0; + buf_desc.MiscFlags = RESOURCE_MISC_BUFFER_STRUCTURED; + buf_desc.StructureByteStride = sizeof(float) * 2; + + plan->pBuffer_Tmp = new GPUBuffer; + device->CreateBuffer(&buf_desc, NULL, plan->pBuffer_Tmp); + + plan->pSRV_Tmp = (GPUResource*)plan->pBuffer_Tmp; + plan->pUAV_Tmp = (GPUUnorderedResource*)plan->pBuffer_Tmp; +} + +void fft512x512_destroy_plan(CSFFT512x512_Plan* plan) +{ + SAFE_DELETE(plan->pBuffer_Tmp); + SAFE_DELETE(plan->pRadix008A_CS); + SAFE_DELETE(plan->pRadix008A_CS2); + + for (int i = 0; i < 6; i++) + SAFE_DELETE(plan->pRadix008A_CB[i]); +} diff --git a/WickedEngine/wiFFTGenerator.h b/WickedEngine/wiFFTGenerator.h new file mode 100644 index 000000000..655a57407 --- /dev/null +++ b/WickedEngine/wiFFTGenerator.h @@ -0,0 +1,68 @@ +#ifndef _FFT_GENERATOR_H_ +#define _FFT_GENERATOR_H_ + +// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. +// +// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED +// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS +// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA +// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR +// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS +// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY +// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, +// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// Please direct any bugs or questions to SDKFeedback@nvidia.com + +#include "CommonInclude.h" +#include "wiGraphicsAPI.h" + + +//Memory access coherency (in threads) +#define COHERENCY_GRANULARITY 128 + + +/////////////////////////////////////////////////////////////////////////////// +// Common types +/////////////////////////////////////////////////////////////////////////////// + +typedef struct CSFFT_512x512_Data_t +{ + wiGraphicsTypes::ComputeShader* pRadix008A_CS; + wiGraphicsTypes::ComputeShader* pRadix008A_CS2; + + // More than one array can be transformed at same time + UINT slices; + + // For 512x512 config, we need 6 constant buffers + wiGraphicsTypes::GPUBuffer* pRadix008A_CB[6]; + + // Temporary buffers + wiGraphicsTypes::GPUBuffer* pBuffer_Tmp; + wiGraphicsTypes::GPUUnorderedResource* pUAV_Tmp; + wiGraphicsTypes::GPUResource* pSRV_Tmp; +} CSFFT512x512_Plan; + +//////////////////////////////////////////////////////////////////////////////// +// Common constants +//////////////////////////////////////////////////////////////////////////////// +#define TWO_PI 6.283185307179586476925286766559 + +#define FFT_DIMENSIONS 3U +#define FFT_PLAN_SIZE_LIMIT (1U << 27) + +#define FFT_FORWARD -1 +#define FFT_INVERSE 1 + + +void fft512x512_create_plan(CSFFT512x512_Plan* plan, UINT slices); +void fft512x512_destroy_plan(CSFFT512x512_Plan* plan); + +void fft_512x512_c2c(CSFFT512x512_Plan* fft_plan, + wiGraphicsTypes::GPUUnorderedResource* pUAV_Dst, + wiGraphicsTypes::GPUResource* pSRV_Dst, + wiGraphicsTypes::GPUResource* pSRV_Src); + + +#endif // _FFT_GENERATOR_H_ diff --git a/WickedEngine/wiGraphicsDevice_DX11.cpp b/WickedEngine/wiGraphicsDevice_DX11.cpp index 110a7842d..961185cb9 100644 --- a/WickedEngine/wiGraphicsDevice_DX11.cpp +++ b/WickedEngine/wiGraphicsDevice_DX11.cpp @@ -1405,7 +1405,7 @@ GraphicsDevice_DX11::GraphicsDevice_DX11(wiWindowRegistration::window_type windo } UINT createDeviceFlags = 0; - //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; + createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; D3D_DRIVER_TYPE driverTypes[] = { diff --git a/WickedEngine/wiOceanSimulator.cpp b/WickedEngine/wiOceanSimulator.cpp new file mode 100644 index 000000000..57220bf31 --- /dev/null +++ b/WickedEngine/wiOceanSimulator.cpp @@ -0,0 +1,612 @@ +// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. +// +// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED +// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS +// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA +// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR +// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS +// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY +// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, +// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// Please direct any bugs or questions to SDKFeedback@nvidia.com + +#include "wiOceanSimulator.h" +#include "wiRenderer.h" +#include "wiResourceManager.h" + +using namespace wiGraphicsTypes; + +static const GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE; // todo: make dynamic + +// Disable warning "conditional expression is constant" +#pragma warning(disable:4127) + + +#define HALF_SQRT_2 0.7071068f +#define GRAV_ACCEL 981.0f // The acceleration of gravity, cm/s^2 + +#define BLOCK_SIZE_X 16 +#define BLOCK_SIZE_Y 16 + +// Generating gaussian random number with mean 0 and standard deviation 1. +float Gauss() +{ + float u1 = rand() / (float)RAND_MAX; + float u2 = rand() / (float)RAND_MAX; + if (u1 < 1e-6f) + u1 = 1e-6f; + return sqrtf(-2 * logf(u1)) * cosf(2 * XM_PI * u2); +} + +// Phillips Spectrum +// K: normalized wave vector, W: wind direction, v: wind velocity, a: amplitude constant +float Phillips(XMFLOAT2 K, XMFLOAT2 W, float v, float a, float dir_depend) +{ + // largest possible wave from constant wind of velocity v + float l = v * v / GRAV_ACCEL; + // damp out waves with very small length w << l + float w = l / 1000; + + float Ksqr = K.x * K.x + K.y * K.y; + float Kcos = K.x * W.x + K.y * W.y; + float phillips = a * expf(-1 / (l * l * Ksqr)) / (Ksqr * Ksqr * Ksqr) * (Kcos * Kcos); + + // filter out waves moving opposite to wind + if (Kcos < 0) + phillips *= dir_depend; + + // damp out waves with very small length w << l + return phillips * expf(-Ksqr * w * w); +} + +void createBufferAndUAV(void* data, UINT byte_width, UINT byte_stride, GPUBuffer** ppBuffer) +{ + *ppBuffer = new GPUBuffer; + + // Create buffer + GPUBufferDesc buf_desc; + buf_desc.ByteWidth = byte_width; + buf_desc.Usage = USAGE_DEFAULT; + buf_desc.BindFlags = BIND_UNORDERED_ACCESS | BIND_SHADER_RESOURCE; + buf_desc.CPUAccessFlags = 0; + buf_desc.MiscFlags = RESOURCE_MISC_BUFFER_STRUCTURED; + buf_desc.StructureByteStride = byte_stride; + + SubresourceData init_data; + init_data.pSysMem = data; + + wiRenderer::GetDevice()->CreateBuffer(&buf_desc, data != NULL ? &init_data : NULL, *ppBuffer); + + + //assert(*ppBuffer); + + //// Create undordered access view + //D3D11_UNORDERED_ACCESS_VIEW_DESC uav_desc; + //uav_desc.Format = DXGI_FORMAT_UNKNOWN; + //uav_desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; + //uav_desc.Buffer.FirstElement = 0; + //uav_desc.Buffer.NumElements = byte_width / byte_stride; + //uav_desc.Buffer.Flags = 0; + + //pd3dDevice->CreateUnorderedAccessView(*ppBuffer, &uav_desc, ppUAV); + //assert(*ppUAV); + + //// Create shader resource view + //D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc; + //srv_desc.Format = DXGI_FORMAT_UNKNOWN; + //srv_desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; + //srv_desc.Buffer.FirstElement = 0; + //srv_desc.Buffer.NumElements = byte_width / byte_stride; + + //pd3dDevice->CreateShaderResourceView(*ppBuffer, &srv_desc, ppSRV); + //assert(*ppSRV); +} + +void createTextureAndViews(UINT width, UINT height, FORMAT format, Texture2D** ppTex) +{ + // Create 2D texture + Texture2DDesc tex_desc; + tex_desc.Width = width; + tex_desc.Height = height; + tex_desc.MipLevels = 0; + tex_desc.ArraySize = 1; + tex_desc.Format = format; + tex_desc.SampleDesc.Count = 1; + tex_desc.SampleDesc.Quality = 0; + tex_desc.Usage = USAGE_DEFAULT; + tex_desc.BindFlags = BIND_SHADER_RESOURCE | BIND_RENDER_TARGET; + tex_desc.CPUAccessFlags = 0; + tex_desc.MiscFlags = RESOURCE_MISC_GENERATE_MIPS; + + *ppTex = new Texture2D; + wiRenderer::GetDevice()->CreateTexture2D(&tex_desc, NULL, ppTex); + + + //assert(*ppTex); + + //// Create shader resource view + //(*ppTex)->GetDesc(&tex_desc); + //if (ppSRV) + //{ + // D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc; + // srv_desc.Format = format; + // srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + // srv_desc.Texture2D.MipLevels = tex_desc.MipLevels; + // srv_desc.Texture2D.MostDetailedMip = 0; + + // pd3dDevice->CreateShaderResourceView(*ppTex, &srv_desc, ppSRV); + // assert(*ppSRV); + //} + + //// Create render target view + //if (ppRTV) + //{ + // D3D11_RENDER_TARGET_VIEW_DESC rtv_desc; + // rtv_desc.Format = format; + // rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + // rtv_desc.Texture2D.MipSlice = 0; + + // pd3dDevice->CreateRenderTargetView(*ppTex, &rtv_desc, ppRTV); + // assert(*ppRTV); + //} +} + +OceanSimulator::OceanSimulator(OceanParameter& params) +{ + // Height map H(0) + int height_map_size = (params.dmap_dim + 4) * (params.dmap_dim + 1); + XMFLOAT2* h0_data = new XMFLOAT2[height_map_size * sizeof(XMFLOAT2)]; + float* omega_data = new float[height_map_size * sizeof(float)]; + initHeightMap(params, h0_data, omega_data); + + m_param = params; + int hmap_dim = params.dmap_dim; + int input_full_size = (hmap_dim + 4) * (hmap_dim + 1); + // This value should be (hmap_dim / 2 + 1) * hmap_dim, but we use full sized buffer here for simplicity. + int input_half_size = hmap_dim * hmap_dim; + int output_size = hmap_dim * hmap_dim; + + // For filling the buffer with zeroes. + char* zero_data = new char[3 * output_size * sizeof(float) * 2]; + memset(zero_data, 0, 3 * output_size * sizeof(float) * 2); + + // RW buffer allocations + // H0 + UINT float2_stride = 2 * sizeof(float); + createBufferAndUAV(h0_data, input_full_size * float2_stride, float2_stride, &m_pBuffer_Float2_H0); + + // Notice: The following 3 buffers should be half sized buffer because of conjugate symmetric input. But + // we use full sized buffers due to the CS4.0 restriction. + + // Put H(t), Dx(t) and Dy(t) into one buffer because CS4.0 allows only 1 UAV at a time + createBufferAndUAV(zero_data, 3 * input_half_size * float2_stride, float2_stride, &m_pBuffer_Float2_Ht); + + // omega + createBufferAndUAV(omega_data, input_full_size * sizeof(float), sizeof(float), &m_pBuffer_Float_Omega); + + // Notice: The following 3 should be real number data. But here we use the complex numbers and C2C FFT + // due to the CS4.0 restriction. + // Put Dz, Dx and Dy into one buffer because CS4.0 allows only 1 UAV at a time + createBufferAndUAV(zero_data, 3 * output_size * float2_stride, float2_stride, &m_pBuffer_Float_Dxyz); + + SAFE_DELETE_ARRAY(zero_data); + SAFE_DELETE_ARRAY(h0_data); + SAFE_DELETE_ARRAY(omega_data); + + // D3D11 Textures + createTextureAndViews(hmap_dim, hmap_dim, FORMAT_R32G32B32A32_FLOAT, &m_pDisplacementMap); + createTextureAndViews(hmap_dim, hmap_dim, FORMAT_R16G16B16A16_FLOAT, &m_pGradientMap); + + // Samplers + SamplerDesc sam_desc; + sam_desc.Filter = FILTER_MIN_MAG_LINEAR_MIP_POINT; + sam_desc.AddressU = TEXTURE_ADDRESS_WRAP; + sam_desc.AddressV = TEXTURE_ADDRESS_WRAP; + sam_desc.AddressW = TEXTURE_ADDRESS_WRAP; + sam_desc.MipLODBias = 0; + sam_desc.MaxAnisotropy = 1; + sam_desc.ComparisonFunc = COMPARISON_NEVER; + sam_desc.BorderColor[0] = 1.0f; + sam_desc.BorderColor[1] = 1.0f; + sam_desc.BorderColor[2] = 1.0f; + sam_desc.BorderColor[3] = 1.0f; + sam_desc.MinLOD = -FLT_MAX; + sam_desc.MaxLOD = FLT_MAX; + wiRenderer::GetDevice()->CreateSamplerState(&sam_desc, &m_pPointSamplerState); + + + m_pUpdateSpectrumCS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSimulatorCS.cso", wiResourceManager::COMPUTESHADER)); + + { + VertexLayoutDesc layout[] = + { + { "POSITION", 0, FORMAT_R32G32B32A32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, + }; + UINT numElements = ARRAYSIZE(layout); + VertexShaderInfo* vsinfo = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanQuadVS.cso", wiResourceManager::VERTEXSHADER, layout, numElements)); + if (vsinfo != nullptr) { + m_pQuadVS = vsinfo->vertexShader; + m_pQuadLayout = vsinfo->vertexLayout; + } + } + + m_pUpdateDisplacementPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanUpdateDisplacementPS.cso", wiResourceManager::PIXELSHADER)); + m_pGenGradientFoldingPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanGradientFoldingPS.cso", wiResourceManager::PIXELSHADER)); + + + + //// Compute shaders + //ID3DBlob* pBlobUpdateSpectrumCS = NULL; + + //CompileShaderFromFile(L"ocean_simulator_cs.hlsl", "UpdateSpectrumCS", "cs_4_0", &pBlobUpdateSpectrumCS); + //assert(pBlobUpdateSpectrumCS); + + //m_pd3dDevice->CreateComputeShader(pBlobUpdateSpectrumCS->GetBufferPointer(), pBlobUpdateSpectrumCS->GetBufferSize(), NULL, &m_pUpdateSpectrumCS); + //assert(m_pUpdateSpectrumCS); + + //SAFE_RELEASE(pBlobUpdateSpectrumCS); + + //// Vertex & pixel shaders + //ID3DBlob* pBlobQuadVS = NULL; + //ID3DBlob* pBlobUpdateDisplacementPS = NULL; + //ID3DBlob* pBlobGenGradientFoldingPS = NULL; + + //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "QuadVS", "vs_4_0", &pBlobQuadVS); + //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "UpdateDisplacementPS", "ps_4_0", &pBlobUpdateDisplacementPS); + //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "GenGradientFoldingPS", "ps_4_0", &pBlobGenGradientFoldingPS); + //assert(pBlobQuadVS); + //assert(pBlobUpdateDisplacementPS); + //assert(pBlobGenGradientFoldingPS); + + //m_pd3dDevice->CreateVertexShader(pBlobQuadVS->GetBufferPointer(), pBlobQuadVS->GetBufferSize(), NULL, &m_pQuadVS); + //m_pd3dDevice->CreatePixelShader(pBlobUpdateDisplacementPS->GetBufferPointer(), pBlobUpdateDisplacementPS->GetBufferSize(), NULL, &m_pUpdateDisplacementPS); + //m_pd3dDevice->CreatePixelShader(pBlobGenGradientFoldingPS->GetBufferPointer(), pBlobGenGradientFoldingPS->GetBufferSize(), NULL, &m_pGenGradientFoldingPS); + //assert(m_pQuadVS); + //assert(m_pUpdateDisplacementPS); + //assert(m_pGenGradientFoldingPS); + //SAFE_RELEASE(pBlobUpdateDisplacementPS); + //SAFE_RELEASE(pBlobGenGradientFoldingPS); + + //// Input layout + //D3D11_INPUT_ELEMENT_DESC quad_layout_desc[] = + //{ + // { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + //}; + //m_pd3dDevice->CreateInputLayout(quad_layout_desc, 1, pBlobQuadVS->GetBufferPointer(), pBlobQuadVS->GetBufferSize(), &m_pQuadLayout); + //assert(m_pQuadLayout); + + //SAFE_RELEASE(pBlobQuadVS); + + // Quad vertex buffer + GPUBufferDesc vb_desc; + vb_desc.ByteWidth = 4 * sizeof(XMFLOAT4); + vb_desc.Usage = USAGE_IMMUTABLE; + vb_desc.BindFlags = BIND_VERTEX_BUFFER; + vb_desc.CPUAccessFlags = 0; + vb_desc.MiscFlags = 0; + + float quad_verts[] = + { + -1, -1, 0, 1, + -1, 1, 0, 1, + 1, -1, 0, 1, + 1, 1, 0, 1, + }; + SubresourceData init_data; + init_data.pSysMem = &quad_verts[0]; + init_data.SysMemPitch = 0; + init_data.SysMemSlicePitch = 0; + + m_pQuadVB = new GPUBuffer; + wiRenderer::GetDevice()->CreateBuffer(&vb_desc, &init_data, m_pQuadVB); + + // Constant buffers + UINT actual_dim = m_param.dmap_dim; + UINT input_width = actual_dim + 4; + // We use full sized data here. The value "output_width" should be actual_dim/2+1 though. + UINT output_width = actual_dim; + UINT output_height = actual_dim; + UINT dtx_offset = actual_dim * actual_dim; + UINT dty_offset = actual_dim * actual_dim * 2; + UINT immutable_consts[] = { actual_dim, input_width, output_width, output_height, dtx_offset, dty_offset }; + SubresourceData init_cb0; + init_cb0.pSysMem = &immutable_consts[0]; + + GPUBufferDesc cb_desc; + cb_desc.Usage = USAGE_IMMUTABLE; + cb_desc.BindFlags = BIND_CONSTANT_BUFFER; + cb_desc.CPUAccessFlags = 0; + cb_desc.MiscFlags = 0; + cb_desc.ByteWidth = PAD16(sizeof(immutable_consts)); + m_pImmutableCB = new GPUBuffer; + wiRenderer::GetDevice()->CreateBuffer(&cb_desc, &init_cb0, m_pImmutableCB); + + cb_desc.Usage = USAGE_DYNAMIC; + cb_desc.BindFlags = BIND_CONSTANT_BUFFER; + cb_desc.CPUAccessFlags = CPU_ACCESS_WRITE; + cb_desc.MiscFlags = 0; + cb_desc.ByteWidth = PAD16(sizeof(float) * 3); + m_pPerFrameCB = new GPUBuffer; + wiRenderer::GetDevice()->CreateBuffer(&cb_desc, NULL, m_pPerFrameCB); + + // FFT + fft512x512_create_plan(&m_fft_plan, 3); + +#ifdef CS_DEBUG_BUFFER + GPUBufferDesc buf_desc; + buf_desc.ByteWidth = 3 * input_half_size * float2_stride; + buf_desc.Usage = USAGE_STAGING; + buf_desc.BindFlags = 0; + buf_desc.CPUAccessFlags = CPU_ACCESS_READ; + buf_desc.MiscFlags = RESOURCE_MISC_BUFFER_STRUCTURED; + buf_desc.StructureByteStride = float2_stride; + + m_pDebugBuffer = new GPUBuffer; + wiRenderer::GetDevice()->CreateBuffer(&buf_desc, NULL, m_pDebugBuffer); +#endif +} + +OceanSimulator::~OceanSimulator() +{ + fft512x512_destroy_plan(&m_fft_plan); + + SAFE_DELETE(m_pBuffer_Float2_H0); + SAFE_DELETE(m_pBuffer_Float_Omega); + SAFE_DELETE(m_pBuffer_Float2_Ht); + SAFE_DELETE(m_pBuffer_Float_Dxyz); + + SAFE_DELETE(m_pQuadVB); + + SAFE_DELETE(m_pDisplacementMap); + SAFE_DELETE(m_pGradientMap); + + SAFE_DELETE(m_pUpdateSpectrumCS); + SAFE_DELETE(m_pQuadVS); + SAFE_DELETE(m_pUpdateDisplacementPS); + SAFE_DELETE(m_pGenGradientFoldingPS); + + SAFE_DELETE(m_pQuadLayout); + + SAFE_DELETE(m_pImmutableCB); + SAFE_DELETE(m_pPerFrameCB); + +#ifdef CS_DEBUG_BUFFER + SAFE_DELETE(m_pDebugBuffer); +#endif +} + + +// Initialize the vector field. +// wlen_x: width of wave tile, in meters +// wlen_y: length of wave tile, in meters +void OceanSimulator::initHeightMap(OceanParameter& params, XMFLOAT2* out_h0, float* out_omega) +{ + int i, j; + XMFLOAT2 K, Kn; + + XMFLOAT2 wind_dir; + XMStoreFloat2(&wind_dir, XMVector2Normalize(XMLoadFloat2(¶ms.wind_dir))); + float a = params.wave_amplitude * 1e-7f; // It is too small. We must scale it for editing. + float v = params.wind_speed; + float dir_depend = params.wind_dependency; + + int height_map_dim = params.dmap_dim; + float patch_length = params.patch_length; + + // initialize random generator. + srand(0); + + for (i = 0; i <= height_map_dim; i++) + { + // K is wave-vector, range [-|DX/W, |DX/W], [-|DY/H, |DY/H] + K.y = (-height_map_dim / 2.0f + i) * (2 * XM_PI / patch_length); + + for (j = 0; j <= height_map_dim; j++) + { + K.x = (-height_map_dim / 2.0f + j) * (2 * XM_PI / patch_length); + + float phil = (K.x == 0 && K.y == 0) ? 0 : sqrtf(Phillips(K, wind_dir, v, a, dir_depend)); + + out_h0[i * (height_map_dim + 4) + j].x = float(phil * Gauss() * HALF_SQRT_2); + out_h0[i * (height_map_dim + 4) + j].y = float(phil * Gauss() * HALF_SQRT_2); + + // The angular frequency is following the dispersion relation: + // out_omega^2 = g*k + // The equation of Gerstner wave: + // x = x0 - K/k * A * sin(dot(K, x0) - sqrt(g * k) * t), x is a 2D vector. + // z = A * cos(dot(K, x0) - sqrt(g * k) * t) + // Gerstner wave shows that a point on a simple sinusoid wave is doing a uniform circular + // motion with the center (x0, y0, z0), radius A, and the circular plane is parallel to + // vector K. + out_omega[i * (height_map_dim + 4) + j] = sqrtf(GRAV_ACCEL * sqrtf(K.x * K.x + K.y * K.y)); + } + } +} + +void OceanSimulator::updateDisplacementMap(float time) +{ + GraphicsDevice* device = wiRenderer::GetDevice(); + + device->EventBegin("OceanSimulator", threadID); + + device->BindConstantBufferCS(m_pImmutableCB, 0, threadID); + device->BindConstantBufferCS(m_pImmutableCB, 0, threadID); + + // ---------------------------- H(0) -> H(t), D(x, t), D(y, t) -------------------------------- + // Compute shader + device->BindCS(m_pUpdateSpectrumCS, threadID); + + // Buffers + GPUResource* cs0_srvs[2] = { + m_pBuffer_Float2_H0, + m_pBuffer_Float_Omega + }; + device->BindResourcesCS(cs0_srvs, 0, 2, threadID); + + GPUUnorderedResource* cs0_uavs[1] = { m_pBuffer_Float2_Ht }; + device->BindUnorderedAccessResourcesCS(cs0_uavs, 0, 1, threadID); + + // Consts + //D3D11_MAPPED_SUBRESOURCE mapped_res; + //m_pd3dImmediateContext->Map(m_pPerFrameCB, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_res); + //assert(mapped_res.pData); + //float per_frame_data = (float*)mapped_res.pData; + //// g_Time + //per_frame_data[0] = time * m_param.time_scale; + //// g_ChoppyScale + //per_frame_data[1] = m_param.choppy_scale; + //// g_GridLen + //per_frame_data[2] = m_param.dmap_dim / m_param.patch_length; + //m_pd3dImmediateContext->Unmap(m_pPerFrameCB, 0); + + float per_frame_data[3] = { + time * m_param.time_scale, // g_Time + m_param.choppy_scale, // g_ChoppyScale + m_param.dmap_dim / m_param.patch_length, // g_GridLen + }; + device->UpdateBuffer(m_pPerFrameCB, per_frame_data, threadID, sizeof(per_frame_data)); + + //ID3D11Buffer* cs_cbs[2] = { m_pImmutableCB, m_pPerFrameCB }; + //m_pd3dImmediateContext->CSSetConstantBuffers(0, 2, cs_cbs); + device->BindConstantBufferCS(m_pImmutableCB, 0, threadID); + device->BindConstantBufferCS(m_pPerFrameCB, 0, threadID); + + // Run the CS + UINT group_count_x = (m_param.dmap_dim + BLOCK_SIZE_X - 1) / BLOCK_SIZE_X; + UINT group_count_y = (m_param.dmap_dim + BLOCK_SIZE_Y - 1) / BLOCK_SIZE_Y; + device->Dispatch(group_count_x, group_count_y, 1, threadID); + + //// Unbind resources for CS + //cs0_uavs[0] = NULL; + //m_pd3dImmediateContext->CSSetUnorderedAccessViews(0, 1, cs0_uavs, (UINT*)(&cs0_uavs[0])); + //cs0_srvs[0] = NULL; + //cs0_srvs[1] = NULL; + //m_pd3dImmediateContext->CSSetShaderResources(0, 2, cs0_srvs); + + device->UnBindUnorderedAccessResources(0, 1, threadID); + device->UnBindResources(0, 2, threadID); + + + // ------------------------------------ Perform FFT ------------------------------------------- + fft_512x512_c2c(&m_fft_plan, m_pBuffer_Float_Dxyz, m_pBuffer_Float_Dxyz, m_pBuffer_Float2_Ht); + + // --------------------------------- Wrap Dx, Dy and Dz --------------------------------------- + // Push RT + //ID3D11RenderTargetView* old_target; + //ID3D11DepthStencilView* old_depth; + //m_pd3dImmediateContext->OMGetRenderTargets(1, &old_target, &old_depth); + //D3D11_VIEWPORT old_viewport; + //UINT num_viewport = 1; + //m_pd3dImmediateContext->RSGetViewports(&num_viewport, &old_viewport); + + ViewPort new_vp; + new_vp.TopLeftX = 0; + new_vp.TopLeftX = 0; + new_vp.Width = (float)m_param.dmap_dim; + new_vp.Height = (float)m_param.dmap_dim; + new_vp.MinDepth = 0.0f; + new_vp.MaxDepth = 1.0f; + device->BindViewports(1, &new_vp, threadID); + + // Set RT + device->BindRenderTargets(1, (Texture**)&m_pDisplacementMap, nullptr, threadID); + + // VS & PS + device->BindVS(m_pQuadVS, threadID); + device->BindPS(m_pUpdateDisplacementPS, threadID); + + // Constants + //ID3D11Buffer* ps_cbs[2] = { m_pImmutableCB, m_pPerFrameCB }; + //m_pd3dImmediateContext->PSSetConstantBuffers(0, 2, ps_cbs); + device->BindConstantBufferPS(m_pImmutableCB, 0, threadID); + device->BindConstantBufferPS(m_pPerFrameCB, 0, threadID); + + // Buffer resources + GPUResource* ps_srvs[1] = { m_pBuffer_Float_Dxyz }; + device->BindResourcesPS(ps_srvs, 0, 1, threadID); + + // IA setup + GPUBuffer* vbs[1] = { m_pQuadVB }; + UINT strides[1] = { sizeof(XMFLOAT4) }; + UINT offsets[1] = { 0 }; + device->BindVertexBuffers(&vbs[0], 0, 1, &strides[0], &offsets[0], threadID); + + device->BindVertexLayout(m_pQuadLayout, threadID); + device->BindPrimitiveTopology(TRIANGLESTRIP, threadID); + + // Perform draw call + device->Draw(4, 0, threadID); + + // Unbind + device->UnBindResources(0, 1, threadID); + + + // ----------------------------------- Generate Normal ---------------------------------------- + // Set RT + device->BindRenderTargets(1, (Texture**)&m_pGradientMap, nullptr, threadID); + + // VS & PS + device->BindVS(m_pQuadVS, threadID); + device->BindPS(m_pGenGradientFoldingPS, threadID); + + // Texture resource and sampler + ps_srvs[0] = m_pDisplacementMap; + device->BindResourcesPS(ps_srvs, 0, 1, threadID); + + device->BindSamplerPS(&m_pPointSamplerState, 0, threadID); + + // Perform draw call + device->Draw(4, 0, threadID); + + // Unbind + device->UnBindResources(0, 1, threadID); + + //// Pop RT + //m_pd3dImmediateContext->RSSetViewports(1, &old_viewport); + //m_pd3dImmediateContext->OMSetRenderTargets(1, &old_target, old_depth); + //SAFE_RELEASE(old_target); + //SAFE_RELEASE(old_depth); + + device->GenerateMips(m_pGradientMap, threadID); + + // Define CS_DEBUG_BUFFER to enable writing a buffer into a file. +#ifdef CS_DEBUG_BUFFER + { + m_pd3dImmediateContext->CopyResource(m_pDebugBuffer, m_pBuffer_Float_Dxyz); + D3D11_MAPPED_SUBRESOURCE mapped_res; + m_pd3dImmediateContext->Map(m_pDebugBuffer, 0, D3D11_MAP_READ, 0, &mapped_res); + + // set a break point below, and drag MappedResource.pData into in your Watch window + // and cast it as (float*) + + // Write to disk + XMFLOAT2* v = (XMFLOAT2*)mapped_res.pData; + + FILE* fp = fopen(".\\tmp\\Ht_raw.dat", "wb"); + fwrite(v, 512 * 512 * sizeof(float) * 2 * 3, 1, fp); + fclose(fp); + + m_pd3dImmediateContext->Unmap(m_pDebugBuffer, 0); + } +#endif + + device->EventEnd(threadID); +} + +Texture2D* OceanSimulator::getD3D11DisplacementMap() +{ + return m_pDisplacementMap; +} + +Texture2D* OceanSimulator::getD3D11GradientMap() +{ + return m_pGradientMap; +} + + +const OceanParameter& OceanSimulator::getParameters() +{ + return m_param; +} diff --git a/WickedEngine/wiOceanSimulator.h b/WickedEngine/wiOceanSimulator.h new file mode 100644 index 000000000..a94c2a60c --- /dev/null +++ b/WickedEngine/wiOceanSimulator.h @@ -0,0 +1,119 @@ +// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. +// +// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED +// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS +// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA +// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR +// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS +// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY +// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, +// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// Please direct any bugs or questions to SDKFeedback@nvidia.com + +#ifndef _OCEAN_SIMULATOR_H +#define _OCEAN_SIMULATOR_H + +#include "CommonInclude.h" +#include "wiGraphicsAPI.h" +#include "wiFFTGenerator.h" + +//#define CS_DEBUG_BUFFER +#define PAD16(n) (((n)+15)/16*16) + +struct OceanParameter +{ + // Must be power of 2. + int dmap_dim; + // Typical value is 1000 ~ 2000 + float patch_length; + + // Adjust the time interval for simulation. + float time_scale; + // Amplitude for transverse wave. Around 1.0 + float wave_amplitude; + // Wind direction. Normalization not required. + XMFLOAT2 wind_dir; + // Around 100 ~ 1000 + float wind_speed; + // This value damps out the waves against the wind direction. + // Smaller value means higher wind dependency. + float wind_dependency; + // The amplitude for longitudinal wave. Must be positive. + float choppy_scale; +}; + + +class OceanSimulator +{ +public: + OceanSimulator(OceanParameter& params); + ~OceanSimulator(); + + // -------------------------- Initialization & simulation routines ------------------------ + + // Update ocean wave when tick arrives. + void updateDisplacementMap(float time); + + // Texture access + wiGraphicsTypes::Texture2D* getD3D11DisplacementMap(); + wiGraphicsTypes::Texture2D* getD3D11GradientMap(); + + const OceanParameter& getParameters(); + + +protected: + OceanParameter m_param; + + // ---------------------------------- GPU shading asset ----------------------------------- + + // Displacement map + wiGraphicsTypes::Texture2D* m_pDisplacementMap; // (RGBA32F) + + // Gradient field + wiGraphicsTypes::Texture2D* m_pGradientMap; // (RGBA16F) + + // Initialize the vector field. + void initHeightMap(OceanParameter& params, XMFLOAT2* out_h0, float* out_omega); + + + // ----------------------------------- CS simulation data --------------------------------- + + // Initial height field H(0) generated by Phillips spectrum & Gauss distribution. + wiGraphicsTypes::GPUBuffer* m_pBuffer_Float2_H0; + + // Angular frequency + wiGraphicsTypes::GPUBuffer* m_pBuffer_Float_Omega; + + // Height field H(t), choppy field Dx(t) and Dy(t) in frequency domain, updated each frame. + wiGraphicsTypes::GPUBuffer* m_pBuffer_Float2_Ht; + + // Height & choppy buffer in the space domain, corresponding to H(t), Dx(t) and Dy(t) + wiGraphicsTypes::GPUBuffer* m_pBuffer_Float_Dxyz; + + wiGraphicsTypes::GPUBuffer* m_pQuadVB; + + // Shaders, layouts and constants + wiGraphicsTypes::ComputeShader* m_pUpdateSpectrumCS; + + wiGraphicsTypes::VertexShader* m_pQuadVS; + wiGraphicsTypes::PixelShader* m_pUpdateDisplacementPS; + wiGraphicsTypes::PixelShader* m_pGenGradientFoldingPS; + + wiGraphicsTypes::VertexLayout* m_pQuadLayout; + + wiGraphicsTypes::GPUBuffer* m_pImmutableCB; + wiGraphicsTypes::GPUBuffer* m_pPerFrameCB; + + wiGraphicsTypes::Sampler m_pPointSamplerState; + + // FFT wrap-up + CSFFT512x512_Plan m_fft_plan; + +#ifdef CS_DEBUG_BUFFER + wiGraphicsTypes::GPUBuffer* m_pDebugBuffer; +#endif +}; + +#endif // _OCEAN_SIMULATOR_H diff --git a/models/Sample/textures/water_bump.dds b/models/Sample/textures/water_bump.dds new file mode 100644 index 0000000000000000000000000000000000000000..ec70d109f4b5c17d6ee791993206300579da2798 GIT binary patch literal 349652 zcma&Oc~q16wk^8f7;lXC-o5ASU9C;oVFLmpMGS;Cq)0^&3`TmtkHZ&mwcr(^MXT1C0+hhMK2#Dm%H`khL z&bdOPc^Uosx4-@E|N82G{Oxc52R<;Izx|(v|NQO$`SRcY|Nk+&W8*&B~Pi>!2hJWG9C zxaATr+-fNr5@HL#Jv?%|%`|Jt6CTTlD;Dg^BDD!%XHrAt8*p^pPcf}Z;hMao$dsn6)T zD;Xa1`N!O6FEZ(qXK2i!-%0Gz%RdE<+;iuQSUD*26c3+F^iBr_vx$A-X zlTT9f`{g(DyIWFnKfXJd)mE@0w?1QiLC0^#(giMCHqBAn%gYqt(ri&eR_W_`Z&$=^>`mj6ehtl~e<$fRGTN}qgvMOyjw<+3M# z7ZyGL`q$zY-^Lfo*F+V)`tEGe({I9yAFriLUambX`>^$_tjFP@bnvGKl3|DJ;we{N z(ZY!U$uciiq6SXWhR8RH28l7n%5A?D*Q~iv{A$&uGCUSFZAdO|SSKoI*bq@r zZ^9_5-{K>A^?j_gVii+bYD$ooI2kF57~iT!{C-|}`Nycb{Qju2tZ+>JtZ=BP=}LE@ z@{}@vIQ(_?+~qszy^4RO*M7Qmv1%kYwQ@Y`oMhr^QmIOMy12O|zOvzcX!Y9$-x^sn zv$>*?K76x^Hh2C$bxDxvyev+p%$>VWR{#FOZsP1S;;g9HYF>2TVqSdTZ2nxKi8eid z>%#f`&GYB)Y@AOkT&GXTS+kUKVfAuy+V{)J$=|PtlE1_K{nrtSzFiR{f3wU_`({O$ z{Pl`ZfW!ZKMSw>E9{IelmJ{N>S&obQb~%>&-EtiF`(3Qx3lIZm*4DJpL5 zVsPv_!`Bx>Bi7?S-?$W>V5EbU_@UyosMW>kP zqOO?eVy|so;U#Zg5hfVv!~t98lf6u4&mSS#L_H8&Ek(y!;W`tR4_g~5o25Jg!1b(_L^X<>6V-8(;rD&teB8%tGvnEkQN+gI`4GqA03UufSeW|a0;Y&7n%D7_#zY=tKb z!|M_@bBq}^&x(&hEsI)?=0$2U_eC~e-5USm+_$G+3%*Yu6*%P1-3iN`cy==PV{>k9 zYjaW7&|6`8uh=51Heh}J+rSOQO`Oe=Pi(SmbT3QR5nv&g#edUMDg3*t^H&qiB&$&S z+@o*&q5H5>O6#fVa;}tV=;^X4Z@v*Gc_=>b?Q-)M&mLnZ?N-3M%6Dpb7 zb5c6u{y;kIeOog0GfUEE6Da*?c3RqHo-Xa)m0#Ar?e}8EreDkI*TqQ`#{QD#UAv{V zX1istx6|bEoz99EjE%~&$p26;g^O0o1v9G3#7U(jYFhq^(JN{Fv9zdmTWP_ot&j3w z?~vto?SGp-EUZl(zAHTod?)qZlk=PIw$TnHSu58s}HpjfrGL&AD<*ZCZhaR$OQ{ccy5YHo0KSLR$W&g>$zyEQoLa zuq4c0z03#xc`4rmE8i~*QvM77z`pQXVDY=Ln2)n7H>zwn=c+7SKCvLN=m z6(JV~xEJyaXVr>W2;7VQ!o3iL&Shd1MUEuP7|G3rq*w8hLvBAK7-1_A>(fVa> z8t|V2>(XPOQhhzCj-vy8+*~37FluEQ&SK3qv+9@j|yQ#YS&iijFox zZ{5Ben_ze}#eHqM7@NCY7niqHAD*&hDV(=?@kGd$1tHs5d(L~i=Hfnc?fJu2=udaDRb_=L77=S))#nPE)e8csZE_UIwIc`nOw=A0*G?DEgF;hR6Xj}&_{rb>dDngU<1 z=>l)o#4QGUF6&SrYGt4?<)OKK zo2I$Vrm1i5@ff@9;W=L7!&FyAG5hK`EM*gm{r>$P-{!Ye&ZkO76zWGL_)geTRB{;V zTiEQygHemw(J{+uDKWUWV&-{Xk*&XOj(M5--D!Ej#`KOx@9dtI`0SCF;aUC9%Cq}R zt5QGT-JSX(`n&8(?^OkITVmO38=~~JnTxz`V{l!Em3RHS;0-O$1YZqxoHfzlKGL?( z8?|NRo}n@urQ)?sz5EmFxojyVTDF)QCto<9CtC~_%f?+|fYsk+%54H^vr)9HW*tZN zd{uz_*;fhj7psocH*Snn^zKY;>D^t~(&biNqjpY_50QSCcUj$hsWi=$_F2YDC#XW% zq+hWVyiPLiOP35C*!^;h5-000PLefl%8}Kq{!LoHE=Kl#qe%9_I9vwozkF(DSNCFz zo3h4;rL1w;(fu^&yYZRGhb!egjk+p+Oj#K_S>MF`Ani2ImcH9~UGjR{BT1Fb`+}El zUAYR{yVOCZ>}+>XV^Wv+!|8WfgYmDgt3q3Dw)^&6YhZo&v)!{Nr-?S7f7yBI0@r0( zl;Wc2?Z`zh#LN*omxSmxmH+cihI-qJ!$g-@Af7gFMqu*A@2u$LiQTq zZuN3P@+x3&)iN*bJG@Tct{8g1i1(!iEb~*oT}}W8;EB-pg)+@cnW^tfA+xUXB&4UW(?f2DaDeLPOT#tXm4_ zZovJrK_77eSQo8Z3=*tc@QYuk^$Xju;1|0QA8*nH#2M)V1V*TxM&KHoba6qOQ9HIR zMueIGKfu1o6g9>aJirVb!VJBCJC5;ExOl5RG{JZ=Bx;*3BE$q-Zu=bXzz)=coreCp ze1T`B%MKwd=A0ldeuqi0xv&s*q21UqyA!X1l2JUnuD zECigeSO|!)#C1SlPuR7>6N8HY|8XL##n1~@`e6Rfe}9dz!UZpgOR&OoB`gF)*~}g| zZmppoCr#7CZD$T1bzER`9kt$K$GHPM2hIMYaLv#0^S34`C{LNt6_n37)bj4D=tR=wv) z=1bRwtgF<8ERM@$j=SsFbvKXsI}wcO$3oAcniGt^x4w)I?>t!VU)lS%JRx#4d928l z1b!51V$_OA9Hza>37aiG897s$7^%;Ui=2w|h*E}|o@%+Y^>ouKJ8@H|@43zvPAX=D z^r`%!?8#KAxR+~@`Y7t}*=6jnC9fQJ%3j&*lFO}0Ev+U;l>L^1&MqH9i!AXQRZpsg zMwcC`(FLSv-~9YRQ$p!iNhw{5M#cxZmR~3Z{$;o~WuIJFvbG)G^5#wF>RLCSme(2` zs(Z3-Z+-dKzRIWnNbYQ0FIJ+*cg`QVuJ{yLqZkQ)Dc5=n<>No`<)|6*hV{S8TeoG& zd!0&TBZt8+*f*uq;dJRxxE*E^Z+Sn-zovEbuk}sqf0MUtPO9s%%#`<9h00%96YDCh z&6KkB2UIm{qE+v9(A16I+cavy%aupJPY*p$9q*L=HdgoPm$%Yc25JiTi@w7DW79%(}pt}hgRx5DFphn|lU2mD8)zQnCTe_o^GM6JQx`@`I!u=VqYBYig%FMj>W9fv6xVz? zE(QFWza4$r48N~Ao|maUHXD3ExDEfNG3Er5g)ok(E`npGI}v1|7lc@@h%s-cB<$2D zhghL*5th@y1u_zd=vTzWoG2T8W(sLJC7HM!7i+DLind-j;YXT3;p3<|$|TPp^P_^3 zxGZrmP#0qJofqP7k>}$J9knNJ+0Vq3TF;*-v|3EKW{KL0{vT(t7@T0az~A*8KT^HInz7*>|adK6Um94fh2V^_!{{ zvuMy5+~*79JvHU$Jm!kH?&A*+x(}Cm?C*N^6SKZ@r+>#Y=dg)KoXDk2ekAUx(3J#X z73Jy?k43(fJ^s)RYLbi>w}%|nA=qSVY@VQM}0JNK1Ve@d#hKZ z%pvt-*M8-5N=JRO(`(rf>6EnF_JHi|j}&>+cBZ0j+eJm^_EUB5wtKX^vb67hZR|F# zFutK~{js>WXXnMvjvwzR8?B@*Z9DJP8}R&oQ*`~iA5JLVt`R6|Ha}3jqc+RC!dhhD z&(hgchIEK;BO5*CB-h06ty>I@Qh+PAV4hMe#a*cHbEVZ)?%t#*Cm9dDSkKfnt`}%N zm<4JyNB3*nl0NFH&W#Pd{HI#^GHgWtIpn2GA1spT1o^Vo-#<$0lT}4cNdpBR{?(P+ zc(E_*?X`Y!LvAl$o-!O$FB;@@obF(MKJ|v#7u7`X4p-2$$MPu9gDAMxRLuI6DbW*0 zjp&grcmNTvpVf4#WCvydQ~cg0y5zjA3!>Xb__sEoKdoEh1ONP#HD8VZ_!mG2;9vMR z2M~Po-yDGV?-_t6{%VCU!r^_jA^`pcJj?}L!@(>Nhk9X{0|claz<$CPJpj1pu3Acn zS`DpgjXolJEwH-|J!&Irz$Wy;&A|U=ElaRvX0K@Tv>R{p1ofBA<3C4j9sfDT7=JcK z4KSHIkhopT5^o33HNp29?|-Ao^X8o=eF4hX@s(@gbI7q%}3 z@lDWgx9fu947I|{z_~HASS+Q*S}v#XEul?WE+zz7p*HQp-z9vR!BGeFIdR0LOaT$~ zjELERw7?0oo#zDF&mKGEFdgmT3@$+fE`hNNw1pG-F2FwpJ;woc)^0Ao$aXIBE&KF3Sn84hMJQM9s=x(qpeUUcdaCdh${(V0 zWVOC>4(yd5>?kez(=WutMSjrf<&;=H^nlZS zmk3GSr_EDGnC|I4mf=}VyQtYRzcu-%GsvPE$(%Ay8I*9p72A z`k&qJH=gTl*!j5o8B?Wr8LyU2=9Nl@Za7Q(&RNRTzdP4pHmVzX%d2m1&#doh`At4u zz^;4iZB$phXLav0r|p_gj{YmNo`My9us}O;wS1+vv{Uo;-bYnEPu2VO=sw zN}dsB+YL*CY~Vk0Eik`kS%?`x_;36R3^PEQf%|`B!1W4G@GUs?x42)wffn^O`0Urq zas00^$A4`&!1p&e&Us|*KP^=k0-wdld?<~e>Fv>c8R=IG7P zowlMEZk;`F0$Ar8Pf&R#YD%=pXL6|Nh{GwfQ769H7=>pxLFJoGd!(9a_NAN7GI-l3 z_k^2HxJH;ydqi8zc=9Y~JX0-an3ut+&zqrlq89MYz!S_g?p)JJ+Alk1JVoY`U%5#~Gd_cim=ZJaQZvNOU((JK&*7JT>2n%eX75XzU&nGN} z@(KD-E>Rzj+7d3b)^oFoOT0|0C2@v}*<-^b8Dy^Ujhs)=L_SNSq0Dx+AF_wKSL z@=F<0Ic4T<9(NAA;T||M^>Q!@6I)KM@nV zRo2nnxK1kc+9A!oAXVRU57o@Qqw2*A*=l_-Pp$P9srxLG)s5?a9k2c_aa_KNr*7PM zN!?+3N!8}?TqQX?rYwn_c{y~pws_zgNz$FaONQ$vhj$|X&~;Yc`~DBvNXb4~XPl}0 zvG2EvBJZ!pm8>6Epr;#Zly>%B*2;&w&6?V~Jt{@9qPz8KhqB>nk3xB=T|Q9!T-N^d zwG?{8H_Op{ z)B|XMalrkT?=x_HgAc&_9`ylwAGCmkgfIPH9~ZJ(7aQ<{_PF1M`G~`tz+1QAUf!zp zJ8rBw7-X!b1q17+%t!2b=0kRoI|giyS`HEttokiaTlMYy)v9NQz^d=Z3swWfOBO>m zi5C6D5X(M71YyXAN0^|VA013p!yw=XKn2+9SpiHNa}lC(8=@ zf(2?Bba#OTem4vBMVu(~|6nW3PegDT>m^}^jV|RH0Um&rJ}rEgJ|%=Wf5neDok_Qy zJLly%&-Wrv{z7v>EpuCr7P&&(b6q%*N1Ho-pE?(Im#pPnah&JmJK$sJgc-zz!|_DS zFV=Gh&e&j1MIYx8QG;xNZyN*t^-&oF9p@7KKEY01Ebz~ZGW33;VdjUffqH~m5gls{ z&QF>TJYqk4FwAlG;4z0OufxumJzXY`c+w{Q7#@?J0iGlFe8wl+Y|jygv+k3wQSR^= z=?fuOJi$%e@miyA^Jx>$M6N@%zte_l<7h*#L)_HQ!rY%4pbx(eWvZWDW@_$T zqwCW0Jhg%XR|Ebr1JO{QXy9*dc)i_MxI#DRLvG+Au2a9b(!s0tFGhtjW)m6AsXz9x z#;&`vd+(3}J8oNsPM_No1OG8*e4kGuG>do*Dh_}_ z#w!x0drM+w_Y$DhZ^`Wi1*4$D)dGdF#uia=x8Q<_nI34N8aGVogs z`pe4n?LzH%l|bA7x=`Ct(V|h@Y#(a9-PYS$+N5ZB++1H*(JJexs*);;>&o6-94@R) z9m?&_YfP^#=t+H5*w61Qu08efL2XQP!H2Mp`wfArJ1=}R>1E8>lSRzY!;d}HzE9ky zqAO^q*Upm}PaL4%khJG-k)Zt(rbUk}p#hq~pMoEjw*`8%LI2wbe`+0G+jYx=3*i50 ze`x^F0hYKKt1#~Y$6{a_xc`EE0s1=Td9DyOK=2*`t zn2&|WhsQ5kUwG_l^vksi5drJ8#}9ANo;bW&8|Jqa9KZznmC5-2L*^s9eJn@Fk%Uh+ zzYzKeA%q@6kj+Q)Slf2vI8uvIqFwVwfnBrFMZ1=5SFAgXeO;iVb3VsNnthRW;3xL8zBfql+N{w>t(W7{Z1l02 z;2dJ2F6cZF{v82&A2@#;@Gm49ct7f+HR>ZtdpMSadEOS+%mL4djNVScHFLqCO?$Il zF;lujk77VeVNQGHF*VFw1~>-;If4CiM{dyP0&lp3kGR1XqRn5wLY*l-PnnjSqyYES z;n$Hg;NI=innaU;nowaeBs{r!IPtPi0k`3q@&1^OIO@zRCp!cV{06nFn-E@?Qk zPWtrsujCb%{-KoRd_UfG#X^fZu3e1F)lR#gTNx(twCEGMLAz_H1*f$0J`v#Cyp=I} z!AhHTh311vwtAR)TRjk6ubMjZq!+cdcbOO0jajC9>Itt~Ee-A-$)%`<)9lnEryMj3 z@qRjRLG3v2xwa>>NmEzQJydyhOey_!QvT7qPCDaxQmW^NNWqWsm?ND#bX?Nlx>Ned z)>!`VM>AywX}h}D&ssZFbz58e@xG>d__F4y`ifenDjcf*_^7w0=AE+s)!Vv;3WdD! zkwW_MiLC5niM+TWue-4J^5CD%H#*Ygc>`kLKe0_(6VvwcWmK!Ap3`6W8rh2~*6g2m zne$h#Gw072GNy%(JwN~QhBlG-j4~bn$U)01B%yX&cPMDy*T)O zTxfn=^nG63D$D?@!4X&grT25!z~^794FmQ$oDK8G4sU{Qz6EF7jIXchG>dIM%3xT2 zrtc$+xCGer5yEYiI|A%ljDyLoTTVE?-*kfdej}Ime&cVnhV?15x($C)nm466;k9-i zbxL%e_P9V%dzCm&`V^7i1rw*~nO0M-d@Dm%2L2df_Ruln0z27e#{V{PKJcp5!jUY@ z;v$R16P%skAqGDUJy`_p4OxI>(XM4NxPQV0=y1>mF@K})5%dY6ggMax;!HZjZaSak zs4k>aCW{W!w6~7CE?*G3EefyEP*YtNdDkfLU&yEfj=+r_?!PZR8#M?%VHz28n!PUM zk`4Y%Tht+2Xyn%51J=ksSnK@6M9hb%2Y+E7{oZ=+@DbAV{$RTaTD0Tzo_Ggf+G!yo z$ay}(hdLSHNmCy<;6Cnlgg$j3fIby)fPtFE(((&fdQmO|oQAO&l)#)~eh;zEd?^H_}3XSb=7#)fVuy znnygt$1A!kmsYd|@!Ij{Jne8TPctp~1312*op8(1wH#1rI*+vs&2z4IV>arYWr$QW zzBf?|E_BcE_Vy0`N>Gjb?w}sN;-r}_WNRlSIob~|Ud&d%ZdS{x+j`$T=uv$5txG=a zUnQH~mm}5r38d5eLZkzPc-gy6zS0uYUGlr;=1Pe@BIQZL5?wym=#QdR`~#eEz(+^-Wdbo9C_ha_NVxhVs_bS9b=I zWcLT-@%6BF`4diG$qV1Ht54YD8BbZ0w~81OrTO$J$whbg{~qeIFKL<>%>E%IPE)}p zcJQIB=fiFh=8oO5nhv;cp$WQYHhc80@%-^STeZh;86h9DL3iSsp$7bge-ShQ5oQ2U z+A7Sy;O&Nf54>{?I=-O>;0W;N|L@_$+v6LuKJfen$n_X}ei0YmKW2VW!fNCK)F+iI?5<^9(H|T|CyX)3(wYC zwd=Iakd43w`hg8}L_2(s{S>PJz4%Weav%g~QkKw-tQNV*0P(ms=x^Zun7Iuayhw;z z06m@uE%3`D&)~Ze@%!88czz`01ng(79dev1^mfL*>;eqYv{$d7H)d1y$vKoo(KRx- zmE%%un*Cx3vINlRkx_7hCIMgWs^hHhbvw)zD&D zPo91hPnmoYMS-UO#Ww`^@mLaNdMt9Ux$1?vG;LO%%glpJ=Sism-)G>X8;q=QGoQ@aN(9Sm=QGIns;s@Rwjvu^co7f#| z!Eg05J==BEB3;F`&K{3*$sH3?@~6+Z6)YuRD1m8V7#YH1(z9^8(Fqtw1yKnx_HB*Nhgp zYtZjk+Pqsd^}CygRMZEmc`qJx0NxNZ8x?qAH?EhV{||k7yjR`(&QIO0jMUWkUe!ER zR;w%9-*ne1suW!h-_}i^l*qxk<)fZOGR&5;PJ0j8yRBaG=NssCMHV}h<@Q_Em~U1T zjgK^s2Og`7`(AdJetM!P8@*p6nYb={H2p8>vkAUTK9nh|=`5AjH$9ao8Y+t0WwL^% z>YDu5&)c%=WR0nH&zjG^c-YT>UNR8#PO1p{_)6kGTy=x3mK695K77s`E|Yjp73I6n z-%obc=4H8QE|$<1gtuK#uPMN~pd~XwHgYWDD?kzR`1r}2QMW%S%20pkMeAs9{ z;to8(Yd?IM0mRS%#A(2=82wv>`CR}WZ{YAke2g4Vg7|;00bh84LF@ZB{(0#C0wH`q z)BxarSsb@&Nf^Ewy5CxTLdZHT*LwppoJQbPTaiOG!7)cp#RB)T74n}%c&YY-hse&Y zyGgW$T|ap?ZavIw+z`iV-guhXv@zDJaeV@-alMGuyuE|3&V%OmZ4`5jjn>Zj*7}+bwW%NSMztOK0rDb3@iH z8lE5YxXctHa<9k$AbXd|{dX=1_(wJnT%E8O8)6NIk-O<@3K zrZk>9Ta-#!xOL81my=D=r)4{#mN-BgaX^025gdpNPDF+m=L~&`GU0j4d771P2Tnp- zV2f<$ypY*BaMEtxi*GmUDYTzqoVTB4#M@!McZ4oNUJQsPw9f3CNf^3&XXr; zw85h!*IpkIL%q+7HM2L7y%?0t!fcC-ZxM4TDuoUmVLvi34EUK$WW1Q8iPa2sSe2{B z`zCGNr-X`(0$C@{b42FT9y+`|ym?1(VMlF#nA7xaZ)fy!^!ET)^nCZlj2t)AH5&9} z>fFVDⓈL^9 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ő(VTf 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 K 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�OjuPcz*mA5op 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Ν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

?=hk)YhiW|&^GdVp zBf6U>BjG!H{iRz&QxkJ-GYhRf>)rYNXOU^$*YW8Cm)OLiy^6SzsqwJ2mSLCEo-~`& z(MX${7MwK^i?DjM^t5>}&a}BPsW!VduG7Ey@l*ZDu~d=f87E8kLdlX`9c0PgHp)-2 zhU(-rJKf7Xb;HFR4*TOknZQRAM;OV?2XP&(gBr}brfsH-& zUyEC2<700E^PL=@EkQ~35hvz4NS#-yD%>}?sFSNgG$>`^nxra(CZQrl{iztP@|0(y z@tCBe{}`!jM$B=tCYK?tuYOtDZr6#rFH~`bP5xv{n(7fP9ctoenJj0+?>8%KZ`6sL zAEX99O}cKKwT9e}bs%nvw>+nDSDx*NRrrSYDR#(;f{rsGg|@Qa#8L z&_B!-wq7gd@tDs1fWAz!OuK9fPs4BF(uPm{lG~5ea~lt38@dj)M<(`t=X!T)C+jyR znwqxuL!^;jYgX?NT)4M2L zTcTqs;E<@cd@#r}IajVWI8(3Jc+^i-q?)4@DK0KbG#?K&dWf4kE!s}|5u>Azhcnr1 zhkgxyit<2Fupvl-k1={Sh$W#l^|!i~y0^=VIIG)vgR!$cpUIV-$c2g3*tPbv=%&h> z@Pd+iG_2q*=ttR&Z(R9VNp8t%e^>5VV|gMmFC^{=E0K1TqFi#_5?g-qv$+V=-O|QA zPY_e)J5K9r!v;5o8R`$g78+ME3Pz7vX{MJ|O?sCtWol2=3CdKz3^|%-#&@b`rX<4$ zF3!OCN<0->OW!xx$rG#Hm8b=96-o(KnVOGQV&FoR$Y~BL*H!wOmkq{7kChH46s*4m zF%E9NS1M_{Sjy?Q&>?H@ui@RoJc` z)m};3!Ayj&#;ke)o_}kS8P=TY?Qe0PRB3*eGOT-pZC9cuC(Au0R!EZ(B_i~QI6+2I zs31MnSBR47C`ym>kYI#`Nm0XdBq*6-q7OeUCC+}S%I!2uYp%CRm@b#`I87G3hwU|4 zC+zjs#xBhKLM&|rgiY+4B0Kk$5*jabi|Zc(f7M^slr`=SCiZW(hs^9{g1A1GgQ?Kz6ro~gjy4n*=DkA+ff55HGi zo@lol-0K!=Qmm6z>HaWfI>J?r5$dHu^L5m^^-|Se2xGQ8%#sWwWx2oz!It6Ymh#vo zJMPpWVLP6?oS9tPrGkx4}w|kt%gsw z!P-w~Qv+gBrqN|guhwI9i2@xJD*t4Vru1Y}B2V?nmS!N5BpAt2V$@6zaZ0+K6uHP% zmR#Z|Pfv=Hrz1V(X}(^{jF50;Qj)XEeYuI&O|g!_eWtYq4I6E7n_*1P7o)#6bJ+EU4eh+#WSM3gs5o_VwCI< zNlI$27#)KVqQ-cN+*X)~A9d@)zS(70smuIAq1@nF1=QRPlGSO! zC^b@ygZjO@j_#F-rtx8vgzaI~cdyGdmEdE08Ptl3Xu`CVaNd9ldt<-#-_!FUOs6MK z?oVsVgE!;K%iE*!^CL^2lG-=8L%Oy<$8~P=1$FG3G_@@UY*lX9&8A&hN5@dY5pjgF zh?uSUh?t4}0?f=oA_Bh~2VWhm4Z2Qk^gNH~x4(AjFeTf^=+nL349U*XMi)l)x@U?x zs<%qv%IDHKT4!o?s&|$(vJ}4}NjfS`gqG+nLN4|YCzW|ik~8h4=~13?WN%AFaxh$# zoD!!*i*Z+`pv~2u@*T9#dTk7Dnmmm`EM|7ptZlQt;^#EKYVF)R{3)otiz#iYPoQq2 zU#WYmT4HoKlWBdy&;71bW^unp{-mc_^E7o{cOLM;CwW}i)w&&rPg*|3R2xt+`MP(Z zE$UYxy)sl(k{CTcOq>#uE=IM%3ejwWh3NhmVOknYh*AkXc9%0r*J3IReC2N=UPZ4gj9}65NSZwS&nC1QY z{;`dieus`){Fo52@})X_U98P-U8L1{Pq@wWcR z_n(;yc#gv96Zx!>3;*ol_2`*Sz{FO*RXm)2TvHr--4cU4?5c`fT&xY5U#s+*-->tH zn)Y+Ks}6O2C@OY+LYIM=wDlGwRGtwf-qVnR3pOUXC;qrqi!r#AjMm>4sx;fz?A1T9 z9#kgy{gR>uWs5T4k;1h2a3OM{od_9YB1M6j%Mt_4lu4LORZ>*6{FC1gC32{*2C+Cv z?Y`by^{UBO>!#h&;AAZD$1WaaxH+V6Jo1ahxwGF)x}hy zt2yWPyE>B_e4X-fX`k?H)Q)Uz(4o_)=b`h2^{rErAu+N_n~+qi`si0HN3<)Fr1|4S z>AnGCRI3niiUm@Fc?YFstGyR z&*(l#-HZ%(w7oaCb2yUy;kqoS?zhD53?F-qfp@$%LUpjnr1$;fSlasnTiXA+EPtH6 zDS4W+DQcFdHFQ<5+2cU+r~SdV6svWPc#Ad8V6z=bP3sd?Jy$U6-g8Mt+OJpUV|1?+ zM`_nLzV4y-KV~M_^%vJ9wRR7zkic=$Hr7F@BycJ1#wkr26Vd_`C zD7E_as(zqJmGex2^`;n~MWg+F#EZSK^4UF~c7 zyjV0ShQ}l^3~LeNE0pNyBzcNgjttp4QIcflDoJ%fey5?6q=>k3 z@h4pEH)>Yw7fM;+7h<*NS4t7$8$BVE|1s-_;C{WL^zOKw()Ln-%IZeE^74AF+}2X2 z)bTh@>~z3d;$+xH7VK#i4`$(tCu45P=L0V4mn|kbPlX-^k0Ayo4>kr?cjiCrkF}IN zmgOZwW(4H0cvjn_iMI~v1MhSS!3>3(O>v#}4gT<+8R4FmS^l1KykH-g;no&A#Zix# zVkz}sWQp;b{}AfE@G;eMSJct{QpL{iMBXBBTwD;{CC!@KC&|&$EyprD_(k<}hRgeQ zoiF(AOu^;h*3_DGkMN-p;(QoS2?>l{+$a&uG1y&CTbfyl+3MWPp0AonIY>K7??zHm zl3-UE%|RR0)2{m!eP)kU>4uN>F<|b5qruj^qv_#9gvCW4&g`P1-smCirzSXmsdksB zrhVWgWCrGtIBg4=dTv^e1x=al*sWVHX`egv$&#GM}=F}h2XDBT;_RE)hSIn!2( zTp9xUZ{c8X3-WN;-tv?@m;xgzQUSsQDbUkG6sZ|#1v1J~#^o81NUp zZ*&uX{N*ZjS7$AIRimYRUtywkmMv#^5g}{)5UA&Jj`;4o;>(U$vfxUc68T=z|JtN( z^qpzP3THt7nTliot+Dsmu3FE)s^VDFs&s$(GEZ3!p1nAIhP5bescwk>ea2(qYbvw3H3_efo z3tWP2xUAVO866o7s63dKecqrVP7^_@q{LtDp7dF23(RO%TM&$=7lFFOQ;+?!puVe7r;u6yH0@%s~d4tukkPUrY}!2Q#@v_4k9 zF*M$@@N0M&pMxJ7!fv;9!;ZQEhh1;G>wZnY#a&UI!F^tX=3Qy6;$>Ue_mj~W@xv)E z@x8Gh-$5UW%1u2?oe*KKap@$df95N0xf00kb`)*~-)Zgj+NmCPxCrktIWz6jzSO8v zBAUd?QGL>689q@`bO)p~I5RFqv-g*x*!sy(JkzCV@zD~LV$g@3?;%A`_5RMldV(3l zhKk4LQrgD~dL~C2>elyGR?al{FxT5qHIJ=Wo{%x*KMB1yf7OiZv5w$_**6yRzTO@p zN{ADtJIC$vyA#8F!*vtFV@3Vn*J69cSNtaU`z^Qm3k)u0({vuR(lnj`uhG3zlN`yV zT$1cmBtrHs`9_78d}hE?xS&WlClnjT#YhI+2AK(*%AEL5#Ap3znpISmwjtS;w$T)opzR45qmT5gZIbZ#P7|r7jMjq58?5$(|w1!74;|D zmJL%}MjZoxc?=G|N*L_@yRftMf8j0dFA`hE-oWc-1k{__G~P~*8LD67bIWgr<_9;Y z7Tcz#Hp}{Z&yzaaPEC4;k3+{M*NrFUwp^C*+v$sg)8iAJv-pPYg{hno{BR^5-xf5x zQtvjuUS_wvRB3iFT&jOtU!ZkUUaxXq-S++ZXRR=?EPc6nJkRBSSEUAYd~@>z?@XeR}1SoW_Fq#Hrn~O@l3a*)ZS!T+Fi%Y+}h|y^YHvqeJ6gVqi(gQpa@^5 zP`Wne);NdPYsYW;4o>f<^>@#YHFZocX7(&hq9#`xyq9L0omPiCO?M_Lb@!%<)DEYU zmCi+pm;y z*%`-qUN=SmpeBbI(_)iw>ff1GP4vI#-f#QPSLh%9b=&7C`s5u_Y)FK!QCD)CWoMg&L!CLZC zBdx!lCrgR!7JpONEdHXmpC@2*iI(x$G8YIQ)n<+DcK)NH%kQ_*LEC>$kL(<%4>svU zqO(8oE*(QYPK_ll>sQ|ovsUi+yhz;Ye-*GX&tbl|!|!mi^)>5sN3DMM8eO(}>Krvc zC8V-3rDT3Jm6~$bGBdH+x;D}SX4KbB?Ua@8b;cFk1xMr`#^{x-4<=QN?YNZ;p2MpL z&y&l$mIrgXrsvTklT+?%Ge0fYXRC~N2Ai}_yK9tA`^vvx4kU@+^n{5$HXy}G$$6r* zz)SૐS7{t$jdwiq%*$dqTt4Z7>t0-P&D{7onD;Vt!nOW_Q6<8AsymTL{0u2e3 z3HnrYq%PUoQ}0p7%kWGLZM6R_U4Q3my!HlfjK-l*u*RLPiw?mfTldnjM}=UXB}4Ev zlOtsMDL)qZD&OX5Y7i=1^vG2(eV}vtglsdzYlMQuxr@BxafqnTN)8ulu}3s{pr5;R zWmvU+ZAfuoVCviaXph9%LPW*gy#4ydhUV~8uiQpQm*!E8$*<7t z# zy9)OsY??(rZGA`H?+FDz+-iqEJX)qbZ0Zf4E=gx?O!1oT&0Bk3PYe{DH*OBC)UM98 zwI4PW^q=RXj~u7Q&n*5(S{V(>nO}luPOMd=PVd5E$4?>?MoyDrdN#UHLo<`^OCx>e zyMv9ofb&&le==Y8aJW+Pyr)3usXBt6njZl6Lq39xc%%>n!wE9HF#-&)h;KA+Cjo%J zC?&~7;;z9?dcR*+VSm&_<8a(o@2o%0;Gxl9^R^SCabM=BK@2g`qq@2n5uL3}F0{?8 zcfKHP@k~B;6K|p&=04_I9P3v6AVyT^5JM`|iJoPkH$PJLu{J^Gw%JSiyj4%}w#`ZX zsm526oav(dh|n@Pg$Y}phs(I#$LjeV)v1N9^eLjp>e$j31|+L@M)aCzd&GNse!W|s z!Fb%@wa50?g(_DERW~MDe_U1#n4jQw9cKJa9EO}89g8i=t~q9RdZk7uinZDo8a2v5 z|7A&@WztW9^&$j#BOf`uoQn~d%+3f%V`ae8m>_h_`{&SzcThm|+hV7ys90+zYOLi)YP1FCJwoZrmCtw4V=rO3J^wGdM<_wdt&FekD;0cUgZZx$ zJEDK9pUh#Mo&LqSyIrL6beEe>TB)13KB#TjzUV`5Y|goDO>Wy9&Mn(s?pNzQ5|BX@ zig)@G)gg*-rkL@tE{DBaMn*o2whW$i%*{-9&d#=V_HO0%&5lNl9VQ1aZbt-8?%IZo z9=M|xw$SLA_4081Zd4F{9}_gaTi`dc-s-$K)nR(r(X4Y?-=cC_)hTt^Tq=CtQTFw* zB7%n&@BW2>bpOf-^A>>6A%YMrT#(@tB1CsW3sIb$glVC0kSmQ5A=JZ#flU@YZPS;z z=yy`O>~mMW?eJ3|76&NY#j2}3U{&=<{-!2RPUaS84&NQl)y%yvl^y-g_0U1<`rST@ zj%${;VNF_;*djG@Y^mZ?O7(YgL8>@4+fVGF(pvJW(^2-S$64{B%}n*UN?Gr)T-yA! zLCx{1&D`U(uB@8Ow#Tabox&wT=y0` zOwQXnw2xC~mBvHXgnoLh3)ebcXp~qHoq!Cul^dVA_iH_d)+rF8>!gX8YLQ1+^H(yg zj2nW-voql7AL+0xW+)W*{y7-_9`M_}gWw5oo{^Y;pa}oh&ru<7!1XOX+W9>t!GeX1 z`@wOGmH%>;Dl2-FFDbW^_f`8MU(W2XT+)6uoyT`5{x4i-@t;-WrA#AB$?R+R#LrjT zX?29%ys^X6+|G@&*7W(?=D_)@pMm%#yuQ8>~ilyw9wm!~I z^&a<+;g<$lw!4OVR?2!iw-W|>mwi^|uy*^i{!V+t?vDG7nx2OPIRAyYX1B$y1UoR7 z&~AFS$Z~1nm%+|dxyJEOsnSJDmCRjXy(l667e5J|_lXwb!%fF}e5Pi&^HDQAzkxix zAj88)nC=oN%5V!8WdsC?QQ|Ye9zIuqmWk#E`5%GXDnqfSpKel*^{z623sQoRtS@_y zP*%B*P|+nM8k!JttSt5t1RYPk6$8jV=wL#AYT!vhyU%Lys_mI=uim|1vnnyZN}iBV zCrQI52tyHGB8&(hFheI?{JzOc;f!2Nc0 z^!$)i+;kI9;Ytce<4hjQ$VkVh?WHQc+iB$71wL_Se$Ichw^w4Jyz_lia^K$tn88oo zum#0Yw-cux%e&MDqwCZz&HKo9g$Hbl1Tnc%h>%*#N5K?wLx?yIMtH_YIx>?PLSo)S z$nXykGVVQuNPGi@WB++ZhWzs!5%d~D`hv%v@2F@iCNj>5{UJ%6=RR9Q;IdRjVxv|- zdB0uFaJyO7V!D{sYod%bvcHQhr>*5}+i=W3t4sboH`_ix9~UvJd$Vohb6aCk!xv2^ z6OU!#vj;6@F7u z%JW1iSvi2GE0dqJ#)~1Iw%2w-TU%alWbVK(t#8K0EEU8mfY_%IL5P=$o&k4d2KN6WAy$+7DNc`{kYgx(+@LJIGpwbuHma;UGRSK)GQ=A++Qk*q zTK%fDspQ2hKA-FAATsG`!tL;I+-10z~%Q_XVQa|Pl%_;jKDxp0B2fe;}=Q-lzuC{BQup2km4M(|N%6TqDJ6kaF- z!wta#xv2^6ypNUEU!Q8+1)j>Bgvt46F&pvZCV<;Z8AnHqjI*pldkR;wdJl4akVzI$P|l9 zT!+aXZdQ$qDwL(b3Z-dbd7=zVln^~V9Mob!jW#++5abI*Zpt(zuZz_bA9D3H9!ib% zPD|uWHh&2^9ac*D?`8=g*9$)<&Q!7$ZP!aQALl7`_ocqZ4+REa56RA~?uwR99xK(3 zY-*2oP6h2%^`LHI$3hR>kAjBH4^Xo@hxx;Dgpw3-N?L;WBdS5@KCGRWippna#KwMv zVj@_fplB9GKr$1kXTEzzV&6Q&5w9Wtus00PkT(pMfVXsKfW5mv6BHD}OpCB*eZpyT zk&=G!QBtf0p7Kpaud8&VKrLBmu~k5Kq@CSiu=*XmBma+_rk^kS`hLCN+s3&NW&*d* zhC@&B^U2FCJH0X0GfM`I%M*ERC)Js?dwtgRgU86m=G&}Vu!`I65_5YmQ?dqk>XXLj zdm@JVm%Q;^(`MVFzqC*L^HeT>ng45Z^mgY^MoZ)EI*a|Ivb+7wLKi(bU#{D;Ki#zj zbKQ1%@H{m8e8Cf>LxUslL$fR2 zV@ohUsS5dxk{8EMPAUFOP0r#1J?b2!Tx)JpvC9`~w)VD^-lY#UNcp~Y2Vm(iJ5pW1z3gDf>ZTaq3D>ILvb zAqayLgs=z!C^B4tp6Dk)NY@v+%2klLFVI&d6q{*XRcaXQH%eQqwTZZH{1OP>tP#NA zJHKVl_6b!i=JRw6V_wZpgiBpb>JDFmo`sbQvySO4x9P!Q*VC$g>(hiW%LBh1n;Dxm z?=`(W|LO0P@HA0I3{HfEs}g*OXyv7b7qc_MlUX4Yf*FcLurLs*%#7fqcaVS7TgW@~ zEyF$N9m6y5J;TF~37maqhEOmTC=$s+M>(-lu*RI^Br{%s|5s|Ny&xe)N9;aTUG^wb zTy-+*omqdv%YfdrmuX#PuNnZyz~X#{?fpdV&dG4r$qYVjr|CGivvhmVtzct5w0vbb zr(}8`TQIg$nK=gfhlUS}BgRi~@R>-y{X?-n5X?3FGi2wCn&*N`5>iH8+}#o*BYMDRgHiReEy~Yy3YEe}(dp%i+AVl+;i3$V^Ts z0?kfOa^s+8I&xDp9C@i}j-M$>mi+V>Hz9gjm@wd25q&5zlDf|~lD{lhS39VYG60-O zR9`C5YHk!ir3A)LP50&}#_9_Z zvW+Bf8cbwwn=F+toAfo08srUk>cy-VtGK=Q3x$z8`9iUa>72PE(SJ9NMEx~8nf~!~ zIVAsK*%-gO9~33{8%(v)0$MeGWsb&4a^oH;;O$9 z(LZ^pet8@WuY`|~KiL1FBAG$_&jf`;zlVGf?-;KB@98cY4PpOLsjg;N=0_hNJeeKmeq97_f^1T^qYkK^gk;b zlm6f7(JY^<-d}qgL)|L_D?ev`?dMJuUe{pLH>Q#^rq(NCSJ#l3Wqer_e!eAi1z+L0 zHy7!)x*q2`z46n2ap9-&L0_%zPW`CTaZ{7{O?R}=*=PW$RfLP|F2o2Ojc0#5Yp&yc zEG=iJ6ehFKaxhGcGz1g15XD4jM1Oc_3;yuf z(GuMNAAGnVJ=#_138N!ULL0~uvHHq4=}KDH1!~5mByXz++)r}~D&6QVv{CoKeOlqs zrAnOc9V@~B`cH{0{zgg80&?cZLyfZeLPY8b(ooJ~~MThj)lt&dW9XrH`fz|vJ*IZtTQ#LoJgaE_dX;T^#O`9*^mA%uyJaA2XKt=XwKt55V8r!RyE?Qh3E z!cqt3Vya830!BmMxt#~)xDfL?{K->c%%u&{|IgSk!vDMjaWy}jzuUCh-&wrh(vn3e zN=x3Z?2kUKt$>~MCxzgbYW&An+Z|_ivMuKKB2DMFGk+{CRcmk0XQ=E>=O}G;^-3ML zHuB%K$MN2FNAg?^U_M>uQs!3N>r?9VVb8-(^_r$;-2oSFM4YPt(AIoJLxBhE*F8e%6z4Ahk%!t~_GFf(Oh zf{Dg;rLO*YccAfUU!LJZJy!22B}?}#a7g9RqgI*{oGVVn<_VGG%lN6dJRVx4>n94z zjE@vyAV>|j5oh@Ofx1zc6eZnB`leV*e!E&g8(;F#YPo>he=k}DwTj_Om~Q4P0kg-N z`(}l}>`LyvNp#EApfj+ou3PQn>H9?strKy(#Y2b_n^wOA`6|x^y*b-O^HuF#iz(?x z+iF3IYu;B%U@;FpB%KrT!?M$z!`Y}-NOqci92?-eVugaStPnDQ1q$>32>Bt{7#=7# zx@-JLh7XXDU?*03m;*Z<<-kQp+wnf4H2L=syi%JU+#1uS%vJ+hAA&~Yxsq1Jl#4es zm8#mJ{umrV=$*GY9d2~^-ZZTyO=n)!R3zO0ijUnb7>_(HZw%b*ZTFm;>akuN>(<{L zD^}f^$13gO;}nmFi)0T+N+tH@(uMbjs`;<$DnMN-hMimy%SNh>VSTELWFh{FV5Stu zF#&u1j*$@ihLM7KP0zypL(h!+i;*1jCnF{7B`wS66{X1QZ)#x>cn-CP28vKEt7W{#hAHqb-4Pd6Hc(Q{1FWWOLn4JN0=YRs> zpP&f9{}*S^3v_^=5@9Mt!J0`tWtqv`*SIU*w1fKipsU*9RFuZaSfa|saFEJHA5!h6 ztWt>(Rr~!ZzE+f&Q}&ILmHQdQ-k%s)KW=iC_2;Ktqi=)^Ls4p4up~VNDanX-mmH^AU;+VtaI|g@JZqrH9*bFhZQUfgJEY zMd}pU1Fu_W?6-1` z5WUflWoeP;>0JnLp4b=E|_$+OHSAd7p0D z(>X|GDZsbKF;fd;nP_>j?`b)>x4@>qp%zEJp%z2~Tx0%Wq(=WmPmBALk&b-{B}D!K z#UlQIl0yEVr}@7GSiXYd!~X*Np1&ALxIY=mh?k6{0C4pKj|2Xur+U4nXZnGhXXrZ! z7xa!E?(u;h>ID3wAJBgvc6yK_7tIOrneH0JL-TXtr39GqQ$wtTKt4%~SQRRM-4iMS zYN%2tlflyGLm}TET5#X*+F(+*oiOR!&RFT2`h3ajih3bJaoHDIcKjzGtK5tvBo{T? zll!s4ocFd`Uw~BND?(566{W|!N)XZd-!DU?lxKt98;u0~=`-X1Hs;h#A@3o?x%dXH zSFsG|XzYx8F+C2(aSs|zo_9&FPk`Q#<{pLh{B^Z%+)6keHq$oih#wv{-mUFd1@VXM zeMp-m0a+(NMwNZ0g=TX>Vc{Il=nzg2i*iATXif$)jh%r_W_iZ`>+}6t7%|=-fuCbz zL?(V@ghjA0g4|ev?z7V(Y`N(n_MZv9`ur>YY*J(Xe;IVSz3>@vew}n2DqBiOGOgMO z;A?C%|8sW2+4iQ#b$_czZn?Kle6eame?9pstPXXN(-yXxzv8i8hBrGd@6&!rY*Zv* z8l@jn>O}79^7yYh^FCiTS8+c4%3~q@j0Ux-s1MYFxOcR?m^bt+ke^J7_y>x|`~&d* zi=K^n#mGSY2_<6xgyLfVfN+t&KSzcC{v3$}3;sKV4SKY=4;Q)@B`l;h4={gzuCw^Ke(vwpm)X+ z`;}tl%|~{$5TN^bi-5RTgp!mdOe%>I069a!hbEZ7L!G@axfCHr%Rz{cOQS>|8NRWTd#p|u_LzvTsl^BrDm>UdaceyKFmFq2BN-ck#rUegLu zuW5O}wr64gW~8D2Vx+*p0{Whk_!3IS06mAjfZ_qJ*r*rJD1du7{P$-BxT3+rz z`7zg!{|f$H?9@(DaohNt?us9`&3rz$|8$pd)bgZG>P4Gp)={-;!D*{;#d4SIul6b4 zq0WAxgDF_aU8Cge{Akpf?@ZL8>2AclG9e~cikz1q`A}RVLP)9k%0MUc zK&V*Ye-gQ%h*%KgMzS%I!`bKwi60>(j^#NN;15GFKO@6{9E345P`)fAcMEossXsT( z2Ej%3vHSGsr~dWaMOthzhv{yaFUe^NaohQ^grk3GbFBd zjkE7Z47T?>-EIckeeViq19njBiA&zgMXT2P*l8^iJYSKJR4#X&-ywdR*~WjL(8f*3 zsAVDMm%OEACIh@Nujsjvf6{Y+jwd03j{kcF_B$Ea`PkSOP&DRuC?4?}loN zp!2^yqhP;3Bmb2H6p#bdZ_gOyZ%_hwO?2pQ&*)(AT=0u$MCc#S5wMq_-UU7<=p`f8 z>lGu$?Qa^+`YjD>&%}uIU7gShF5AzYMH$4{hk|1X3F#FxikNuNn2nV^Rvm4lj-%Em~DYh}!Zgo53)qL5cv^QICa8*5OdxV|#*!NitS~0x}?bE)G zYE-_=?UKGJY!-aTt^0DH(adq5*8G8zlK(dp8}ka}C0{~uxIdt{@E1@l&|4h(k7o?( z#WObY1%$!=4)Q6#L70gDLKygefo}irIVuR?ANqfFKLW@A3gC~#fP1L_LMZ?LJ)?ub zbAd1ZkN#s&;Ij}whC=^f#Cg4B#Jjzs#9F+jVeLN9qa0c27#DU%d>|)1*@ugg@5b>| zV)cpe(}9mz6Ttsehvk20isvI#XYx|A)48CyNDe64pOcXgz)j1I;wG2Ja1wuFILM{( zAny>*3cdr&a|F;U1kf)yj_DbW`T+h;P=E7dqND9uK|IOvfK&T?p7ULJJD*SPut8M^ z%pWzJpH?wjoYi*O9@F<;9a9Wl>yp5N++gZP3NM)JEnhc-=IrcDdb>SpU3l|L`E<5k z{j8!@<0NuWbJuCkXhZkFc2wrky;g)0P{2=%NC&yl93FB~F()-O3+#z8%=BC&6O@JVm3W0F(A$ zOdMBT3mG@qvE72rYo8YP$zK+Airwb7e7!5J<9aCk$?}v?^Ohcw{uhLf{S)M*{{Xqk z7tcrpz%2yWcLdN~Bv@F0Ee_~2_J99k`-}wmqr(3SMFjp2_^;oLt)-53?DZ(inTK*!O;5Cqn-shL^&ib+`<6Xz>vCg# zMP|pf=6egxnkPl0#@pClp9}Y~=wt2u(qW04iaLSYyjGsajM|Un#F7tGT)}GwGWiu0 z9{chc2JnXgza9$khXLP>0Jy>6FQ5?Y3kVhq@WcW9(f@spMEvI&9roX6SOCBo*nkjV z1A@SNLIJ*EASMh0b^r$Ke@O7}P!Q1n5MV>Xfn1@`AWjVX9g6UM0iir!0^k1^Eym^z zJ=XODBL?I{Ffb-YoIeXa*^`NyX#0^7=gt8z=3*qlxj>A`1!R;Hg2k{y2sFqUAlX2C z!bZzVU}YrZm?3O9^K)bb^S?b-jFg!7j1>4gMmp*pBLn>oiVJ%OVM5-4nBxQRH7sV2h^;Mr0!aRWp8TCwqz#)Mg%6n>+b-+QYwm;W%$diK;7vpqH!-D@ zm6nmjL`%+m$B2)94aJ1NW?8=473{yCBT&Z$8I6q$9+TBFez(PLiQthL5$x2mTGqmb zMbZ4GOU1yTP+e0M^Fn)I?ZM9@|2xX5-J-03fX#cPoBc?Y3=nf}dzGU6kcXsNF6sVO$hVEzjmh(FjF(Z1|Z1n8$kMsPsB zXikP-9FT#ykBr!O7J6DD(5;B~|K=1xE%5_ADe)Z?3wkS}5pNht*f)%L*lQ>*@C_q5 zZ}uwb&dw`a%xoAq z4Q(j;f`y)+)kxSaQ^~}~ah5fw{WjF;7kpf8csp8S@sQr^^WZk&dZ@l{KPi6i(C`&3 z?kBe@R$5U0dwOv88%Ai_Umzdx=Q9#uj{<%=9PHV`!Ow{QW3yjC*vQ`@RP=A4kLiEU z;Q)U)z#kd(A1Ko2e?XUihtL84z82UH9FPGFhy?-u$e`b#NI&5Fga3F&W5DwOe;BY2 zz*az^KrT?;FBuruSBwake;5%?ZyE8vALyyr5A^hycZ@jCH`I9R5A+xpRwyzA^iH_4 zL*WRZ`(YeV2(Smisho5~)<;@w7Be+H1K3p**sBJ=rDvi*PaWpXa}@Fogu{Tony7!C zqe8*{74Z)vG2|awvdbG&pXc=ZRjtWugh*(&wY7x{l!N0DPW=prN4!OqClNDKz^eq~IT-BxftB|Bs}vev3kF-v0yM>-)a%IY-4nQAz~q5D-NW z1*E&XYj@f1WfvBf?h-^r5nBulOb{eQy1Tn!JN%CC=ZEKlOE_|Ho|(C4=Dz3Q2huDL zyerKG@x`Xn6oQE)KgpC^8+&o>bI6sMUr|=QqyDx($KBmJe^~^yH|Zy|*Qzl4sEVaS z5o(X#Cj8a-JuZChC1P{%A(CJHI&v4??lt8;V%=i7VD-Xy#o>+arvF3XM;bd{;BaHp0mJr=@9>^?0-X^hdV(3f6%i7EQkh7 zhy%O;7@zVdet`W+Pz%tYMo5A9p8@zk9sW)xvu|e_D~6m~ls0n0j9U z@bz(NcIr`SKIy0|k9ka%OFJgZhL72tqrW+fqq1}s9H!iF4*7q52be9&iz|tzM0d!i z1b3oO^KSX8a7*3Qw@R<;ZRK4x+swOaHO4vXGQw7kn5@vobd<@b4HC}Xn)JQ#u+_)l zO&e1AS6}(L`6(uD_iNDH>=(b4Z{M6|Yuc~&RvPbIgUk`b3lUPe{|TFV`&kUTe8@-B$|zn)45}opG$Z z3#IV<9pXRj-&5l@Ugc~LJfrby>SK2(ozBC7!xrD2rtQ97T?lA4UQPIZevkS_Nsv+_ zFJ*!K=>>-&xBl^a$kSl26tE!+Y|jL{CndvyT920YpWpPTyXS^rJH)PXgEK z3@_7NZ8yhSdo%Bf$$Hvl%Mq4}^F*db*nGMXvYCFA)fj#BZmWgP`!)l!p9?Pj!&9tF zlM^(zomSGsa#O@|dvoMcU2nuhS*!nS$`_Bts9vW5=U&$#^J(`Mt-YY9D#D1e(~?Nm zDQOf{L5d(M$OwS{iOkc#X~Z)S|CEG9p=Sl9A?kuGUmZbtw88#^kPEP*0NZ>$aOG{6FRXMQq8FEBVei{Ni20 z-#y6Z?@JKZdZRntTOtpcf<@_`Qbn0+~qJ4)_ZxD*rT<>Y*2(ACFG+5uz&Uu zm`xt~P3AzXPW!*!4ioOcA1p@!`~SiANr3GcY5#`!{(px~|F?{Rhl7Eyf&Cd^TQVFx z^n)?b10q09po8Djphrl7dV&h}rvs*dJ0K%t{*jX74}*64D9k}1H$t8!K(7%`J|@9M z$qOj~e|QiokPp?Q^k@w!j-n|==V-!QPhHB&Qxj3|s0fJF31$mAl2XW-61Q&l?j14;}B={)`={{%oIgXlZ z=@&1ovP~=}GR`@TbJT(d5UQwe0Y}q6hW}IB;&ZZj%=N;^iWPQx%+Ymb9O=H-5k0)p z9J0{U9K2ZH<1%!+(`7X0i}O70r_G@MFNfh9L+-s7*8Se9^Mfjs#lb8^aafX)6rBcr zAIAUnekk-a5JRE=8?P+L@>ApI_-IKop!b;+Y;ch0ZpzK|w-Cl1gM`hH^BY@EX148{zO#2&b z0l6CdmI`;^4{rO@+o6N~iLC#~uuM1*190^J$neDf$Vga-`S`zOIKX;|ltVHU^m(w% zL%?@}`viB147CIq`Uw=c4}b0enFK!r17A-(0`ELR?janLkqO5EcLFDhaYBrYIt6+1 zj3hZy3F>v|mq%$pzSNQu*jl2PVhsth;G775TUAJ^IU~m3Iw2!u9s5n79fhBHREC3? zPsltfBLkkHr5yTAryTmtq#Ty9FmS)|N2Lki{}GrI;wZ#P2_gpaU!al@8=%I+duncS zEDTo)Os|ZvR2}+}iotEsCkQ`qNAo^l{`2fdxO~^Rm;Usqx%0F+mr^7=$S|%i}UXCFa^eY_-*P!iS4I z(qnxII}6^(CBj>I3A#c?pus}=1+%Ww(~b=_N21@{{VVHL=Kp(HPy6p!dqC>$EAz?W z*JnO|s8eaJX}C6#{tdkr-_4kFog(*JtVVq^Soi&)0XX0cFQx`?NzoA*6a1e6HX;EA zBm-unKpdw)Jf{Jsrvs*=Kwl3>{jV&M@s|tcYfe zZ63?RV1Z?DsgJDS*hM)R(ZxAMZehvgexUxR?nl2{}~_Acx0cWwSN)m>M9y5_s z&q{B`UtFL0{xoc_>^*rmp^G!;I-B{+VxRHSXb1IHdpq{M>V9?uu5kdwrLf5oOA zmmu#mV>$x{1ksoii4Us9(__6NIb=L#VBx{ zQqJ+*m};WnWSE&U&q|ZfYGPKTI&e3%g{&$qh=b?ElnPZb?v|nyTciMaL0*Pqoq+gq z0%8`#gN)-+Z0=DhE+2gUPaMJ>mg3{#=OK?vsZeV$5T~Jcbw->NuEa-ps|gXoT2eAf zcNgnuFdeRYWe}<4Jd&Xr_ABcY?i1m!{AQ-y%kOD&ZEZM(rJ1CX{a-KI=h`NVM>;2o z!?OkWvB7KgBh8Le&8;^E9{1|Kyw&J1k^RkQ5!LVB?>lm%{l@h5PnXuc-Wcpf*PRz9 z6hIA|qAJ6tsYpUwe!Q`$D$SVp00A?)&`bNzLHC-@ zzQm8Yd6DOP^Mb&SZ6FH68Eiz_n9PS7Utdfxb{S1l3;L4#5B_QCzpCEU{?p#_Kz(JN z&OGS%YuJ7fx7+;)#lKTWK0vi+EjrGXwVCltZW->UztB0L*Qp&~>&^&}_x{*SPKHi9 zEF(c5n2`;(|AX707XbYr8J+?@4z|XT4?+Ao1o-6;#QsClMC=i1936a~A}7WE@vnG! zNo>kVaYX(JaahJNX?(Vv3GDIRf5M#Y|xB?Hek4XFV9g46sYAH_YK zorWkrQb!P(pf~S(#dz3F%X-4o&~ql%F!Wp0QNlaVBR5;!72kXbko(mUrM5ARd3MlF zT$morYiggo?eSwMS8I4KCTFhMd7`J|!u$7a8g(xltY>aE*vwEnEW5*ptXe(Bo!YJE zgIh0d$G+1M#g?c`6QPGmfIC3PpOvPe&PiD)4RJz(rYI^_TSAW3mu8a;rCI5~S)}Uo zY6*r1br>Vwod{ELCfiIx&N3B6&`o(BSkuh_m&==ME1T84i!PHKjliaqLzvga|1No5 z^Y`b*TKS2YO4sdQwauH)AFg#i%ipVhfjPi7#jQ9^PY^OK}J};IbVP z1rm=7{BS2k5pky^(ah75sO(dcxV)1RRPk}}PdOQi3U`DK_Mpm1k&yGz=sz(VVlhfy z7#XI(3-?pzhWe@tLlU$g2I+u5>xg5Ev?NiP=K+sE{wHb3*l<`RO%bu;Jm{>?%II0j zQcSiI?20-K+**0Ss3!oY$V1%%T>5Ollgy*iSmH5hH2pZlt>eE*M7SF`dEl6wf_LT4 z2oeL7wn~jE{LcIE!&Y0>re=Tnkzu0V z?ke^n_h-!9;($lnuQ|tnwi$xq;AVQj%z)>o$)DN}J37q1G=I7=`=sI8Om4I77`ET8 zE2Jl2)OnK7e`yc@MpqnHd>(!;bty6TtSmJdW*%r&aeDYU0nJxif<@}fC^UW04(rQU zOan<~p`PGgh7Ru`QfI%$*FbO&Zz9gY8;h|~7X@Jv<|1UmRenbBwfPJK$3eVOU_1F3 z_Dk_UIj>9p^YR1xuYpcZ!p{3A^W(2yti1bNwO0B*X@}Stwd&m+J$-qD^-)VySawd5 zTX2@2@=!^b^Z@F>yraKq$q*-@7WfkfP)UcN2YKi>9{imQCkbqyOg;RYK|1oAM1>em zkdq|F9uq}J$@6@pPxIZWilPv*qBH?GZE?AZl7wOfNqnKaBr@x`B!U9Ci*{TRm2_O1 zKsq5q1Lrv&dr}w{t-ucsQR0OKs0m^NHKjO$F60+oNi1GZ9G#~pK^AFCFop0wI6(v6 z1Js0=qbVkqXbLfR)g*{C6>$VZMG#b`B#gMJ06p`QFejG(O@Boj+0tR?s+sps3!UgDV{O`3 zTg9R;F-Kl@k(7RRQx&JCs0O=R$aF!s&*0jiS^dbkHga&z(syj$(Rp^NzPg51g<5b1? z$a8{ZFUV_=`f%?JWhAz~jLFg$rdI0;@@{GfOXD^86+t?%XHH*`;eSEE^1mcxg<49J zXjWWOh}8^A*RelIF}R1LfNRe^o?e&nuP1Ma|BSZg+}wI~d+XQ7yQ`1C-(JdX&RfB? zW0nHiVwbFD6MyOmnD@>~3JR5lg}2X$(r$zQXCDRM1`M4Jn2dAeHyLVYGV3tRa}NKe zvjI!}siy$jbAZdiL_@qs9TUcco!|!gpXLWdsE9&n5WBNgrO0Fz;5mX_N)$z*`3mA- z&M9#S{iGxoYG4HOBxrz6ieu2H`2LY+Am*#UyMJn8OpJz@5vDDpkaR`Ccmr`1#Yl`R zH;~|qbtTAbEpaqjOM+zUNEvJ`ae9fSgjRS?5(hsof_P34UaH2AsZkQ+OBH@oIr8v3 z{IRvXC=_{06aw1XfFwmx0OyP(jCEQX3*1W#0XTM%O8be9YI9*m`jalk7UQ;N4uess4jnL?A+V8>^(joEtv{K=q)++LlV>)on^wLUfU z=CpDA&XRBK?o>d>{D{Y|p`Ug`&F=%&o;1X7rVK`p#QzMR@fq;wxw_``hPnJY7lHt<~Wdx3afKsC?B;SU92lkveBTQ4A z7OE@A_603Ns41_=$6QE^v0CzRuo<^E^B51l5Zpsnz;qE$roCYPtM(=1)MPvH%j&zp z#h>+IYmdIN*E4#uXA}FArUN^X3%287y#_l8@6JK|R}^R7JSAmAkD3bgHwOAID4111 z?u1?+)Xa<{zmwo2Gxbk@7-}iTKQbovkc1W`Cq{=I=STZ0a3j1_L{SKJDUu0&n#^;; zfF!_ftaGA>qO-!lJY|u8s**UEtqA!5{_SBFj{*IAOvD*pNT8}9C`w%%PJ-H=dLH_H z+JMvagwYgJ3BAx*$|^RLVl!b5%GQWeDZ$Otl>kCUSE6X<&}x1 zg#G0j`oU%~nzvCzX8c*~-ursuZpHv>0x?b=4(vxvIgG{)o30|hpXVk%QxcPIosv;= z05^jc36DP^#btsPsqolua=IMM&Y>@mdIV-WfZyRz0sB+1hs2~1IU&wZUP$sg!)JP( zl_Vk0%W!mf$2Ix91Wh_GMrCPAP=)GJM7o+3k$F~%1|2|LjuPC9Gon!H8Gd-6vLGf% zT^JpzB@ChKiX+ps#mHzKevH3?5Lsv@q~5&%GadsOiKZ_lr5T7h6}qrjPg7h}bWWNJ zb07}zDKm-Zq&XyYab}6SkXfT5#TTBDA(;xY1n_Yj%=?qjXC#y;sI#zu6<~fqNk0p@ z1?~dwEY~+weK*)edzAqi`*JB?c_wT9S{2Rf{k$3b}u-@*!~enpmpp8DpG zF7vx@yUu_7GH`LAcf@;Ucrl4%A~R=U143GP0sKt~kDg7}BfmY`$xpjTlejEOegjI_Hv;-hUhSqU-3^zN4fLHH^lhD^SFaguL8I7+ug@7L*c_A1L#TT5!BeF z70eH<1ALu|5Pwercxoqp(`kU4@bbV}l9#fJK%2+``-ANna0h7M|4Q-{n_lpz?3P>U!^V}VDBA)k>jqd|W~ zJuA&mR+VvpGeAI}l^|o)1*jk`9@5Kzhw(I7W}96Z%~W-s$WjkoE4M_9J~tsxbl_7a zdNIiZ319-?+jg9YN4%6^=2=Pw-(Iw zzjvIUxaqk+PF~$6K-HR;2W|o zaK7~<(P=v3NP?~?E>fQ#fHpdCO*Ptg%`iA{OVfv1SYPN*(-%i38t`KLjSs@SFE4po zUF-8v^XReHj_Nmbz;+s4X8gFQUC?T-^PxSDEqd!UfwfT|-qoy3z0BWx*N9xq|9Q2G{=;c3W-xBl zeFOF3+K%fj^Hs~+7k3(uTd?)dfFuyW!}wz8%pgQ9W*YDShVV1zk2;ce|a`-wa%|{@!|3 zue&`)X?-Z6mixwkWAv5#`pb6_Te+VwTk-vglde4`KIoFtPW~n( zpM?IOJj_!73l;+Qx$_JlA!P~jlQ zQH9HK)s)~!y3&MH9jG020FP^nWAZcs%bgbnC2I0~;x;vvhJ3Fi!+krl;hsy( zg)J}FO9w7^%YD~`tGg~C){DhIuET!gi`?u-2K*Ax2o{wA2ZRG$pFCAbQ4Vl@VD4YW zI3q5OQ-q!N5DVx~OTnQ7&z=n2AR@$j9K;1=);TE}c$XBY6B3YsQ2=M+BhO2du+T$| z*Ab@p8XT}4P1n=RtyW4eJI+75;_%{e%Si!@~iHzhAoTt?fO8_(;TH$oid?5?{_1{ehOr7EGjOO)6H zM75ja(VCC?;}skF{4~0U-Sj8>V~utO3qI_(b7rUNgEzjt2;3=a2wFpT22A;k#Eo2^ z#`YPlqZ-e_&I-^S!ETD=)RVu{IP$X0OyEys9h0%4ZfB+g?t_|-4fTFH?XZjua})+i z?l+5iRK_456_dh_i->^=0*06JL9UCsurO9vnwF#oxItHjW#~vE*gDY9{qw%FKF>YT zNEnDU=6c1O?p#Nk?c32WbKTJ92NAxO!k7dbK~RFN0E=;0ENQ#4SMch>eqo)CpyZCa zxVZ4Fqy&7qAV&%GC&1sxRRAvODc}bI_XDdaDJLpP%81IcJkU|3P*ee*o&`+>>|bKT ztQc}bJVlik7QxnS$=KjyztJ(XZ4+L z$nG0l#P*Xr;R|nG`b~5vdiVD`2lf8E8r$(zhurN&S1y%cfNO9fvrl zgU~>?^*AfP&18r0>342%&CUA6uP+n|`$}QQJ^94vA*^!$7*c(1I3p|aI_si~) zkN2jUU*v4w`N&u&x6+pb`#96q3n@P@Y$rCH=O#Q=783wFCqoa71-ogO*)WF%+jG(Z z_op2Gox(mW%T4`9mIK^P4gzXFoE*f0qtZm=F;RTzNiizyjF=4dUrxB1urm6*xEQG= zO~&X-v1onh_ZWy`;taU~!6pYTm`hxD+NE7j|0@R`sB3($Oe;|^1>RInxF*Jj*@~$I z2Vs7h-5R6$@-F+G(SFV=Eq+0bn($_s5@?G4@cMw~l?}aH&MBBR0*8ilN|udNkY$n1 zNb`uGBSHZe46h>1$Ew1godtffnv9gBCPv1c6T}7qhDz0uqM2G!3PM|i4A&DQKwp|l zzi@CH?m*3rE1Oj|wkvs;UAHK%0pfIe@cylPLGyQ7Jl3Bx?HB88UB};=1@yevP8e#p zqxSZ@!P`C-`QP5ERDZcE_w9S}(TUMQ_r2b4ueMbyZt$`+knRo@{D-KH2){RZmam_xcgc&w@Gsp5&QpYbi~jQ?A$K zF`ualQXYW))8%1C1AL$Y-~<9Ehz;|=6bA4@vtS+oGeH*VZ)r;WAz5l7#D&D8QVPsc zlA=#ZIMKk_!zzm_u`0sb(Q3kqNDWa%oHo=mdJ;l{Aulx8c-_s>d<)+6-mzg=9k|6> z3xiNM_}*zZ1U{)Y;%J7ggqCnalESp-r_|W4M1HqeMYmkoP5z+C%YwQtv;4F)lX*&- z2fH&ksS42N1^dHc1Gg^|5Bn5I3ZOXx?hx>MZ-TzC6!_#B392$GT1|!{ofG3?G=x}x z9SMS|CyCF|5hLPsgb9I$5**PO-le=CN{%wxO|!YQOf|8FJ)!Q~gs>1%ZfOuV@1fsP zdYj9Bex~(yxvj(M?W-Q6&ke$+TfC8D-Qj4c1!*5XC=`8odAz=%R<8HQt;CJCN5hj1 zuV$KiKfQiD(Nb2^^XpaBK;x%^;mqEYCGU5*9q&8HMf0q5*fqtcV#+ zTI8}3D`LSA8#!T68M$cv*ni0`$Zyu;TJ&CI5@Iv@VeEGB8kPLVmew#r=prmC{-uU-6~r(7R9Ae}*~{6@ZI!3ThMJb+Y881*DUpSAhQy66h0wSCt-h z8nja?z%M)}Bj7YdsL1oYSRd#o(hbECS^C1zIDKw#fUzKieNhx!Xe@}2y|5Z~_3})x z;f>`ab02;=DG>Ui{`~A(pOy3<&U;xE*S3r7YRDW4(Gu-z#un!tYc1syd5?1!wL|bl zulwSOtJjn9i0k`uHQ?-$Z_$vpncIWtDP{*}C+H%uzuMI3qi5&QQ1#U4Slcw zJ$>W(``oqc_6H-0qfa_RW}kg_Uwru4ZocTJ`4XpHZ=d}}O~|fMlrV0d{7p?c0a)*Z zl%0EA!UFrJWP^q)19V=%1xuy?ZwG3BCjKAjpC5*K%u!h~;Fy%Gogonl+Foos6>L4M*5QAv>vuO!@t%kgxUBvajBce0C^ zUgRX9{DT8%LC5~_UXY%!OF|1$Wsz(H1Pbxl_i`g;0Z=*2oM3<+!#+o&=s19V^c5i0wat! zT|6&uS*KdATNPb~cZV-cUB7C*8R+K9&x`it)uMa^6&%04o6kHq3O_n<3B}j;(5|p+ z+}CNR(B5M&&m~}Qs8_@{O|qG8GpAl9XxCoMik;TDtuiyH}VO54|N6KLbG&WMfDIDxN9`&Y@u{8t=H3Fy+37L@%5Nj z?U$2|uRj={Z~ft;IMap6+<|%T&k;D5aGAZ-ypIPCG75&pQVDDvv;}g&8n#I{)Z<*yO(eTm3C#VE>WQV}UD{ z2>37Y_-_{U^4S=$KX4k;fY+8v04_J|u*nKm5tf9g@74Ngt-f+JTzGTCeEW6cHGXZT z)qX9)YW0<;-S)dAc+-dNAg*BBOY#eENVC#x#92Ak0#1#^7VFc+P0UxFgM?>lFf)N3 z9#s)$W=b*~-~cq}CQ+O-QcS*rlnis9B;a~7V22JNPF{kJQ4q(+0;eKQMM{i2D^3d0 z;HP@%@KQZ5h*|WD5`2>JTF{M4Q&;S4rnE?o1Bxt%L1lvDl4YQ)AcEv6Wn_4Wil}aa zI}8tQUXANs>@E8}C*}=aP^`TK7wxo9b#h-%yb>@JtR6cRW=QR&sAPP}JzU#bexea} zi;wlwgLhhtpRd;&5B2t%eSJ4${;2M|@6?@N;giV=!Cx@@K1m4JM~xAs!26U@;iLoq zaQcBWhPmgE!a4{pW$;27RBl*8(u!>{W6io4zFv;!29}}ulv@aHMgU?x-T^kx zC6eBpx}NexMepwGqmCcn{}tT&SdV8`m&u@?{BZ?wq!4( zCJKA~r>ol>7phw}0&x60mE}iW!wk5*pw?sQYDUS;wWBMMtHaqCK z{{dVe(D}*ez}qD!9+FWJM}WV36gV}jg@^ZdpCRi6b%mWt z9Cf&yG823~yS+rA>U-tUhK@(deI3o0mjMO569i7S`=eey7ciIV^-1I3uM&6 zkLO+MFW)S7zI?V`^ZDU2tM&0zZ10mHkI7dp*H)f>Fg>_gt9Ov~LX(&MNL7+kd`6rO zGl29W;LGP9m1ct;BrOMgA8h}J|M7+8s1{aR%HM3`6eY1f#vzIMdy?RM>YO zadGFFpM{_-^139GWh2ThzsAEoH5bNLUF3z_y|Cv~tIPGPIxmQ-P?MrzPXY#gR*KI! zE1{M^zqjCwBo;KweymdhKl&+QRK#gvLdc)*0p186bk_uRF)`vi{FyEBmf)645i4o+iu(d<$F2{^rH6%5s)uaGWK~Bu7Dtp9>Ea;I-q# z_E|A!dTm(Jz4ts5y+^`zg4-kIiBo8^tbKA+;dq8#`S%AWU-f=9Y3=Lv?D_u1WTqEW zxYcg-ZK}m`;MZs8h1V@^Gv(dEW2|k)6O4d@Ox%rkCi3V}v`wNtb0OR;XVT2GY(Sk_ z)ou8pc+!xQv3xB&cj@ZgoK>3#oQ3O8nL|d82%{Imv3)j%Nljkz#r3u)-@Mk=?Qhh# zTj`6Z?)N>)+#UQ*-I{Gc?DRH1-+b9NHc;0w_VV55nWBbwv-sAJL;ihlyKc-s|6;cD z;E4h6Zk6`_?Z@i8vRWl^aRKNiO5`PZCC32wfDRC}Ky2WDG5>JD!S~s~17QM}n~nm` zX53-Wo_GSd`|_}VTR~b1TAqB$X$dFpj4(3*c8A2N z@7ANvbL((gT-bSkP>0hM+{5VcYN8EyYduYOZaG@OJ8;%gV!<^5xyBOe(Mw`{nJGW! zt`RS|TK6FKiG~EnR)xE#CSetwl`>(k4mM3m97$5*`(RXnGo-@zA*cYC?ks4|)MPBY zx&(*N5JbUVu?Qaneq@-bB!*&kkPvWbk$B~fH)B~%l_pd z%*t|*#{RRC-8nt$4Cv`OArnC{&*N9yA#^OyHVHA*Un)e|a*wX1H88a^$Y)+@^%*UHNn zQBUH`I5}oEMjpLY=l9?5jlNnd{WtUXz7D)!XjrZ6|GFGD&^jGA^R4gmXx+z!k8fMX z%0K)VP5Sm}EPSZ0%Xy}%(_*XSgVBD)bJ(?3b&gkAts*G7rywrLmzNagAD85o%1N_x z0rP=YD;2n3Dd7KqIN(XZ`6FZhk`j?%|JWm-zd9;oX8^A{>B#R?-~n*RN2J-nN!q1{7(uW*ngBjMfL2pSO8YGX^rEqqD`-Sr>r_|RW&d)|CD zcY*d~!Gis4&FkT#MaP$S*X{1!T(e{r&l*rmhqRF;U%VCXzd-)0vn$netL1tBLPOg? z$Jf~hZ$GVsynVlgYJ9s?`}IZZcx`>_X!6JQf!L0g4!8NYZ>-l!zFykNeQ&Uz^-@EW zaYt2@dq+{2Uwu-NUvNxXm?bC8$OE2i7Wh4IcK@g6{XhOM^!)#l5fcBAkr?uXc) z0-WtE=mXPXCO|v_JRaZ-K}`756XcKq7eHR1K|ZF3pA@A8DoQzMRmdTr;mQI{ezFqm z-BIS}c&dw2W3++St0T+|)aMoZm~d|gUEOh47ch)?^uUX|@%PMjB3|qB zgR3;fiACpRq~x=bC??F4XlH>hq$Y}tJ}-?SX^F$Kw1h#q8sbovx;O%_E+T|$O48$W z1^I#c+Xc24_eq{s`$0aon}N1YyAdwl`?;QhvrT^CBLf-Hi#2tju+Q6jKk=!PID=@< zWx6@9AWVHX!mPtr!)y}PGZ?t-dyf(Xm896+dp?M{$5)7Jnbxeac=g;>+?6VED)rvh z^Tc~&PcJ@MO}X}AIMbl<8)${Qi4|L)!Z-T*0*2nV>(o8^^jBKVr@!*6zaPoC)u$41 zYf6W7uU{|d-q1y-*E>PC>i2Q!FLn^o_1yTH*9S58pKQ3*JeW5ryt81Ga(l|y?_Qs3 z;-hco#xGun{I~BXjj+{Fvpo9w&FqKn&tIy$M-r==R$0{_)~l*s_5aFlXdXedeH`$f ze%xryEw8w;oAvtQZt8nIKI^H5F!{cUkn`ZQDC6!4X?h{_`Z9srlM3^F;C(Vd@B4@Q z&-$a^0L~}&54Rfw98My94;l710M9-d=7MDEVc>uOrwjPosFY*Bi4YeEq@z-F0&sk= z@?u03=*wbZpAAx3mPA#RQbCu`%sM0HL@9Dpoz;Z2U~S<4>WXQRdLl*uyzhj($bG^w z1D>yuFb>}O!rU$7Fi(k9$RwUe5uJ`s_d=(HbOe4qarG+Pql^UlkK1G-G&@SB{aJ$$x7-PTOY2P$4 z>z3a%Zj|(`{S>0PS3ULTlu^m|C9C|;%MQ4Yqk2i7f1USfZo6zh(@JQb>i2G{@5i*1 zb=~eIj#mD1<8i94>?gl6d!9oGr-ApYBuylrgJl7rM27bP1BNd6{5WTFMUCviy-Q>HHp zrRhTNN>@sz=}IvqJ#lP;AvY-G(w;NgiWiKx-Y-Jk*l0jGZ@*%=?H4|<7o||H^NZqa z*Qy*mwsL(UcjE$3n{;RTY@&7k5Xtn(uT-tx`55<|+2ES(Zo`6!@eAbf`6$Hfynph- zun~K%$L(YP5*zb%J~OtpJJ4!!oWb7jDesvWEo%GNd1tt^zj6q*ME~w`K(4hoV81fp zvj9UfZi22Y7dRt-G+-&9{eV3oWZ;XEh=;|fm}C5q@H0Dhh_lNkiRYJ1Sy~&{NLpOq zY%M_$LqizGI4>okwWLX~8$O+?$Il2b=9R}^5v9Sq9tgInFf_$f7@iKZ1G=Fg2xTmY zh`+QQ;AVXgN^%jzvz_-)_igu!vaamDNVMSHq+A!~=R%E%wOo7PY`=2XI&h`J5w(c3 zNM4M$sMsJlKkZFdYaM#-vp)5hxjTD{wmXj~+}TByEN+#?E=)e*tPkZj3@v!EzyES{ z>21E5ywEs(r|)Oq)5iXWwue8Tk0p=4ZVZB0;J{6OV8KmzY{-p%bWWIf2kK||{Yl9% zCrkd{ocxb}$S7zzVPfE^gBXu<^KLdK0~$egJ*PP?6S`$K^Cl(cE9PkzxdG*d5-iv^ z6SN(vBpu!Zg6_eCXhU%!*+fD~FcXBMEjO&;jd63dS;)Osu|U55Vkyn3 zdFX3Sd(W@n&c0qo|F0)*3lnK?R_0!24b4_&^iR4+4fPu$Cw|7!R^PuL_|`R0R5AXO zd~5Xe*MgpVqo}3Qk52sTVoM1#vz z8lUZ}B_c){^Am#1;oVx>J%qpOx<`u7e#9*&ekjduH!H+-rzXXVSDog*`=HW^%PPIT zp9pV&WUvjSY`UQ+A;x&y$Jb&i)Yxr-Y=&CT2%-;=G|R@^92?eA=$1vyi!bXX=(h{^ z=pWiRI{p2K+LiIJTfHlBKK;`HS60>o$h^+<#^tYNi?v%-h&@d3>`Ce4uxFJq&1l-VA^t8Cr z@2=(Z>6%hjj~V^>FQ=~ZNy}>5q}x}-WYjO0P2_uH2_{!d%1qaga0q7wX?~gqbT?Ce zVx-kRHQa71%*AUjk{Y%h@z`64Pj(ejb7AK>(_WmJW-nx9!+V)n+s%*{R=l_Z*gcnU z;UL7%e97O+dBe{+bRBD(IO=Je`omG}QJ>9?-abR-_@HNF`?4qf!>lv*OAl3RV3Lr( zJsVazJ#HH?w}KDd=N6<3T1qA-TXSbCb~EqOL|N&C-OQTAZG3I`j`uC^UDrzIeIJgE zAPQ}_;q%CP`C66nv~Gspw05e_ygo~F=>kP-&5WtNd7YxQ>}h*p)ZWCo-9{VPj6Z#E zipKfYj~jWgnDsezqT)y5bY7S19_9HZ9<5ealwY7GPK#Cs4V1+hiu(u`|d>&{Ff~9eYZT*-S=bi zoaan2&Xecau9N4JT*fu>92bnzZKibywu36kw%r;&PVJYBVux(5WKQM>-tO(fJRPiU zzB``X%N%*s6|z$M&YD|t&vd`=sg^jSR9Qp{QRZX3wbsxUX1(!he$(-h>#RqqW$3Gzldkce?dp+KdPy3R8jlE46>K|c@O?J}9r@vJW z54S#PnXPJ{}E8YgR-S%|v1<#_FCI3=L3HN4xXWdg|M; z6V;tAI~7kaax3rZ^2_d=m1L%$5mJLxH)3q{W?imZ&!yY?&wPnMY?oos+z2{h-LV)m zs+Ai*d--PA+I336l1oVBdSqncws#SF%R3si6<&y63Vk2G>%#Wgiwz0b4h@Q#cDBbg zg~^vUCMXP!#Z+!jI@XO%c{96uC{D9O)z9ZfUOng@X}|krz6t$dFok-YDrw|vPRHK*BvN0uDLZ4#jP0)2jFqU~FE8u`Y;{p%hR_e>EpXy>#wkCLq2qzBBcj4;{(8=JP8 z?UJ{F!&T1Nh1^|tVm)3&pdYQKW;~xkG*5cQ+`$$#88^M5V>87^^nEK5dnK}LQ$&cE8(k34Dr;by% zv+sxOR1<@CGfD#vg38^wepybN(E-lD6O9Y`@JT2N`K4)xa;Yq7^6B$#8YuV!t zj`wD-hSjfHV;V*c$c;0eZ{E)4eZDiqZcAQBZHS(I{LN+SQT1hBO}4S9oOVG>Ofo(Q zEZ5(4#%ixvprlt7(I@kdOY?UsaJdJC(IvYmR_RiL!|kER*FVfOWqqIel=J1sox%IPMPs<_iYh-* zG2L63hVtddvI7qMY5r@D(Scnqy7;O1xWe_K$Jsn`MbaLsm^dD2%NX*~y*1!z@@4W$ zOy9H_VPMLUHr^FH)Y?xSyg5&P&K$}ZidfEThPOIu4TZT$+5&8pfglE9y6S*5A5f35 z88f&SyJB4x%#EV?Yy<^`Zbt>jE|M%sL)jXo9UR5RnaH5|@#vP(F1P-79hcj4$FJ9; zcRX?;wqt!__oEZAD`6qzHI!ZYGSRteC-zqLydR~!&++opejC%D3t_~`$)vXK9?!1l z^KSPF56~%D+!A8$`lINA^*esI7aJ%wV}r>xL%mg117GTMhB-aRJ<=^NQA(0KKRwc0 zoJk1er(=SaNe*${cf1(gzc?wYAJZ7S6)Ci>h}fhVjAzzxy5aq)9LuJehu%Y5If$7h z7IUFJbFyP7dAM>qw>@bzXE=DB{K0A`@TSqCRlL!RX@KRlevEyWmYrXZS}1z&KR?+EU9l34v9s!7-0#EhFkCL(RXBg&0jw`{ry7*gTrP{m3KK?FEGhV^}e~2U+?5jcC*>z?RS_{Z4gUdJc-!Ptnw1zF&+W}3Hsw?Uw&S) z?@C=r&{RuK=yV%4X!cuZoPsQeL4Pd+jvY<-H2Iz$--3@XCmlI z`Faxe(Rx%$?VMHQlSL=@cXJkgKSnRP42(rMt+{hHa-3X>H zmvqZ>w<5=3`L&O0@7s$pQ}q$dwPe@q%@p5?xk}HcgWnPLKc-XPb_^lkcfV+QF^8%u z8a98%8PKf4|2W1#HyY8))sDApBakHqQUk0x_+th1bw|jTo zoTWA3mt)F;z(@nSB&q zv5dO*bSom{^Ln7yj|n@=iSbaxW@r7K#ooTkzKOQ{_U;z%u2u$Z;hpo`*PlL}tZjNR zV!8Bf$V%N^*NqniHX9H99JsfL9^C9OA0Z<>kQbTgzhRFH-Lj^K@A+XOwjxU+_aYzo z@5DWF*oX|Un~$~gpJbS!m(raxmU9D(mmbq^&%e1-I#Wf>nSBvjGF{EMwH|@3TJlN1 z-FPOl=G|dJ{b$3vr=8TF?4{aJ+*q|iR$rpltN9zTEn_vIlM^|2rv?j}fBYg1-ktP) zOW#h9B5c+MCePOgWp%#LeK2>|r+F>ay?+7sq;J?Muesl-{PURg+h;@9s%{S3JKUQ0 zyY`M7k7`;WSa(j4ELVE&)i3rpH#E(?Nq@C=kNsx$fnR-V^@ZWa;7=0`wljCfy}#gR z=segB5+kBA{^2iBz$JC@$TLC@k0_r(`eRE8|Q)cF0;_``;b$wy*mc zZ`bv+G;8c@|okDCLcLeWkPMK(|9=e(&5L)d6yR*qttIR#Z?Vct}h-BU27_u z_pPCBRnV~;#on}ulFRvh?<}7V<>_|~`qO7R;fbDMlSi|?rXSl^TyMYL42pTZUJ&(h zg%jQ~4!1$r z67Cl^7i}>10D(_GhQ8g(oMi1SByxIYspC=mPwxQ8A)Z z%Nf^x);0UvY2W+p%D(T~6>>j!DwWp_X%s#jv3pgz7+A%g@ODU_U;cw25XEW(f4 zxAC_Um(y<$*B?f3`pOJ${v@3K*_D*G+*cbqyNDt0_4@pnpK-k1v>sW}u#UsmclwzA z?8MTSn_hSRY)fvt)fW_-IgT;8Ka*4aeEx0Gt?oM0M@_dZ$Jj2=_hL|B|&mELWM$GinO#?DaGC00|^p$B?(S&cPLQY zN{hQ|2vG>Z-6aXVo}>3XFZzO+&SXwI&g0zo{kLs87IRY?KFc#7h&#hNnahJu3+8$s zw0!ITGofVqJ*`BXQ=1`6FNu=mL=zXrGqC*W%&_(5T%Xf?jK_X`l!s(8%u_HM`^Y5lA;#v%R0B(*d!bV9VI> z^h(*p7bfk&eB!3w85DIqdfzwtBxrHYCvt1PC3bCUiY1%vz>4PzU@Jpjl(mtUxuOyC ziJlqrwwy(19ecU-D{`y+s~=!BJDz;O*d3KI><_9^t#)b?KCKOee%`5d(dGA=n}g%` znJiZ$ZzTI>%YqZL`Kt8fX_e@Nu7}21ty+3R{VB+msqR{ytgN7asmg9(veaNjOm5ti z(IdsnaV{H+jFkQHEYZYzOi}d)(x*nksj1nbF|(yXi2T`?@!4(WPwRR}q~(do!JY~8 zp29_=D(0$bDRxo6%Ky*}>AoNCAF#tr3)oFfcRL{lxvU@!gQi`cMlD+S6-hqkXYvgk zvwEy=cg^~{Y;R&rFFp;huc6m*UfGL2 zRwaD>kfy==&Rtz9w!8s+hHUC+jQ`2c9g*F0tBc4+eKU4GZy@4tfF~vs5iuH-a^Qn)n5nHyLnww2DAG$cj$oup3UV9%Dc5nm|P9v8DCEocyIGOLExTzz}zU(rYV9yGU%J+96azAb0U zf|?O~E-`^S7O}pEkWimF_yg1!=C6u%8;7BFpo?16B8>>hmUE(9>)lya&&D#Vy?>LI z+Bi#8>Eq#SHufStj@MwEQ?6y+`k2?9KP)_q!Rnnqg$f1bmt85+Z+5CsD(y2Up)N$+rWUuOyQz)18&VA5ViO^ z4kuKFBgb^KnL`c_>ZD$Jvr_#4*{VV8>V{L6KtwiM9xCh?Nvd+%w^<3zqMWy#f~L;` zNtMU$$~VsLQc3X6f=~M46k=q)CuyZ(E^!jMN6GcwhX1fTrq}A9;HqC=Msgl2Ban|l z?dO>?7WPa@hN&HsZ8Z5Y53J{-?<1EobyB9-Dh(5ns!KbG(I=wp<~2dyz__?IuSMLB zZaT<~ZrUq@v`V{ZZ8H;zO@g}G59tW1wZ>9Db_}?!Gf*}q3x0(M}F&qK?7&s7tK0C5@Th)p7EXUp4arN zT61UJ16Rk(u$%KK_?>N+>~(=(#Kr)FKQ&+YrDAq2J|B3ON~dOQe#{R$P0VMw^Chl! zJ0g1C(Ij0kHSRZGF=;YH-Z0Gwl{)phZ8_C^JoGHrz6?x#afP6QxoP@SB{S^tCCy#+ z1ZAVWL{qg~$bSJ77QT+@BmdsarMz07%x_ukovG`YnJlPY&gB#>Mu+7~I}?h9qY(uw zRncWziNwOCtje72_`GOgHooBvl1D8AR!m+4J9s*jFc^8EwyJ8Bm>^0(XKaXyey5@3B*J3CIN9SDQw>qB~6Y= zV#jNRq`{u5(^@ZZkfPsC=zpgS>x2)FVXz_Ng50uX+~mthrY7;gh6L z!)qa172o;g&x*06=Szbj9nv0)mWCzYjNAjXf9`5hdckbVclPWQ5lEtY>+I!?5`2ybinv#_V=#nMDp`>LQTb!77!Y;?WI znY)*d6tAXM&TrycdshQGs`qV@@^@Vb*#m~J>bruy#chp~{k)d+EFs6Ba5VeL&|H6r zR9GD)>!mFXj<6SFcM}U>+eHY-OsNWKyk51ux9s+uFp+&EgB3^@d`2hcqvpS?Guva1 z<5CDaNmcMoMzilKX~=vxy5+Tk0Nh!D@JE-f^jDWYsF&w~5Y0o3-G`M79a~|;+mOMg z`*Gh|9t@8qz_$k97cO_-ukW4JE~plx+A{gnaojG86}Z}r3z2mu2g;f%+?HDk%y%kc zjn0|T`j=_A52v|ky~A{C6X_R2m+uwV=xsTGXNjy@oJXg(Zc%)HtW#}A_M@TPgP6RT z_1Lza4P0vNt_}O^l(ubSr#jle>|{&n;Zl=SntHyxX+Y5MfEh_Egt!% zHX(?&-4olD?~Peb&5T>lec|bMv6s#2BMzb}ypQry9girnHrwvyCg(7!-g$h=`%4Z* zOA!^Oa};A`w1@d@H%PhxZ_4;bYacmkZC1B*jQ`4OOl*i)*hZ&v53;kywutqe+n%T< zp;=ISpSc--sHRfd);Hfg-_XX|DTWicqrQyM@2_)&g?iomFD*UX^1fVIWl79NS~dP8 zE-q{fWr>{hf0Wqc@OSTmmEVdO61Ky~Hmwi%@cIUlL`kCNui-mibA1mBa$R>4zFO?q zm*}3_Q?%p(5!##YLaeslLF_lQ!(5luu%5eCOphaIkmp*GGi0Pgozc_%q;{lTT_BDp z9P#KA0zjMkzG(I%k7qs{wdgnKzvn@6+;$+i?gtk-974Z-x?tqIRlwnzmw^!7gFsh9 zX^4j7WbkdwVDi24uG-JsDbI*?u`_LRDdWqsxT1Sp_C4wQb{_o4nwv-OjOkn63^s13 zw{U5&KVO_Is;v)S=^+KJeMbe2ep4lmOqi8&2Z}3a%0}bXl9-r{fK24712lX}=WSH? z+b5L+uCFEHOzh$6myCTu&38#({X|E1$~t4Su-@aeJkjo`FvH>q4mI2PNVD2B&il0e zDacUtG!Vjl=oc}4H;gEJ5=)f4uZq~lB>NrYh5Ib0=!8#y(~R%ze$)z-!?ICir+AH7 z-M-;cT08GKnK|b_7rBkZcy5N1{nwoGJ*2DwlZynV<|W+g?XI2erzLGC`)OV6V4jsx zj0FC%a4J%#ZH%V2Bn?PA7NGjYf`XAwNmF^LpwqUDU-qhdj`3PL8P~DipCfCTi(QD3 z1$RLXvB_>{8Ii7%9PMCXk2PIZADYOw%Z-^)^I?tN#w54@;hz2NFO#;uXXf)%A<7zEF})>T|^UJ9?(qQ$)K-nms|{?62EuUl>qZ>o|{4Sw4Khl zAlPtwF+KlyuzY5Ap{S#FjZvQ=qgkiT;GZ>(glR}e>0`^ol(iZeX^6Fn{2sZ>X!bZw z2X)yhYfovnQ;4uqi!$f;F`uXIQ6o0=`#x=|JF#G5u_0n5?oa+`7frQRe_!jsHA`B* z)=uX05~Y}LuvNbfm$R^FQ+W{9RL+VtKTUPAJHWboEtrEkcSc~wa%)VQU?@D5*MQC+ zix;pY_)d?#FdOGJ2YblA7X-dS{!DC%{Z~z2AX6%~X&zo7cQx=J!>Q|L#l&NO7J5Co zI6{y&;<6Ljtb6W5(AbBX>&l{E*~wBgVcQ&InlM8nOHv+G)BjzopWo`VDqvA}`^`sY zrnGXKrk`e33_WwM?A3TRF^dl0nJk{=568)>momB;>q+0kcG%xtFXCe@PIE)7cB>qm z1Yb2m`wDMmEYa;cg#nIZJe!Zw1y1zt*hmYPUy;?dD5`WY6Tdu2M{=iqiK6OS+^T zt?;GkQ0VS-WApM<>u_IJD?ekly9Kt~k?px%{KZxtOEJAjV}3r$b2i^7F>oKOc|;su z@F?yc*KZv!(34GKyH_R?2fr_7d)94GksT{mmCehBIc1`ksrkz$d(K;lhK3E75K>zHZ%ml=^{-gRHEiWTnpeNZweBY18l{Q(b<2#Zyfrgi#;j>yi~u?uyqle2cgl2i zyd)9bcPr^`Ck5&H2jx`Vof=oequOwb!v>tyP7A~Gu-M!BAk)Qdzar32l=Bibp7St! zr1)N+u+(^c5>~s>5wYGRrVP_%iG9JROs4BDG2i)sQ~v2JBVQLdIW^@;5S`0Z{QIMd z@b}wwb|#CZ8g2`@Pw|V@PU%BSA^Dx6&&?BaKxH)3G}zAno;x7pu*O6js-US2x>=X$ zwx0)gISjyi?K_k@?b(-`Uih&MF2e(jE?8dX%kkQ7TR1Ds0l|&hm2|yyknOh2cW4(# zA_lv*l8Y*LQxTed*&NlCX7XvE@OGqs?J{vg2NLq4{}0 zmYxEUsB@Vdu6tcys;N*xIwPWCU+4QjS=xCAO za;wGU@PPAt!gApE;Nu{U$3aYu-7&4o@PeMHtst^rDPzK3D-w`e@;IjEVS&4jr2f6x zRJjV2-$SQyXOd9^Qa3`&PB7>Da%D7ktQ5N08s&FbjPpBUaD29X@|{-Qog5d9Jt49; z84#I4o%_B;j>Sa?$>bd2ZN3`$-fbh%7`fv0F>2W5LFo|oK7YZraC_WzY*eI`(zx`N zP%rUosa&B{CLX3m#0tiu66VKqqC}i__=aDz>sdJ3_6U<>eGtSkUinyUc^F*%K|y7` zxlDm+Ub21OUl0)=FBnLpQx42@zb43PtJ&X)KjQ7#)qIaU)9|`xvf$aoU}*GOgNdwu z?&A;aTEKVzBSN*!5uwKL5}l|ehfv?hLzx=#WEz+&!2$O&_BHSuy_;(@a+($-d2ts~ z?F4gB8{Rz3MKVrzJt(0&pD+WRPhb$AwNGx4nfrvu?pt<*{=dRG6ZeZ_=hT`|5{)lD zGR}?{W#yfFHr%9v1J~o$uYJk_;C9zJ)9%gVcd5mpwCziOVU)r@fsJg-fu6m zuzDAq2>pvxs*yYeZgi4|GTvZXT1;g;3mxjw;Pm&tZ0;@kwAlWslmG4IOyRgz7ka^Y z$oT}5Z+Pj?);$jdvns4S&C8ffRe2_u6--B}?w5JKS{jAvil;bl*LxYSx7!0XH@m~% z@3saS?3cQl?-MLswu3)I=B!@Rh9B7Fj^1^xTYMYd&ed*hm{V`cn7mia82gijpL;|{ ziM3pDy*f`byA2+WOndt6h|q|`)%3{I%>v@ZeC+53m$T3_lgZ23Os^rzirPcBi>n=W zS{W9L<4E9IX4{-bv&=RzdiGK$qYz2zK=5inx7D#z<_CEI`>hg&(@~(1?-WehJ9!3D zR~a3tcNFYoyzcPXaUbO#AWqXl4aVNd9U$MHn8jiDJ3sO#`d<#__vmtw!gm!mhi0)R zCy*k8^T?7na&+$VE8u3n;$RO!6_oVXv8xh5QAJo4O z1-i*b2TR0#%kkrV(unB^K147&)Wq%WnyDTg=wtHxYoUuBHEw%lRc5;t6}mf(c#W-Q zFWuc7591Azj?-eae)#T}RF`vFj@^l8rs<(I_xM$yTZ{8AIQ0pKH8)+Fx6-#o${?)8#0*b>HRED5t#Ob>^gpN-t%kvd=^~% z_RJ?w{W2g;MM;ahe-#sdTNxXDLy<=KOPLyRL!KRe=Q1-8aCqIG%TiyzmvJ;)*P(jh zy}s8|d;PD^%B;hVHhoCPT&L2l;b+COV-LM&_|HfZpkzou{-bVwm|xW- zXh|xWZw)RV6@}OJ2rDbQCPrzq6ZO8puWY&6m#M$s82j!lCjYe(M^cqD{9o?Hn!I1n z`(Pt&3-mp%LR*|B!mZA{AogqTFwXp^3@4dktnDGJ%=8oyt#_0Q*Wa)3_b$|w4_&C%qEB?ZDVy%r?HNwu%kn_=wpdx*5(8Zb+}LV5FCFc-ZUHyl}1cZ;tvKT~M>*B0s&u zXmhh;x})=YsA=#{5EU+t?DF47HJG2plzq5NqH3NN0LVt2kN#?P;Hq`&vwNb=;%I@GFbz1J?j(dvj+_x_X#IBeuhFh`zp>n8#D z*oiUMf06+=mWsHhVEElUiMD*Sm9DQPEq!anD^(BbDfuh8KljGS0yA_^kliO*YbA|I z+b~n36D~s0m%szwm*TzN(cbCKzPYl2_DS?&58YGR;1($8D+-zsraR5@vn|%Ta}5rQ zi#5+_StYrKnTE%|rj6vu9B4A0{))MT^@x@m2^ZqUP9MRhjs zWHxt6stfuDMk(_@x?MM#$Y#>-zK*<}Zx)OF<(ligY}JDntm;Jp3%I@6PnEIs$BG2Z zXQ$=-t+NX3t&=*;oy$VXU3o6yE^zJLk>|kg{91P}i?ENB z@qksr#NAUQ2R>571w2(GK-Dg2Zg0+_>KH-XH@Td_qkOlU?3%t`RJ9xb}^R7>A28x9LamX+FGf0To?23usQ1f$=CQh z@{FXvl<85|6xrC_`~cV@5|fW=B$@HEd`HGDieV7I_8EVGwQk`19)=NnZSF>1};6|No5oM zag_l7=T!m)WUMH_g$MYl@$lcS;v=sqDWNy6h~amXguwf9RHUjL3Q<=QaBnVBI4@5t zi7!sSguMig%h$(MsV~k7vK}ciSa%ee*gL2Bo)1rR++XaWjNb`Dp4m@w9uii+xMVDs zo0RsqzUuELvsQk%ue1#kRw&%0Iosp(T-}p|%2!HCipozK8!*%WON$IMehOL`HYZ4$4LIWTH!|UONFYfszJXCc(N$1^M z?F;9T9(DZi*M}wJoq8ifX_)at*LY{1N5G65d} zI1uRMfBd8Y7Yd#JKY&95JZZp%Mgk560&po1@&5z-IN+Rsj1?0PTxigL$^XxT1-Y$A zj=cAap#ZpA50%NZM_0^f;Eg2%?h)WcbC|cTVx#^B{JR_A9s(XQ++SCWP~ebpd$3(- z{A#RWUC-(xuDNa`wZA@&uf8v*=Ux9vO!&o#1B`!gTtwgyB(lIY2fhf(|GC0|7Y?{0 ze`SgBz$Jn~flTQyUld?VlVShw|2!~kz>$b1{H=)h2kxIRuoj^2|HOj47aq7g6OwK! zQ%QjF0GJQaV80SW{^MsH24vPCeI_kG3$THH@qhvECj7=F!~M=V%jLO(0=%#RNS(`o zXiWtbuDM_4@}56rV?H`->@(V>MV^j%TF{36JW~^}+0f~~?zQaLp|$T_`{**V;Lj@@ z2k>%!Ih240<(Kb{27C#GU%m`HaOD6`3I*KrxL+O|z|W@v2Mz@sKX79Jp3eWfVg&!^ zDh9lNljwgcu)cpP5dL?s0QdSQiu_PX$h@bZ=7Nkp$k#*zzX2QMuh?MDJ_@ju<05`n zq`|LWa6JD$N;G@29s3TfgO8kMSa)&bzE8@g+_Xn{1jqG0U%~8voj9-Gb`dsf&(+`Y zY6Q9ZxW5347w|MdPLl??b&>4x=$zxOcADa)bB>JHmE)7&T&9y>9#z6#3#)Yu2QsdO z^b`J5_yc3kYhZ4)_Bo5g7d{P|%L0bfPw>?@0W386Py`d!e0~dIhiV_|TxS*N$ z<>)-X{$Tv}Uo;8$A^?|?N&-AukUhg7e*>A%YjU{P-)DH+$6HJtZ9eI~!!YzZsmJ&C zR-whGMUhX??pV;&;)tEFcF`s?1q7@mC=z)7(YL_0zjql0vf?BU zjdL>e{Yhx4j+~V8Ldk%uZpIjB^=16w(Gv5&`Ry=!Nweu(SG(>^%os%CwiG_0x$oNu z{I%(TpUec=4{%+7c{6@J>uBIZjR3BM2<-oYOfq2SqyF_Xj0za&AYY3Co-GV;r67P0 z0TT&w6Cgt!Nw{|5g1e`TK;2i;LmnxJfQ63F2HdjDo4_M~4fr%TZE;MB7IxC*2E2Dq-j|HMXIzaqeHoFZ)RA9=Z`OKh|Z zc=j*6I-IX1^tt{uxDXbyHv=Ewi?Mdo3!WMLkvHEv+Kt4?U>)!gd5rzq3q1vlxCd7h z3Rg@L*ppJ#&Wb}`%Gux@901iiLfGp`XwMzGV*W+@p8osLa8~+SWA=7+ zJ7ULgnbi0CA~x^gC9(L%FNQK;ll)@c1Mda6zIfcPEYQC|CjCEhZ|L<)55x^+AmgSo zIO)1Fm=5^lc);5Qoa$iMpBKI!cL0}GMG=fw1KdwlC5-yy3PpaZz$HIYLa6sI1JZ8; zPUxRM;iT&d|HwaqBlyNuFz$vjBIMRykg>{=h3<#B4_eOSMrzn$K9Cpw@2abhQa_fr%~dc#AeE0-R-1y!4LLk&M0LiU_NwS=af`-Pr=Lt z{B6Jx2K;D{fdcGczz(AThBztg-+;Gu?cCSruEL)J7}0YaF7;l;Rl1)%{>eB_OD zcfY&xK-gm?3b2_XlQgbyG3rW8w5lSW^91Cgfcp{fBZ*n}ui$|H6%O24NGRZYf=pp3 zDzm=~0@)1m!? zTlaD%tNR1jcg3g@)D_ABQ#}e~3n|YO^bpl65*2u-0c#k7e5yeDsGUUGYfBt8Ekq{o zgT|~Mvf6PsJ9;9Dq>TZ4B~u}yARd%&uowRAp%Mc;Ul`oAD_ra!VBH7oRKU!}k^Tv| zlmC$uJit4~>jB6xs$9WofQuOm_TN$WEyTKM1YWkw+z~ z0q!Zt1SSE`EpT1N!S7!YWA0x;>36Pz@pqJwSg>cs-&LYv_vJAEC#Oi8*D~r$V_Ete z-<3ue^x_18FgL%c*B_zK4y##YBloXH0Kt*U3Mpn>Q^T+COU^9qLis1zW=sm(dXTdaR!@OX#I6f0^ln` zjQ6aN<|}U!KW`g^8*e*VSxe||1Hjx2dm448p6jc!FvE`8?M#r2wxG6{Q*eppI`GV6~_~+1A zLEL)(_rX41S7qf??qq_@vjw{JCfQ5;nC2#Zj(1yqMQ~r&OLW;V_jFx1jrG|x<2auL z1i0*xg1v|Fk1>7GzkeOUyp?p+cXOMj#)^Bp$KbOW^JY7&4%Kr;>8)S2gexNOiBW++ zfC|2OD8LIuh5qM?0@ysUFyQ%x-o4BSczhA%u67h`_x9KuVRY^v`su=*{#g-5{3Iu^ zO}6nMr;cg{j)WM%c>=s}H0{PuQWWrd0yY^l@-HRT|JOI#!wZ;)syvYR>crLe&7mz_ zXUpD6cRSL;>O76)d>B`2eMCWg+)pvH*+>I(gro<=@3H@C>BUf&2CWC$g-$I65_lhL zhfxx-9}yI>=Y~V>e~Lovn-JlvMyX*t*67d+SX{`y4K--ebt0w>^=Qa&LnB{@N7-96uen^k`sjLk1aGVkqz{M_dEk ze6VK+-p_cyyQk@nPj~apv`_OehR4}{#%JkdkUNh7c|NA`38hy12vYl05d^+Z!hkaj z3f#ts#J{e9A4?GyctZ&d0ndHlqYH}biwkV%n`OI?MuKNyc2X^}{W%)&i&Ci8piXFc z>P#>@3wO7cK-B#B{x2B=oa?nCx$h+XZWYpT?XDjq4*b+j|5ErFJ^+3K^}_F&MbI}* zVyL^;M9TK(O#FdwZulm|K4M7KF{Ny6j5xW3sj-bc|hJPP^(k{x!C(x*tmO2DB~8`7u}KvfGjx% z@bDvpR4#%&U!I0~YcFEe%!R?`A&XuuLHq1-Yb70PehPE5-T55hy7>|1wh*WWTc>}D zSx8kYp6S)^8!2~|3|W5{_E<`*cut+f|LZYUR)t zJ~&f<*xsn@cD7j>s7P2DAAm1+_I1xT^#b2t3(reh+NXP*U;gYYqxg=3lYCtn_bW>e z_CHjx|0RM9I2Hz&2_(SF1*{Yn>NmivymrMw{HbI?{+7pu+&?cxJUT0dJ-y6?s2yk9 zYhT7A^)GS~wJu4NH-|nY>T+n(qboS`o+1!&Pwo@>7;w@bDI?GiVq+s)1?@un9o}Q3>G%I8?<`aC4uFKjXo98XpT78`>)9M?d zVpjScCEfi`YqCaf5)$~&{nPjc*2TLn%$hALX7!eJNy*x~_$6o=^bT;SR7?1SRQ4;KJR6?dv4RL(IWDvy!lXLKHnxyBK&}v6&f+; zmt5le=N-JpCedkxUNM_YTalk1xEa zj}?)>vd8Eb=cyqYasol?%pLdk#F6m!*w5GS1VXYp!l!*aO(*M~r4!z5rkPr-QuR

WJOrqmAw6^s{k<#>JY((8P(<^yM4H73 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 BQ Date: Fri, 10 Nov 2017 09:07:13 +0000 Subject: [PATCH 02/10] ocean fix --- Editor/Editor.cpp | 12 +++++++----- WickedEngine/wiOceanSimulator.cpp | 7 ++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 16a13946c..9c5bf10e7 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -208,7 +208,6 @@ void ResetHistory(); wiArchive* AdvanceHistory(); void ConsumeHistoryOperation(bool undo); -#include "wiOceanSimulator.h" OceanSimulator* ocean; void EditorComponent::ChangeRenderPath(RENDERPATH path) @@ -1322,7 +1321,8 @@ void EditorComponent::Update(float dt) } void EditorComponent::Render() { - ocean->updateDisplacementMap(1.0f); + static wiTimer timer; + ocean->updateDisplacementMap((float)timer.TotalTime() / 1000.0f); // hover box { @@ -1537,9 +1537,11 @@ void EditorComponent::Compose() } - - wiImage::Draw(ocean->getD3D11DisplacementMap(), wiImageEffects(100, 100, 100, 100), GRAPHICSTHREAD_IMMEDIATE); - wiImage::Draw(ocean->getD3D11GradientMap(), wiImageEffects(200, 100, 100, 100), GRAPHICSTHREAD_IMMEDIATE); + wiImageEffects fx(500, 500, 500, 500); + fx.blendFlag = BLENDMODE_OPAQUE; + wiImage::Draw(ocean->getD3D11DisplacementMap(), fx, GRAPHICSTHREAD_IMMEDIATE); + fx.pos.x = 1000; + wiImage::Draw(ocean->getD3D11GradientMap(), fx, GRAPHICSTHREAD_IMMEDIATE); } void EditorComponent::Unload() { diff --git a/WickedEngine/wiOceanSimulator.cpp b/WickedEngine/wiOceanSimulator.cpp index 57220bf31..e8f0c1a64 100644 --- a/WickedEngine/wiOceanSimulator.cpp +++ b/WickedEngine/wiOceanSimulator.cpp @@ -431,9 +431,6 @@ void OceanSimulator::updateDisplacementMap(float time) device->EventBegin("OceanSimulator", threadID); - device->BindConstantBufferCS(m_pImmutableCB, 0, threadID); - device->BindConstantBufferCS(m_pImmutableCB, 0, threadID); - // ---------------------------- H(0) -> H(t), D(x, t), D(y, t) -------------------------------- // Compute shader device->BindCS(m_pUpdateSpectrumCS, threadID); @@ -471,7 +468,7 @@ void OceanSimulator::updateDisplacementMap(float time) //ID3D11Buffer* cs_cbs[2] = { m_pImmutableCB, m_pPerFrameCB }; //m_pd3dImmediateContext->CSSetConstantBuffers(0, 2, cs_cbs); device->BindConstantBufferCS(m_pImmutableCB, 0, threadID); - device->BindConstantBufferCS(m_pPerFrameCB, 0, threadID); + device->BindConstantBufferCS(m_pPerFrameCB, 1, threadID); // Run the CS UINT group_count_x = (m_param.dmap_dim + BLOCK_SIZE_X - 1) / BLOCK_SIZE_X; @@ -521,7 +518,7 @@ void OceanSimulator::updateDisplacementMap(float time) //ID3D11Buffer* ps_cbs[2] = { m_pImmutableCB, m_pPerFrameCB }; //m_pd3dImmediateContext->PSSetConstantBuffers(0, 2, ps_cbs); device->BindConstantBufferPS(m_pImmutableCB, 0, threadID); - device->BindConstantBufferPS(m_pPerFrameCB, 0, threadID); + device->BindConstantBufferPS(m_pPerFrameCB, 1, threadID); // Buffer resources GPUResource* ps_srvs[1] = { m_pBuffer_Float_Dxyz }; From b66d1ed63192c7127e220e0141e840f30234c739 Mon Sep 17 00:00:00 2001 From: Turanszki Janos Date: Sat, 11 Nov 2017 17:15:08 +0000 Subject: [PATCH 03/10] ocean rendering update --- Editor/Editor.cpp | 53 +- Editor/Editor.h | 2 + Editor/Editor.vcxproj | 2 + Editor/Editor.vcxproj.filters | 6 + Editor/OceanWindow.cpp | 40 + Editor/OceanWindow.h | 24 + Editor/perlin_noise.dds | Bin 0 -> 43816 bytes WickedEngine/ConstantBufferMapping.h | 39 +- WickedEngine/ShaderInterop_FFTGenerator.h | 16 + WickedEngine/ShaderInterop_Ocean.h | 74 + WickedEngine/WickedEngine.h | 2 +- WickedEngine/WickedEngine_SHADERS.vcxproj | 12 +- .../WickedEngine_SHADERS.vcxproj.filters | 14 +- WickedEngine/WickedEngine_SHARED.vcxitems | 6 +- .../WickedEngine_SHARED.vcxitems.filters | 10 +- WickedEngine/fft_512x512_c2c_CS.hlsl | 27 +- WickedEngine/oceanGradientFoldingPS.hlsl | 2 +- WickedEngine/oceanHF.hlsli | 64 - WickedEngine/oceanQuadVS.hlsl | 2 +- WickedEngine/oceanSimulatorCS.hlsl | 36 +- WickedEngine/oceanSurfaceHF.hlsli | 35 + WickedEngine/oceanSurfacePS.hlsl | 79 + WickedEngine/oceanSurfaceSimplePS.hlsl | 4 + WickedEngine/oceanSurfaceVS.hlsl | 46 + WickedEngine/oceanUpdateDisplacementPS.hlsl | 6 +- WickedEngine/oceanWaveGenHF.hlsli | 23 + WickedEngine/wiFFTGenerator.cpp | 69 +- WickedEngine/wiLoader.h | 22 +- WickedEngine/wiOcean.cpp | 1520 +++++++++++++++++ WickedEngine/wiOcean.h | 257 +++ WickedEngine/wiOceanSimulator.cpp | 609 ------- WickedEngine/wiOceanSimulator.h | 119 -- WickedEngine/wiRenderer.cpp | 23 + WickedEngine/wiRenderer.h | 6 + 34 files changed, 2298 insertions(+), 951 deletions(-) create mode 100644 Editor/OceanWindow.cpp create mode 100644 Editor/OceanWindow.h create mode 100644 Editor/perlin_noise.dds create mode 100644 WickedEngine/ShaderInterop_FFTGenerator.h create mode 100644 WickedEngine/ShaderInterop_Ocean.h delete mode 100644 WickedEngine/oceanHF.hlsli create mode 100644 WickedEngine/oceanSurfaceHF.hlsli create mode 100644 WickedEngine/oceanSurfacePS.hlsl create mode 100644 WickedEngine/oceanSurfaceSimplePS.hlsl create mode 100644 WickedEngine/oceanSurfaceVS.hlsl create mode 100644 WickedEngine/oceanWaveGenHF.hlsli create mode 100644 WickedEngine/wiOcean.cpp create mode 100644 WickedEngine/wiOcean.h delete mode 100644 WickedEngine/wiOceanSimulator.cpp delete mode 100644 WickedEngine/wiOceanSimulator.h diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 9c5bf10e7..3bef1efc1 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -14,6 +14,7 @@ #include "AnimationWindow.h" #include "EmitterWindow.h" #include "ForceFieldWindow.h" +#include "OceanWindow.h" #include // openfile #include @@ -208,8 +209,6 @@ void ResetHistory(); wiArchive* AdvanceHistory(); void ConsumeHistoryOperation(bool undo); -OceanSimulator* ocean; - void EditorComponent::ChangeRenderPath(RENDERPATH path) { SAFE_DELETE(renderPath); @@ -263,6 +262,7 @@ void EditorComponent::ChangeRenderPath(RENDERPATH path) animWnd = new AnimationWindow(&GetGUI()); emitterWnd = new EmitterWindow(&GetGUI()); forceFieldWnd = new ForceFieldWindow(&GetGUI()); + oceanWnd = new OceanWindow(&GetGUI()); } void EditorComponent::DeleteWindows() { @@ -277,6 +277,7 @@ void EditorComponent::DeleteWindows() SAFE_DELETE(animWnd); SAFE_DELETE(emitterWnd); SAFE_DELETE(forceFieldWnd); + SAFE_DELETE(oceanWnd); } void EditorComponent::Initialize() @@ -293,6 +294,7 @@ void EditorComponent::Initialize() SAFE_INIT(animWnd); SAFE_INIT(emitterWnd); SAFE_INIT(forceFieldWnd); + SAFE_INIT(oceanWnd); SAFE_INIT(loader); @@ -305,17 +307,6 @@ void EditorComponent::Load() { __super::Load(); - OceanParameter params; - params.choppy_scale = 1; - params.dmap_dim = 64; - params.patch_length = 1000; - params.time_scale = 1; - params.wave_amplitude = 1.0f; - params.wind_dependency = 0.5f; - params.wind_dir = XMFLOAT2(1, 1); - params.wind_speed = 100; - ocean = new OceanSimulator(params); - translator = new wiTranslator; translator->enabled = false; @@ -478,6 +469,15 @@ void EditorComponent::Load() }); GetGUI().AddWidget(forceFieldWnd_Toggle); + wiButton* oceanWnd_Toggle = new wiButton("Ocean"); + oceanWnd_Toggle->SetTooltip("Ocean Simulator properties"); + oceanWnd_Toggle->SetPos(XMFLOAT2(x += step, screenH - 40)); + oceanWnd_Toggle->SetSize(XMFLOAT2(100, 40)); + oceanWnd_Toggle->OnClick([=](wiEventArgs args) { + oceanWnd->oceanWindow->SetVisible(!oceanWnd->oceanWindow->IsVisible()); + }); + GetGUI().AddWidget(oceanWnd_Toggle); + //////////////////////////////////////////////////////////////////////////////////// @@ -941,6 +941,24 @@ void EditorComponent::Update(float dt) originalMouse = wiInputManager::GetInstance()->getpointer(); } + const float buttonrotSpeed = 2.0f / 60.0f; + if (wiInputManager::GetInstance()->down(VK_LEFT)) + { + xDif -= buttonrotSpeed; + } + if (wiInputManager::GetInstance()->down(VK_RIGHT)) + { + xDif += buttonrotSpeed; + } + if (wiInputManager::GetInstance()->down(VK_UP)) + { + yDif += buttonrotSpeed; + } + if (wiInputManager::GetInstance()->down(VK_DOWN)) + { + yDif -= buttonrotSpeed; + } + Camera* cam = wiRenderer::getCamera(); if (cameraWnd->fpscamera) @@ -1321,9 +1339,6 @@ void EditorComponent::Update(float dt) } void EditorComponent::Render() { - static wiTimer timer; - ocean->updateDisplacementMap((float)timer.TotalTime() / 1000.0f); - // hover box { if (hovered.object != nullptr) @@ -1536,12 +1551,6 @@ void EditorComponent::Compose() } } - - wiImageEffects fx(500, 500, 500, 500); - fx.blendFlag = BLENDMODE_OPAQUE; - wiImage::Draw(ocean->getD3D11DisplacementMap(), fx, GRAPHICSTHREAD_IMMEDIATE); - fx.pos.x = 1000; - wiImage::Draw(ocean->getD3D11GradientMap(), fx, GRAPHICSTHREAD_IMMEDIATE); } void EditorComponent::Unload() { diff --git a/Editor/Editor.h b/Editor/Editor.h index 6735c1b27..1c1c134a0 100644 --- a/Editor/Editor.h +++ b/Editor/Editor.h @@ -14,6 +14,7 @@ class LightWindow; class AnimationWindow; class EmitterWindow; class ForceFieldWindow; +class OceanWindow; class EditorLoadingScreen : public LoadingScreenComponent { @@ -45,6 +46,7 @@ public: AnimationWindow* animWnd; EmitterWindow* emitterWnd; ForceFieldWindow* forceFieldWnd; + OceanWindow* oceanWnd; Editor* main; diff --git a/Editor/Editor.vcxproj b/Editor/Editor.vcxproj index ffa45db89..2947be691 100644 --- a/Editor/Editor.vcxproj +++ b/Editor/Editor.vcxproj @@ -177,6 +177,7 @@ + @@ -197,6 +198,7 @@ + diff --git a/Editor/Editor.vcxproj.filters b/Editor/Editor.vcxproj.filters index 159aa2f1a..6ad593974 100644 --- a/Editor/Editor.vcxproj.filters +++ b/Editor/Editor.vcxproj.filters @@ -67,6 +67,9 @@ Code + + Code + @@ -117,6 +120,9 @@ Code + + Code + diff --git a/Editor/OceanWindow.cpp b/Editor/OceanWindow.cpp new file mode 100644 index 000000000..87d236fa7 --- /dev/null +++ b/Editor/OceanWindow.cpp @@ -0,0 +1,40 @@ +#include "stdafx.h" +#include "OceanWindow.h" + + +OceanWindow::OceanWindow(wiGUI* gui) :GUI(gui) +{ + assert(GUI && "Invalid GUI!"); + + float screenW = (float)wiRenderer::GetDevice()->GetScreenWidth(); + float screenH = (float)wiRenderer::GetDevice()->GetScreenHeight(); + + oceanWindow = new wiWindow(GUI, "Ocean Window"); + oceanWindow->SetSize(XMFLOAT2(600, 300)); + GUI->AddWidget(oceanWindow); + + float x = 200; + float y = 0; + float inc = 35; + + enabledCheckBox = new wiCheckBox("Ocean simulation enabled: "); + enabledCheckBox->SetPos(XMFLOAT2(x, y += inc)); + enabledCheckBox->OnClick([&](wiEventArgs args) { + wiRenderer::SetOceanEnabled(args.bValue, params); + }); + enabledCheckBox->SetCheck(wiRenderer::GetOcean() != nullptr); + oceanWindow->AddWidget(enabledCheckBox); + + + + oceanWindow->Translate(XMFLOAT3(30, 30, 0)); + oceanWindow->SetVisible(false); +} + + +OceanWindow::~OceanWindow() +{ + oceanWindow->RemoveWidgets(true); + GUI->RemoveWidget(oceanWindow); + SAFE_DELETE(oceanWindow); +} diff --git a/Editor/OceanWindow.h b/Editor/OceanWindow.h new file mode 100644 index 000000000..a7c2c0d77 --- /dev/null +++ b/Editor/OceanWindow.h @@ -0,0 +1,24 @@ +#pragma once + +#include "wiOcean.h" + +class wiGUI; +class wiWindow; +class wiLabel; +class wiCheckBox; +class wiSlider; + +class OceanWindow +{ +public: + OceanWindow(wiGUI* gui); + ~OceanWindow(); + + wiOceanParameter params; + + wiGUI* GUI; + + wiWindow* oceanWindow; + wiCheckBox* enabledCheckBox; +}; + diff --git a/Editor/perlin_noise.dds b/Editor/perlin_noise.dds new file mode 100644 index 0000000000000000000000000000000000000000..0fe2ff0a5374ffa887a28d7d5e5267c4064383de GIT binary patch literal 43816 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 literal 0 HcmV?d00001 diff --git a/WickedEngine/ConstantBufferMapping.h b/WickedEngine/ConstantBufferMapping.h index cde75e1e0..ff517e3e4 100644 --- a/WickedEngine/ConstantBufferMapping.h +++ b/WickedEngine/ConstantBufferMapping.h @@ -6,30 +6,35 @@ // Persistent buffers: // These are bound once and are alive forever -#define CBSLOT_RENDERER_WORLD 0 -#define CBSLOT_RENDERER_FRAME 1 -#define CBSLOT_RENDERER_CAMERA 2 -#define CBSLOT_RENDERER_MISC 3 +#define CBSLOT_RENDERER_WORLD 0 +#define CBSLOT_RENDERER_FRAME 1 +#define CBSLOT_RENDERER_CAMERA 2 +#define CBSLOT_RENDERER_MISC 3 -#define CBSLOT_IMAGE_IMAGE 4 -#define CBSLOT_IMAGE_POSTPROCESS 5 +#define CBSLOT_IMAGE_IMAGE 4 +#define CBSLOT_IMAGE_POSTPROCESS 5 -#define CBSLOT_API 6 +#define CBSLOT_API 6 // On demand buffers: // These are bound on demand and alive until another is bound at the same slot -#define CBSLOT_RENDERER_MATERIAL 7 -#define CBSLOT_RENDERER_CUBEMAPRENDER 8 -#define CBSLOT_RENDERER_VOLUMELIGHT 8 -#define CBSLOT_RENDERER_DECAL 8 -#define CBSLOT_RENDERER_TESSELLATION 8 -#define CBSLOT_RENDERER_DISPATCHPARAMS 8 -#define CBSLOT_RENDERER_VOXELIZER 8 +#define CBSLOT_RENDERER_MATERIAL 7 +#define CBSLOT_RENDERER_CUBEMAPRENDER 8 +#define CBSLOT_RENDERER_VOLUMELIGHT 8 +#define CBSLOT_RENDERER_DECAL 8 +#define CBSLOT_RENDERER_TESSELLATION 8 +#define CBSLOT_RENDERER_DISPATCHPARAMS 8 +#define CBSLOT_RENDERER_VOXELIZER 8 -#define CBSLOT_OTHER_EMITTEDPARTICLE 8 -#define CBSLOT_OTHER_HAIRPARTICLE 8 -#define CBSLOT_OTHER_LENSFLARE 8 +#define CBSLOT_OTHER_EMITTEDPARTICLE 8 +#define CBSLOT_OTHER_HAIRPARTICLE 8 +#define CBSLOT_OTHER_LENSFLARE 8 +#define CBSLOT_OTHER_FFTGENERATOR 8 +#define CBSLOT_OTHER_OCEAN_SIMULATION_IMMUTABLE 8 +#define CBSLOT_OTHER_OCEAN_SIMULATION_PERFRAME 9 +#define CBSLOT_OTHER_OCEAN_RENDER_SHADING 8 +#define CBSLOT_OTHER_OCEAN_RENDER_PATCH 9 diff --git a/WickedEngine/ShaderInterop_FFTGenerator.h b/WickedEngine/ShaderInterop_FFTGenerator.h new file mode 100644 index 000000000..516888ce1 --- /dev/null +++ b/WickedEngine/ShaderInterop_FFTGenerator.h @@ -0,0 +1,16 @@ +#ifndef _SHADERINTEROP_FFTGENERATOR_H_ +#define _SHADERINTEROP_FFTGENERATOR_H_ +#include "ShaderInterop.h" + +CBUFFER(FFTGeneratorCB, CBSLOT_OTHER_FFTGENERATOR) +{ + uint thread_count; + uint ostride; + uint istride; + uint pstride; + + float phase_base; + float3 FFTGeneratorCB_padding; +}; + +#endif // _SHADERINTEROP_FFTGENERATOR_H_ diff --git a/WickedEngine/ShaderInterop_Ocean.h b/WickedEngine/ShaderInterop_Ocean.h new file mode 100644 index 000000000..db8f09a09 --- /dev/null +++ b/WickedEngine/ShaderInterop_Ocean.h @@ -0,0 +1,74 @@ +#ifndef _SHADERINTEROP_OCEAN_H_ +#define _SHADERINTEROP_OCEAN_H_ +#include "ShaderInterop.h" + +// Simulation constants: + +CBUFFER(Ocean_Simulation_ImmutableCB, CBSLOT_OTHER_OCEAN_SIMULATION_IMMUTABLE) +{ + uint g_ActualDim; + uint g_InWidth; + uint g_OutWidth; + uint g_OutHeight; + + uint g_DtxAddressOffset; + uint g_DtyAddressOffset; + uint2 Ocean_Simulation_ImmutableCB_padding; +}; + +CBUFFER(Ocean_Simulation_PerFrameCB, CBSLOT_OTHER_OCEAN_SIMULATION_PERFRAME) +{ + float g_Time; + float g_ChoppyScale; + float g_GridLen; + float Ocean_Simulation_PerFrameCB_padding; +}; + + +// Rendering constants: + +CBUFFER(Ocean_Rendering_ShadingCB, CBSLOT_OTHER_OCEAN_RENDER_SHADING) +{ + float3 g_SkyColor; + float g_TexelLength_x2; + + float3 g_WaterbodyColor; + float g_UVScale; + + float g_Shineness; + float3 g_SunDir; + + float g_UVOffset; + float3 g_SunColor; + + // The parameter is used for fixing an artifact + float3 g_BendParam; + float Ocean_Rendering_ShadingCB_padding0; + + // Perlin noise for distant wave crest + float g_PerlinSize; + float3 g_PerlinAmplitude; + + float3 g_PerlinOctave; + float Ocean_Rendering_ShadingCB_padding1; + + float3 g_PerlinGradient; + float Ocean_Rendering_ShadingCB_padding2; +}; + +// Per draw call constants +CBUFFER(Ocean_Rendering_PatchCB, CBSLOT_OTHER_OCEAN_RENDER_PATCH) +{ + // Transform matrices + matrix g_matLocal; + matrix g_matWorldViewProj; + + // Misc per draw call constants + float2 g_UVBase; + float2 g_PerlinMovement; + + float3 g_LocalEye; + float Ocean_Rendering_PatchCB_padding; +}; + +#endif // _SHADERINTEROP_OCEAN_H_ diff --git a/WickedEngine/WickedEngine.h b/WickedEngine/WickedEngine.h index 2da68da3a..9d147e042 100644 --- a/WickedEngine/WickedEngine.h +++ b/WickedEngine/WickedEngine.h @@ -56,7 +56,7 @@ #include "wiSpinLock.h" #include "wiRectPacker.h" #include "wiProfiler.h" -#include "wiOceanSimulator.h" +#include "wiOcean.h" #include "RenderableComponent.h" #include "Renderable2DComponent.h" diff --git a/WickedEngine/WickedEngine_SHADERS.vcxproj b/WickedEngine/WickedEngine_SHADERS.vcxproj index 117279127..4b923b7f6 100644 --- a/WickedEngine/WickedEngine_SHADERS.vcxproj +++ b/WickedEngine/WickedEngine_SHADERS.vcxproj @@ -32,7 +32,8 @@ - + + @@ -461,6 +462,15 @@ Compute 5.0 + + Pixel + + + Pixel + + + Vertex + Pixel diff --git a/WickedEngine/WickedEngine_SHADERS.vcxproj.filters b/WickedEngine/WickedEngine_SHADERS.vcxproj.filters index 9d994e897..09bebcd73 100644 --- a/WickedEngine/WickedEngine_SHADERS.vcxproj.filters +++ b/WickedEngine/WickedEngine_SHADERS.vcxproj.filters @@ -115,7 +115,10 @@ HF - + + HF + + HF @@ -669,6 +672,15 @@ PS + + VS + + + PS + + + PS + diff --git a/WickedEngine/WickedEngine_SHARED.vcxitems b/WickedEngine/WickedEngine_SHARED.vcxitems index 21b08991c..7fa360db4 100644 --- a/WickedEngine/WickedEngine_SHARED.vcxitems +++ b/WickedEngine/WickedEngine_SHARED.vcxitems @@ -236,6 +236,8 @@ + + @@ -334,7 +336,7 @@ - + @@ -674,7 +676,7 @@ - + diff --git a/WickedEngine/WickedEngine_SHARED.vcxitems.filters b/WickedEngine/WickedEngine_SHARED.vcxitems.filters index bf7044b98..09759cad2 100644 --- a/WickedEngine/WickedEngine_SHARED.vcxitems.filters +++ b/WickedEngine/WickedEngine_SHARED.vcxitems.filters @@ -1107,9 +1107,15 @@ ENGINE\Graphics - + ENGINE\Graphics + + ENGINE\Graphics\GPUMapping + + + ENGINE\Graphics\GPUMapping + @@ -1898,7 +1904,7 @@ ENGINE\Graphics - + ENGINE\Graphics diff --git a/WickedEngine/fft_512x512_c2c_CS.hlsl b/WickedEngine/fft_512x512_c2c_CS.hlsl index 841d66ad0..26286a559 100644 --- a/WickedEngine/fft_512x512_c2c_CS.hlsl +++ b/WickedEngine/fft_512x512_c2c_CS.hlsl @@ -1,16 +1,4 @@ -// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. -// -// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED -// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS -// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA -// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR -// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS -// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY -// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, -// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// Please direct any bugs or questions to SDKFeedback@nvidia.com +#include "ShaderInterop_FFTGenerator.h" #define COS_PI_4_16 0.70710678118654752440084436210485f #define TWIDDLE_1_8 COS_PI_4_16, -COS_PI_4_16 @@ -18,15 +6,6 @@ #define COHERENCY_GRANULARITY 128 -cbuffer cbChangePerCall -{ - uint thread_count; - uint ostride; - uint istride; - uint pstride; - float phase_base; -}; - void FT2(inout float2 a, inout float2 b) { @@ -107,8 +86,8 @@ void TWIDDLE_8(inout float2 D[8], float phase) TWIDDLE(D[7], 7 * phase); } -StructuredBuffer g_SrcData : register(t0); -RWStructuredBuffer g_DstData : register(u0); +STRUCTUREDBUFFER(g_SrcData, float2, TEXSLOT_ONDEMAND0); +RWSTRUCTUREDBUFFER(g_DstData, float2, 0); #ifndef FFT_V2 diff --git a/WickedEngine/oceanGradientFoldingPS.hlsl b/WickedEngine/oceanGradientFoldingPS.hlsl index 2908024b8..59ecdc3fc 100644 --- a/WickedEngine/oceanGradientFoldingPS.hlsl +++ b/WickedEngine/oceanGradientFoldingPS.hlsl @@ -1,4 +1,4 @@ -#include "oceanHF.hlsli" +#include "oceanWaveGenHF.hlsli" // Displacement -> Normal, Folding float4 main(VS_QUAD_OUTPUT In) : SV_Target diff --git a/WickedEngine/oceanHF.hlsli b/WickedEngine/oceanHF.hlsli deleted file mode 100644 index fed79e654..000000000 --- a/WickedEngine/oceanHF.hlsli +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef _OCEAN_SIMULATOR_HF_ -#define _OCEAN_SIMULATOR_HF_ - -// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. -// -// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED -// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS -// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA -// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR -// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS -// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY -// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, -// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// Please direct any bugs or questions to SDKFeedback@nvidia.com - -//---------------------------------------- Vertex Shaders ------------------------------------------ -struct VS_QUAD_OUTPUT -{ - float4 Position : SV_POSITION; // vertex position - float2 TexCoord : TEXCOORD0; // vertex texture coords -}; - -VS_QUAD_OUTPUT QuadVS(float4 vPos : POSITION) -{ - VS_QUAD_OUTPUT Output; - - Output.Position = vPos; - Output.TexCoord.x = 0.5f + vPos.x * 0.5f; - Output.TexCoord.y = 0.5f - vPos.y * 0.5f; - - return Output; -} - -//----------------------------------------- Pixel Shaders ------------------------------------------ - -// Textures and sampling states -Texture2D g_samplerDisplacementMap : register(t0); - -SamplerState LinearSampler : register(s0); - -// Constants -cbuffer cbImmutable : register(b0) -{ - uint g_ActualDim; - uint g_InWidth; - uint g_OutWidth; - uint g_OutHeight; - uint g_DxAddressOffset; - uint g_DyAddressOffset; -}; - -cbuffer cbChangePerFrame : register(b1) -{ - float g_Time; - float g_ChoppyScale; - float g_GridLen; -}; - -// The following three should contains only real numbers. But we have only C2C FFT now. -StructuredBuffer g_InputDxyz : register(t0); - -#endif // _OCEAN_SIMULATOR_HF_ diff --git a/WickedEngine/oceanQuadVS.hlsl b/WickedEngine/oceanQuadVS.hlsl index 1578c0c98..da1c5caea 100644 --- a/WickedEngine/oceanQuadVS.hlsl +++ b/WickedEngine/oceanQuadVS.hlsl @@ -1,4 +1,4 @@ -#include "oceanHF.hlsli" +#include "oceanWaveGenHF.hlsli" VS_QUAD_OUTPUT main(float4 vPos : POSITION) { diff --git a/WickedEngine/oceanSimulatorCS.hlsl b/WickedEngine/oceanSimulatorCS.hlsl index 54d5043d9..e91be6f10 100644 --- a/WickedEngine/oceanSimulatorCS.hlsl +++ b/WickedEngine/oceanSimulatorCS.hlsl @@ -1,40 +1,12 @@ -// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. -// -// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED -// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS -// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA -// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR -// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS -// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY -// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, -// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// Please direct any bugs or questions to SDKFeedback@nvidia.com +#include "oceanWaveGenHF.hlsli" #define PI 3.1415926536f #define BLOCK_SIZE_X 16 #define BLOCK_SIZE_Y 16 -cbuffer cbImmutable : register(b0) -{ - uint g_ActualDim; - uint g_InWidth; - uint g_OutWidth; - uint g_OutHeight; - uint g_DtxAddressOffset; - uint g_DtyAddressOffset; -}; - -cbuffer cbChangePerFrame : register(b1) -{ - float g_Time; - float g_ChoppyScale; -}; - -StructuredBuffer g_InputH0 : register(t0); -StructuredBuffer g_InputOmega : register(t1); -RWStructuredBuffer g_OutputHt : register(u0); +STRUCTUREDBUFFER(g_InputH0, float2, TEXSLOT_ONDEMAND0); +STRUCTUREDBUFFER(g_InputOmega, float, TEXSLOT_ONDEMAND1); +RWSTRUCTUREDBUFFER(g_OutputHt, float2, 0); //---------------------------------------- Compute Shaders ----------------------------------------- diff --git a/WickedEngine/oceanSurfaceHF.hlsli b/WickedEngine/oceanSurfaceHF.hlsli new file mode 100644 index 000000000..7106f1364 --- /dev/null +++ b/WickedEngine/oceanSurfaceHF.hlsli @@ -0,0 +1,35 @@ +#ifndef _OCEAN_SURFACE_HF_ +#define _OCEAN_SURFACE_HF_ +#include "ShaderInterop_Ocean.h" + +//----------------------------------------------------------------------------- +// Global variables +//----------------------------------------------------------------------------- + +#define PATCH_BLEND_BEGIN 800 +#define PATCH_BLEND_END 20000 + + +//----------------------------------------------------------------------------------- +// Texture & Samplers +//----------------------------------------------------------------------------------- +#define g_texDisplacement texture_0 // FFT wave displacement map in VS +#define g_texPerlin texture_1 // FFT wave gradient map in PS +#define g_texGradient texture_2 // Perlin wave displacement & gradient map in both VS & PS +TEXTURE1D(g_texFresnel, float4, TEXSLOT_ONDEMAND3); // Fresnel factor lookup table +#define g_texReflectCube texture_env_global + + +//----------------------------------------------------------------------------- +// Name: OceanSurfVS +// Type: Vertex shader +// Desc: Ocean shading vertex shader. Check SDK document for more details +//----------------------------------------------------------------------------- +struct VS_OUTPUT +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD0; + float3 LocalPos : TEXCOORD1; +}; + +#endif // _OCEAN_SURFACE_HF_ diff --git a/WickedEngine/oceanSurfacePS.hlsl b/WickedEngine/oceanSurfacePS.hlsl new file mode 100644 index 000000000..49344016f --- /dev/null +++ b/WickedEngine/oceanSurfacePS.hlsl @@ -0,0 +1,79 @@ +#include "globals.hlsli" +#include "oceanSurfaceHF.hlsli" + +float4 main(VS_OUTPUT In) : SV_Target +{ + // Calculate eye vector. + float3 eye_vec = g_LocalEye - In.LocalPos; + float3 eye_dir = normalize(eye_vec); + + + // --------------- Blend perlin noise for reducing the tiling artifacts + + // Blend displacement to avoid tiling artifact + float dist_2d = length(eye_vec.xy); + float blend_factor = (PATCH_BLEND_END - dist_2d) / (PATCH_BLEND_END - PATCH_BLEND_BEGIN); + blend_factor = clamp(blend_factor * blend_factor * blend_factor, 0, 1); + + // Compose perlin waves from three octaves + float2 perlin_tc = In.TexCoord * g_PerlinSize + g_UVBase; + float2 perlin_tc0 = (blend_factor < 1) ? perlin_tc * g_PerlinOctave.x + g_PerlinMovement : 0; + float2 perlin_tc1 = (blend_factor < 1) ? perlin_tc * g_PerlinOctave.y + g_PerlinMovement : 0; + float2 perlin_tc2 = (blend_factor < 1) ? perlin_tc * g_PerlinOctave.z + g_PerlinMovement : 0; + + float2 perlin_0 = g_texPerlin.Sample(sampler_aniso_wrap, perlin_tc0).xy; + float2 perlin_1 = g_texPerlin.Sample(sampler_aniso_wrap, perlin_tc1).xy; + float2 perlin_2 = g_texPerlin.Sample(sampler_aniso_wrap, perlin_tc2).xy; + + float2 perlin = (perlin_0 * g_PerlinGradient.x + perlin_1 * g_PerlinGradient.y + perlin_2 * g_PerlinGradient.z); + + + // --------------- Water body color + + // Texcoord mash optimization: Texcoord of FFT wave is not required when blend_factor > 1 + float2 fft_tc = (blend_factor > 0) ? In.TexCoord : 0; + + float2 grad = g_texGradient.Sample(sampler_aniso_wrap, fft_tc).xy; + grad = lerp(perlin, grad, blend_factor); + + // Calculate normal here. + float3 normal = normalize(float3(grad, g_TexelLength_x2)); + // Reflected ray + float3 reflect_vec = reflect(-eye_dir, normal); + // dot(N, V) + float cos_angle = dot(normal, eye_dir); + + // A coarse way to handle transmitted light + float3 body_color = g_WaterbodyColor; + + + // --------------- Reflected color + + // ramp.x for fresnel term. ramp.y for sky blending + float4 ramp = g_texFresnel.Sample(sampler_linear_clamp, cos_angle).xyzw; + // A workaround to deal with "indirect reflection vectors" (which are rays requiring multiple + // reflections to reach the sky). + if (reflect_vec.z < g_BendParam.x) + ramp = lerp(ramp, g_BendParam.z, (g_BendParam.x - reflect_vec.z) / (g_BendParam.x - g_BendParam.y)); + reflect_vec.z = max(0, reflect_vec.z); + + float3 reflection = g_texReflectCube.Sample(sampler_linear_clamp, reflect_vec).xyz; + // Hack bit: making higher contrast + reflection = reflection * reflection * 2.5f; + + // Blend with predefined sky color + float3 reflected_color = lerp(g_SkyColor, reflection, ramp.y); + + // Combine waterbody color and reflected color + float3 water_color = lerp(body_color, reflected_color, ramp.x); + + + // --------------- Sun spots + + float cos_spec = clamp(dot(reflect_vec, g_SunDir), 0, 1); + float sun_spot = pow(cos_spec, g_Shineness); + water_color += g_SunColor * sun_spot; + + + return float4(water_color, 1); +} diff --git a/WickedEngine/oceanSurfaceSimplePS.hlsl b/WickedEngine/oceanSurfaceSimplePS.hlsl new file mode 100644 index 000000000..65d8e119a --- /dev/null +++ b/WickedEngine/oceanSurfaceSimplePS.hlsl @@ -0,0 +1,4 @@ +float4 main() : SV_TARGET +{ + return float4(1.0f, 1.0f, 1.0f, 1.0f); +} diff --git a/WickedEngine/oceanSurfaceVS.hlsl b/WickedEngine/oceanSurfaceVS.hlsl new file mode 100644 index 000000000..4c6c8cb4a --- /dev/null +++ b/WickedEngine/oceanSurfaceVS.hlsl @@ -0,0 +1,46 @@ +#include "globals.hlsli" +#include "oceanSurfaceHF.hlsli" + +VS_OUTPUT main(float2 vPos : POSITION) +{ + VS_OUTPUT Output; + + // Local position + float4 pos_local = mul(float4(vPos, 0, 1), g_matLocal); + // UV + float2 uv_local = pos_local.xy * g_UVScale + g_UVOffset; + + // Blend displacement to avoid tiling artifact + float3 eye_vec = pos_local.xyz - g_LocalEye; + float dist_2d = length(eye_vec.xy); + float blend_factor = (PATCH_BLEND_END - dist_2d) / (PATCH_BLEND_END - PATCH_BLEND_BEGIN); + blend_factor = clamp(blend_factor, 0, 1); + + // Add perlin noise to distant patches + float perlin = 0; + if (blend_factor < 1) + { + float2 perlin_tc = uv_local * g_PerlinSize + g_UVBase; + float perlin_0 = g_texPerlin.SampleLevel(sampler_aniso_wrap, perlin_tc * g_PerlinOctave.x + g_PerlinMovement, 0).w; + float perlin_1 = g_texPerlin.SampleLevel(sampler_aniso_wrap, perlin_tc * g_PerlinOctave.y + g_PerlinMovement, 0).w; + float perlin_2 = g_texPerlin.SampleLevel(sampler_aniso_wrap, perlin_tc * g_PerlinOctave.z + g_PerlinMovement, 0).w; + + perlin = perlin_0 * g_PerlinAmplitude.x + perlin_1 * g_PerlinAmplitude.y + perlin_2 * g_PerlinAmplitude.z; + } + + // Displacement map + float3 displacement = 0; + if (blend_factor > 0) + displacement = g_texDisplacement.SampleLevel(sampler_point_wrap, uv_local, 0).xyz; + displacement = lerp(float3(0, 0, perlin), displacement, blend_factor); + pos_local.xyz += displacement; + + // Transform + Output.Position = mul(pos_local, g_matWorldViewProj); + Output.LocalPos = pos_local.xyz; + + // Pass thru texture coordinate + Output.TexCoord = uv_local; + + return Output; +} diff --git a/WickedEngine/oceanUpdateDisplacementPS.hlsl b/WickedEngine/oceanUpdateDisplacementPS.hlsl index c65aad138..4d0d455b4 100644 --- a/WickedEngine/oceanUpdateDisplacementPS.hlsl +++ b/WickedEngine/oceanUpdateDisplacementPS.hlsl @@ -1,4 +1,4 @@ -#include "oceanHF.hlsli" +#include "oceanWaveGenHF.hlsli" // Post-FFT data wrap up: Dx, Dy, Dz -> Displacement float4 main(VS_QUAD_OUTPUT In) : SV_Target @@ -10,8 +10,8 @@ float4 main(VS_QUAD_OUTPUT In) : SV_Target // cos(pi * (m1 + m2)) int sign_correction = ((index_x + index_y) & 1) ? -1 : 1; - float dx = g_InputDxyz[addr + g_DxAddressOffset].x * sign_correction * g_ChoppyScale; - float dy = g_InputDxyz[addr + g_DyAddressOffset].x * sign_correction * g_ChoppyScale; + float dx = g_InputDxyz[addr + g_DtxAddressOffset].x * sign_correction * g_ChoppyScale; + float dy = g_InputDxyz[addr + g_DtyAddressOffset].x * sign_correction * g_ChoppyScale; float dz = g_InputDxyz[addr].x * sign_correction; return float4(dx, dy, dz, 1); diff --git a/WickedEngine/oceanWaveGenHF.hlsli b/WickedEngine/oceanWaveGenHF.hlsli new file mode 100644 index 000000000..2298cb598 --- /dev/null +++ b/WickedEngine/oceanWaveGenHF.hlsli @@ -0,0 +1,23 @@ +#ifndef _OCEAN_SIMULATOR_HF_ +#define _OCEAN_SIMULATOR_HF_ +#include "globals.hlsli" +#include "ShaderInterop_Ocean.h" + +//---------------------------------------- Vertex Shaders ------------------------------------------ +struct VS_QUAD_OUTPUT +{ + float4 Position : SV_POSITION; // vertex position + float2 TexCoord : TEXCOORD0; // vertex texture coords +}; + +//----------------------------------------- Pixel Shaders ------------------------------------------ + +// Textures and sampling states +#define g_samplerDisplacementMap texture_0 + +SAMPLERSTATE(LinearSampler, SSLOT_ONDEMAND0); + +// The following three should contains only real numbers. But we have only C2C FFT now. +STRUCTUREDBUFFER(g_InputDxyz, float2, TEXSLOT_ONDEMAND0); + +#endif // _OCEAN_SIMULATOR_HF_ diff --git a/WickedEngine/wiFFTGenerator.cpp b/WickedEngine/wiFFTGenerator.cpp index 4bd3717fc..2da42821e 100644 --- a/WickedEngine/wiFFTGenerator.cpp +++ b/WickedEngine/wiFFTGenerator.cpp @@ -1,25 +1,12 @@ -// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. -// -// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED -// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS -// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA -// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR -// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS -// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY -// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, -// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// Please direct any bugs or questions to SDKFeedback@nvidia.com +#include "wiFFTGenerator.h" +#include "wiResourceManager.h" +#include "wiRenderer.h" +#include "ShaderInterop_FFTGenerator.h" #include #include #include -#include "wiFFTGenerator.h" -#include "wiResourceManager.h" -#include "wiRenderer.h" - using namespace wiGraphicsTypes; static const GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE; // todo: make dynamic @@ -37,7 +24,7 @@ void radix008A(CSFFT512x512_Plan* fft_plan, // Buffers GPUResource* cs_srvs[1] = { pSRV_Src }; - device->BindResourcesCS(cs_srvs, 0, 1, threadID); + device->BindResourcesCS(cs_srvs, TEXSLOT_ONDEMAND0, 1, threadID); GPUUnorderedResource* cs_uavs[1] = { pUAV_Dst }; device->BindUnorderedAccessResourcesCS(cs_uavs, 0, 1, threadID); @@ -52,7 +39,7 @@ void radix008A(CSFFT512x512_Plan* fft_plan, device->Dispatch(grid, 1, 1, threadID); // Unbind resource - device->UnBindResources(0, 1, threadID); + device->UnBindResources(TEXSLOT_ONDEMAND0, 1, threadID); device->UnBindUnorderedAccessResources(0, 1, threadID); } @@ -69,32 +56,32 @@ void fft_512x512_c2c(CSFFT512x512_Plan* fft_plan, UINT istride = 512 * 512 / 8; cs_cbs = fft_plan->pRadix008A_CB[0]; - device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); radix008A(fft_plan, pUAV_Tmp, pSRV_Src, thread_count, istride); istride /= 8; cs_cbs = fft_plan->pRadix008A_CB[1]; - device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride); istride /= 8; cs_cbs = fft_plan->pRadix008A_CB[2]; - device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); radix008A(fft_plan, pUAV_Tmp, pSRV_Dst, thread_count, istride); istride /= 8; cs_cbs = fft_plan->pRadix008A_CB[3]; - device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride); istride /= 8; cs_cbs = fft_plan->pRadix008A_CB[4]; - device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); radix008A(fft_plan, pUAV_Tmp, pSRV_Dst, thread_count, istride); istride /= 8; cs_cbs = fft_plan->pRadix008A_CB[5]; - device->BindConstantBufferCS(&cs_cbs[0], 0, threadID); + device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride); } @@ -107,21 +94,21 @@ void create_cbuffers_512x512(CSFFT512x512_Plan* plan, GraphicsDevice* device, UI cb_desc.BindFlags = BIND_CONSTANT_BUFFER; cb_desc.CPUAccessFlags = 0; cb_desc.MiscFlags = 0; - cb_desc.ByteWidth = 32;//sizeof(float) * 5; + cb_desc.ByteWidth = sizeof(FFTGeneratorCB); cb_desc.StructureByteStride = 0; SubresourceData cb_data; cb_data.SysMemPitch = 0; cb_data.SysMemSlicePitch = 0; - struct CB_Structure - { - UINT thread_count; - UINT ostride; - UINT istride; - UINT pstride; - float phase_base; - }; + //struct CB_Structure + //{ + // UINT thread_count; + // UINT ostride; + // UINT istride; + // UINT pstride; + // float phase_base; + //}; for (int i = 0; i < ARRAYSIZE(plan->pRadix008A_CB); ++i) { @@ -134,7 +121,7 @@ void create_cbuffers_512x512(CSFFT512x512_Plan* plan, GraphicsDevice* device, UI UINT istride = ostride; double phase_base = -TWO_PI / (512.0 * 512.0); - CB_Structure cb_data_buf0 = { thread_count, ostride, istride, 512, (float)phase_base }; + FFTGeneratorCB cb_data_buf0 = { thread_count, ostride, istride, 512, (float)phase_base }; cb_data.pSysMem = &cb_data_buf0; device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[0]); @@ -144,7 +131,7 @@ void create_cbuffers_512x512(CSFFT512x512_Plan* plan, GraphicsDevice* device, UI istride /= 8; phase_base *= 8.0; - CB_Structure cb_data_buf1 = { thread_count, ostride, istride, 512, (float)phase_base }; + FFTGeneratorCB cb_data_buf1 = { thread_count, ostride, istride, 512, (float)phase_base }; cb_data.pSysMem = &cb_data_buf1; device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[1]); @@ -154,7 +141,7 @@ void create_cbuffers_512x512(CSFFT512x512_Plan* plan, GraphicsDevice* device, UI istride /= 8; phase_base *= 8.0; - CB_Structure cb_data_buf2 = { thread_count, ostride, istride, 512, (float)phase_base }; + FFTGeneratorCB cb_data_buf2 = { thread_count, ostride, istride, 512, (float)phase_base }; cb_data.pSysMem = &cb_data_buf2; device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[2]); @@ -165,7 +152,7 @@ void create_cbuffers_512x512(CSFFT512x512_Plan* plan, GraphicsDevice* device, UI phase_base *= 8.0; ostride /= 512; - CB_Structure cb_data_buf3 = { thread_count, ostride, istride, 1, (float)phase_base }; + FFTGeneratorCB cb_data_buf3 = { thread_count, ostride, istride, 1, (float)phase_base }; cb_data.pSysMem = &cb_data_buf3; device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[3]); @@ -175,7 +162,7 @@ void create_cbuffers_512x512(CSFFT512x512_Plan* plan, GraphicsDevice* device, UI istride /= 8; phase_base *= 8.0; - CB_Structure cb_data_buf4 = { thread_count, ostride, istride, 1, (float)phase_base }; + FFTGeneratorCB cb_data_buf4 = { thread_count, ostride, istride, 1, (float)phase_base }; cb_data.pSysMem = &cb_data_buf4; device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[4]); @@ -185,7 +172,7 @@ void create_cbuffers_512x512(CSFFT512x512_Plan* plan, GraphicsDevice* device, UI istride /= 8; phase_base *= 8.0; - CB_Structure cb_data_buf5 = { thread_count, ostride, istride, 1, (float)phase_base }; + FFTGeneratorCB cb_data_buf5 = { thread_count, ostride, istride, 1, (float)phase_base }; cb_data.pSysMem = &cb_data_buf5; device->CreateBuffer(&cb_desc, &cb_data, plan->pRadix008A_CB[5]); @@ -216,7 +203,7 @@ void fft512x512_create_plan(CSFFT512x512_Plan* plan, UINT slices) buf_desc.StructureByteStride = sizeof(float) * 2; plan->pBuffer_Tmp = new GPUBuffer; - device->CreateBuffer(&buf_desc, NULL, plan->pBuffer_Tmp); + device->CreateBuffer(&buf_desc, nullptr, plan->pBuffer_Tmp); plan->pSRV_Tmp = (GPUResource*)plan->pBuffer_Tmp; plan->pUAV_Tmp = (GPUUnorderedResource*)plan->pBuffer_Tmp; diff --git a/WickedEngine/wiLoader.h b/WickedEngine/wiLoader.h index bb66519b8..4375da2fe 100644 --- a/WickedEngine/wiLoader.h +++ b/WickedEngine/wiLoader.h @@ -1082,49 +1082,49 @@ struct Camera:public Transform{ XMStoreFloat4x4(&this->InvProjection, InvP); } - XMVECTOR GetEye() + XMVECTOR GetEye() const { return XMLoadFloat3(&translation); } - XMVECTOR GetAt() + XMVECTOR GetAt() const { return XMLoadFloat3(&At); } - XMVECTOR GetUp() + XMVECTOR GetUp() const { return XMLoadFloat3(&Up); } - XMVECTOR GetRight() + XMVECTOR GetRight() const { return XMVector3Cross(GetAt(), GetUp()); } - XMMATRIX GetView() + XMMATRIX GetView() const { return XMLoadFloat4x4(&View); } - XMMATRIX GetInvView() + XMMATRIX GetInvView() const { return XMLoadFloat4x4(&InvView); } - XMMATRIX GetProjection() + XMMATRIX GetProjection() const { return XMLoadFloat4x4(&Projection); } - XMMATRIX GetInvProjection() + XMMATRIX GetInvProjection() const { return XMLoadFloat4x4(&InvProjection); } - XMMATRIX GetViewProjection() + XMMATRIX GetViewProjection() const { return XMLoadFloat4x4(&VP); } - XMMATRIX GetInvViewProjection() + XMMATRIX GetInvViewProjection() const { return XMLoadFloat4x4(&InvVP); } // when the projection matrix is modified for reverse zbuffering, this returns the normal projection - XMMATRIX GetRealProjection() + XMMATRIX GetRealProjection() const { return XMLoadFloat4x4(&realProjection); } diff --git a/WickedEngine/wiOcean.cpp b/WickedEngine/wiOcean.cpp new file mode 100644 index 000000000..5ce5d3e61 --- /dev/null +++ b/WickedEngine/wiOcean.cpp @@ -0,0 +1,1520 @@ +#include "wiOcean.h" +#include "wiRenderer.h" +#include "wiResourceManager.h" +#include "ShaderInterop_Ocean.h" + +using namespace wiGraphicsTypes; +using namespace std; + +static const GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE; // todo: make dynamic + +// Disable warning "conditional expression is constant" +#pragma warning(disable:4127) + + +#define HALF_SQRT_2 0.7071068f +#define GRAV_ACCEL 981.0f // The acceleration of gravity, cm/s^2 + +#define BLOCK_SIZE_X 16 +#define BLOCK_SIZE_Y 16 + +// Generating gaussian random number with mean 0 and standard deviation 1. +float Gauss() +{ + float u1 = rand() / (float)RAND_MAX; + float u2 = rand() / (float)RAND_MAX; + if (u1 < 1e-6f) + u1 = 1e-6f; + return sqrtf(-2 * logf(u1)) * cosf(2 * XM_PI * u2); +} + +// Phillips Spectrum +// K: normalized wave vector, W: wind direction, v: wind velocity, a: amplitude constant +float Phillips(XMFLOAT2 K, XMFLOAT2 W, float v, float a, float dir_depend) +{ + // largest possible wave from constant wind of velocity v + float l = v * v / GRAV_ACCEL; + // damp out waves with very small length w << l + float w = l / 1000; + + float Ksqr = K.x * K.x + K.y * K.y; + float Kcos = K.x * W.x + K.y * W.y; + float phillips = a * expf(-1 / (l * l * Ksqr)) / (Ksqr * Ksqr * Ksqr) * (Kcos * Kcos); + + // filter out waves moving opposite to wind + if (Kcos < 0) + phillips *= dir_depend; + + // damp out waves with very small length w << l + return phillips * expf(-Ksqr * w * w); +} + +void createBufferAndUAV(void* data, UINT byte_width, UINT byte_stride, GPUBuffer** ppBuffer) +{ + *ppBuffer = new GPUBuffer; + + // Create buffer + GPUBufferDesc buf_desc; + buf_desc.ByteWidth = byte_width; + buf_desc.Usage = USAGE_DEFAULT; + buf_desc.BindFlags = BIND_UNORDERED_ACCESS | BIND_SHADER_RESOURCE; + buf_desc.CPUAccessFlags = 0; + buf_desc.MiscFlags = RESOURCE_MISC_BUFFER_STRUCTURED; + buf_desc.StructureByteStride = byte_stride; + + SubresourceData init_data; + init_data.pSysMem = data; + + wiRenderer::GetDevice()->CreateBuffer(&buf_desc, data != NULL ? &init_data : NULL, *ppBuffer); + + + //assert(*ppBuffer); + + //// Create undordered access view + //UNORDERED_ACCESS_VIEW_DESC uav_desc; + //uav_desc.Format = DXGI_FORMAT_UNKNOWN; + //uav_desc.ViewDimension = UAV_DIMENSION_BUFFER; + //uav_desc.Buffer.FirstElement = 0; + //uav_desc.Buffer.NumElements = byte_width / byte_stride; + //uav_desc.Buffer.Flags = 0; + + //device->CreateUnorderedAccessView(*ppBuffer, &uav_desc, ppUAV); + //assert(*ppUAV); + + //// Create shader resource view + //SHADER_RESOURCE_VIEW_DESC srv_desc; + //srv_desc.Format = DXGI_FORMAT_UNKNOWN; + //srv_desc.ViewDimension = SRV_DIMENSION_BUFFER; + //srv_desc.Buffer.FirstElement = 0; + //srv_desc.Buffer.NumElements = byte_width / byte_stride; + + //device->CreateShaderResourceView(*ppBuffer, &srv_desc, ppSRV); + //assert(*ppSRV); +} + +void createTextureAndViews(UINT width, UINT height, FORMAT format, Texture2D** ppTex) +{ + // Create 2D texture + Texture2DDesc tex_desc; + tex_desc.Width = width; + tex_desc.Height = height; + tex_desc.MipLevels = 0; + tex_desc.ArraySize = 1; + tex_desc.Format = format; + tex_desc.SampleDesc.Count = 1; + tex_desc.SampleDesc.Quality = 0; + tex_desc.Usage = USAGE_DEFAULT; + tex_desc.BindFlags = BIND_SHADER_RESOURCE | BIND_RENDER_TARGET; + tex_desc.CPUAccessFlags = 0; + tex_desc.MiscFlags = RESOURCE_MISC_GENERATE_MIPS; + + *ppTex = new Texture2D; + wiRenderer::GetDevice()->CreateTexture2D(&tex_desc, NULL, ppTex); + + + //assert(*ppTex); + + //// Create shader resource view + //(*ppTex)->GetDesc(&tex_desc); + //if (ppSRV) + //{ + // SHADER_RESOURCE_VIEW_DESC srv_desc; + // srv_desc.Format = format; + // srv_desc.ViewDimension = SRV_DIMENSION_TEXTURE2D; + // srv_desc.Texture2D.MipLevels = tex_desc.MipLevels; + // srv_desc.Texture2D.MostDetailedMip = 0; + + // device->CreateShaderResourceView(*ppTex, &srv_desc, ppSRV); + // assert(*ppSRV); + //} + + //// Create render target view + //if (ppRTV) + //{ + // RENDER_TARGET_VIEW_DESC rtv_desc; + // rtv_desc.Format = format; + // rtv_desc.ViewDimension = RTV_DIMENSION_TEXTURE2D; + // rtv_desc.Texture2D.MipSlice = 0; + + // device->CreateRenderTargetView(*ppTex, &rtv_desc, ppRTV); + // assert(*ppRTV); + //} +} + + + +wiOcean::wiOcean(const wiOceanParameter& params) +{ + // Height map H(0) + int height_map_size = (params.dmap_dim + 4) * (params.dmap_dim + 1); + XMFLOAT2* h0_data = new XMFLOAT2[height_map_size * sizeof(XMFLOAT2)]; + float* omega_data = new float[height_map_size * sizeof(float)]; + initHeightMap(h0_data, omega_data); + + m_param = params; + int hmap_dim = params.dmap_dim; + int input_full_size = (hmap_dim + 4) * (hmap_dim + 1); + // This value should be (hmap_dim / 2 + 1) * hmap_dim, but we use full sized buffer here for simplicity. + int input_half_size = hmap_dim * hmap_dim; + int output_size = hmap_dim * hmap_dim; + + // For filling the buffer with zeroes. + char* zero_data = new char[3 * output_size * sizeof(float) * 2]; + memset(zero_data, 0, 3 * output_size * sizeof(float) * 2); + + // RW buffer allocations + // H0 + UINT float2_stride = 2 * sizeof(float); + createBufferAndUAV(h0_data, input_full_size * float2_stride, float2_stride, &m_pBuffer_Float2_H0); + + // Notice: The following 3 buffers should be half sized buffer because of conjugate symmetric input. But + // we use full sized buffers due to the CS4.0 restriction. + + // Put H(t), Dx(t) and Dy(t) into one buffer because CS4.0 allows only 1 UAV at a time + createBufferAndUAV(zero_data, 3 * input_half_size * float2_stride, float2_stride, &m_pBuffer_Float2_Ht); + + // omega + createBufferAndUAV(omega_data, input_full_size * sizeof(float), sizeof(float), &m_pBuffer_Float_Omega); + + // Notice: The following 3 should be real number data. But here we use the complex numbers and C2C FFT + // due to the CS4.0 restriction. + // Put Dz, Dx and Dy into one buffer because CS4.0 allows only 1 UAV at a time + createBufferAndUAV(zero_data, 3 * output_size * float2_stride, float2_stride, &m_pBuffer_Float_Dxyz); + + SAFE_DELETE_ARRAY(zero_data); + SAFE_DELETE_ARRAY(h0_data); + SAFE_DELETE_ARRAY(omega_data); + + // D3D11 Textures + createTextureAndViews(hmap_dim, hmap_dim, FORMAT_R32G32B32A32_FLOAT, &m_pDisplacementMap); + createTextureAndViews(hmap_dim, hmap_dim, FORMAT_R16G16B16A16_FLOAT, &m_pGradientMap); + + // Samplers + SamplerDesc sam_desc; + sam_desc.Filter = FILTER_MIN_MAG_LINEAR_MIP_POINT; + sam_desc.AddressU = TEXTURE_ADDRESS_WRAP; + sam_desc.AddressV = TEXTURE_ADDRESS_WRAP; + sam_desc.AddressW = TEXTURE_ADDRESS_WRAP; + sam_desc.MipLODBias = 0; + sam_desc.MaxAnisotropy = 1; + sam_desc.ComparisonFunc = COMPARISON_NEVER; + sam_desc.BorderColor[0] = 1.0f; + sam_desc.BorderColor[1] = 1.0f; + sam_desc.BorderColor[2] = 1.0f; + sam_desc.BorderColor[3] = 1.0f; + sam_desc.MinLOD = -FLT_MAX; + sam_desc.MaxLOD = FLT_MAX; + wiRenderer::GetDevice()->CreateSamplerState(&sam_desc, &m_pPointSamplerState); + + + m_pUpdateSpectrumCS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSimulatorCS.cso", wiResourceManager::COMPUTESHADER)); + + { + VertexLayoutDesc layout[] = + { + { "POSITION", 0, FORMAT_R32G32B32A32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, + }; + UINT numElements = ARRAYSIZE(layout); + VertexShaderInfo* vsinfo = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanQuadVS.cso", wiResourceManager::VERTEXSHADER, layout, numElements)); + if (vsinfo != nullptr) { + m_pQuadVS = vsinfo->vertexShader; + m_pQuadLayout = vsinfo->vertexLayout; + } + } + + m_pUpdateDisplacementPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanUpdateDisplacementPS.cso", wiResourceManager::PIXELSHADER)); + m_pGenGradientFoldingPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanGradientFoldingPS.cso", wiResourceManager::PIXELSHADER)); + + + + //// Compute shaders + //ID3DBlob* pBlobUpdateSpectrumCS = NULL; + + //CompileShaderFromFile(L"ocean_simulator_cs.hlsl", "UpdateSpectrumCS", "cs_4_0", &pBlobUpdateSpectrumCS); + //assert(pBlobUpdateSpectrumCS); + + //m_device->CreateComputeShader(pBlobUpdateSpectrumCS->GetBufferPointer(), pBlobUpdateSpectrumCS->GetBufferSize(), NULL, &m_pUpdateSpectrumCS); + //assert(m_pUpdateSpectrumCS); + + //SAFE_DELETE(pBlobUpdateSpectrumCS); + + //// Vertex & pixel shaders + //ID3DBlob* pBlobQuadVS = NULL; + //ID3DBlob* pBlobUpdateDisplacementPS = NULL; + //ID3DBlob* pBlobGenGradientFoldingPS = NULL; + + //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "QuadVS", "vs_4_0", &pBlobQuadVS); + //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "UpdateDisplacementPS", "ps_4_0", &pBlobUpdateDisplacementPS); + //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "GenGradientFoldingPS", "ps_4_0", &pBlobGenGradientFoldingPS); + //assert(pBlobQuadVS); + //assert(pBlobUpdateDisplacementPS); + //assert(pBlobGenGradientFoldingPS); + + //m_device->CreateVertexShader(pBlobQuadVS->GetBufferPointer(), pBlobQuadVS->GetBufferSize(), NULL, &m_pQuadVS); + //m_device->CreatePixelShader(pBlobUpdateDisplacementPS->GetBufferPointer(), pBlobUpdateDisplacementPS->GetBufferSize(), NULL, &m_pUpdateDisplacementPS); + //m_device->CreatePixelShader(pBlobGenGradientFoldingPS->GetBufferPointer(), pBlobGenGradientFoldingPS->GetBufferSize(), NULL, &m_pGenGradientFoldingPS); + //assert(m_pQuadVS); + //assert(m_pUpdateDisplacementPS); + //assert(m_pGenGradientFoldingPS); + //SAFE_DELETE(pBlobUpdateDisplacementPS); + //SAFE_DELETE(pBlobGenGradientFoldingPS); + + //// Input layout + //INPUT_ELEMENT_DESC quad_layout_desc[] = + //{ + // { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, + //}; + //m_device->CreateInputLayout(quad_layout_desc, 1, pBlobQuadVS->GetBufferPointer(), pBlobQuadVS->GetBufferSize(), &m_pQuadLayout); + //assert(m_pQuadLayout); + + //SAFE_DELETE(pBlobQuadVS); + + // Quad vertex buffer + GPUBufferDesc vb_desc; + vb_desc.ByteWidth = 4 * sizeof(XMFLOAT4); + vb_desc.Usage = USAGE_IMMUTABLE; + vb_desc.BindFlags = BIND_VERTEX_BUFFER; + vb_desc.CPUAccessFlags = 0; + vb_desc.MiscFlags = 0; + + float quad_verts[] = + { + -1, -1, 0, 1, + -1, 1, 0, 1, + 1, -1, 0, 1, + 1, 1, 0, 1, + }; + SubresourceData init_data; + init_data.pSysMem = &quad_verts[0]; + init_data.SysMemPitch = 0; + init_data.SysMemSlicePitch = 0; + + m_pQuadVB = new GPUBuffer; + wiRenderer::GetDevice()->CreateBuffer(&vb_desc, &init_data, m_pQuadVB); + + // Constant buffers + UINT actual_dim = m_param.dmap_dim; + UINT input_width = actual_dim + 4; + // We use full sized data here. The value "output_width" should be actual_dim/2+1 though. + UINT output_width = actual_dim; + UINT output_height = actual_dim; + UINT dtx_offset = actual_dim * actual_dim; + UINT dty_offset = actual_dim * actual_dim * 2; + Ocean_Simulation_ImmutableCB immutable_consts = { actual_dim, input_width, output_width, output_height, dtx_offset, dty_offset }; + SubresourceData init_cb0; + init_cb0.pSysMem = &immutable_consts; + + GPUBufferDesc cb_desc; + cb_desc.Usage = USAGE_IMMUTABLE; + cb_desc.BindFlags = BIND_CONSTANT_BUFFER; + cb_desc.CPUAccessFlags = 0; + cb_desc.MiscFlags = 0; + cb_desc.ByteWidth = sizeof(Ocean_Simulation_ImmutableCB); + m_pImmutableCB = new GPUBuffer; + wiRenderer::GetDevice()->CreateBuffer(&cb_desc, &init_cb0, m_pImmutableCB); + + cb_desc.Usage = USAGE_DYNAMIC; + cb_desc.BindFlags = BIND_CONSTANT_BUFFER; + cb_desc.CPUAccessFlags = CPU_ACCESS_WRITE; + cb_desc.MiscFlags = 0; + cb_desc.ByteWidth = sizeof(Ocean_Simulation_PerFrameCB); + m_pPerFrameCB = new GPUBuffer; + wiRenderer::GetDevice()->CreateBuffer(&cb_desc, nullptr, m_pPerFrameCB); + + // FFT + fft512x512_create_plan(&m_fft_plan, 3); + +#ifdef CS_DEBUG_BUFFER + GPUBufferDesc buf_desc; + buf_desc.ByteWidth = 3 * input_half_size * float2_stride; + buf_desc.Usage = USAGE_STAGING; + buf_desc.BindFlags = 0; + buf_desc.CPUAccessFlags = CPU_ACCESS_READ; + buf_desc.MiscFlags = RESOURCE_MISC_BUFFER_STRUCTURED; + buf_desc.StructureByteStride = float2_stride; + + m_pDebugBuffer = new GPUBuffer; + wiRenderer::GetDevice()->CreateBuffer(&buf_desc, NULL, m_pDebugBuffer); +#endif + + initRenderResource(); + createSurfaceMesh(); + createFresnelMap(); + loadTextures(); +} + +wiOcean::~wiOcean() +{ + fft512x512_destroy_plan(&m_fft_plan); + + SAFE_DELETE(m_pBuffer_Float2_H0); + SAFE_DELETE(m_pBuffer_Float_Omega); + SAFE_DELETE(m_pBuffer_Float2_Ht); + SAFE_DELETE(m_pBuffer_Float_Dxyz); + + SAFE_DELETE(m_pQuadVB); + + SAFE_DELETE(m_pDisplacementMap); + SAFE_DELETE(m_pGradientMap); + + SAFE_DELETE(m_pUpdateSpectrumCS); + SAFE_DELETE(m_pQuadVS); + SAFE_DELETE(m_pUpdateDisplacementPS); + SAFE_DELETE(m_pGenGradientFoldingPS); + + SAFE_DELETE(m_pQuadLayout); + + SAFE_DELETE(m_pImmutableCB); + SAFE_DELETE(m_pPerFrameCB); + +#ifdef CS_DEBUG_BUFFER + SAFE_DELETE(m_pDebugBuffer); +#endif + + cleanupRenderResource(); +} + + + + +// Simulation functions: + + + +// Initialize the vector field. +// wlen_x: width of wave tile, in meters +// wlen_y: length of wave tile, in meters +void wiOcean::initHeightMap(XMFLOAT2* out_h0, float* out_omega) +{ + int i, j; + XMFLOAT2 K; + + XMFLOAT2 wind_dir; + XMStoreFloat2(&wind_dir, XMVector2Normalize(XMLoadFloat2(&m_param.wind_dir))); + float a = m_param.wave_amplitude * 1e-7f; // It is too small. We must scale it for editing. + float v = m_param.wind_speed; + float dir_depend = m_param.wind_dependency; + + int height_map_dim = m_param.dmap_dim; + float patch_length = m_param.patch_length; + + // initialize random generator. + srand(0); + + for (i = 0; i <= height_map_dim; i++) + { + // K is wave-vector, range [-|DX/W, |DX/W], [-|DY/H, |DY/H] + K.y = (-height_map_dim / 2.0f + i) * (2 * XM_PI / patch_length); + + for (j = 0; j <= height_map_dim; j++) + { + K.x = (-height_map_dim / 2.0f + j) * (2 * XM_PI / patch_length); + + float phil = (K.x == 0 && K.y == 0) ? 0 : sqrtf(Phillips(K, wind_dir, v, a, dir_depend)); + + out_h0[i * (height_map_dim + 4) + j].x = float(phil * Gauss() * HALF_SQRT_2); + out_h0[i * (height_map_dim + 4) + j].y = float(phil * Gauss() * HALF_SQRT_2); + + // The angular frequency is following the dispersion relation: + // out_omega^2 = g*k + // The equation of Gerstner wave: + // x = x0 - K/k * A * sin(dot(K, x0) - sqrt(g * k) * t), x is a 2D vector. + // z = A * cos(dot(K, x0) - sqrt(g * k) * t) + // Gerstner wave shows that a point on a simple sinusoid wave is doing a uniform circular + // motion with the center (x0, y0, z0), radius A, and the circular plane is parallel to + // vector K. + out_omega[i * (height_map_dim + 4) + j] = sqrtf(GRAV_ACCEL * sqrtf(K.x * K.x + K.y * K.y)); + } + } +} + +void wiOcean::updateDisplacementMap(float time) +{ + GraphicsDevice* device = wiRenderer::GetDevice(); + + device->EventBegin("OceanSimulator", threadID); + + // ---------------------------- H(0) -> H(t), D(x, t), D(y, t) -------------------------------- + // Compute shader + device->BindCS(m_pUpdateSpectrumCS, threadID); + + // Buffers + GPUResource* cs0_srvs[2] = { + m_pBuffer_Float2_H0, + m_pBuffer_Float_Omega + }; + device->BindResourcesCS(cs0_srvs, TEXSLOT_ONDEMAND0, 2, threadID); + + GPUUnorderedResource* cs0_uavs[1] = { m_pBuffer_Float2_Ht }; + device->BindUnorderedAccessResourcesCS(cs0_uavs, 0, 1, threadID); + + Ocean_Simulation_PerFrameCB perFrameData; + perFrameData.g_Time = time * m_param.time_scale; + perFrameData.g_ChoppyScale = m_param.choppy_scale; + perFrameData.g_GridLen = m_param.dmap_dim / m_param.patch_length; + device->UpdateBuffer(m_pPerFrameCB, &perFrameData, threadID); + + device->BindConstantBufferCS(m_pImmutableCB, CB_GETBINDSLOT(Ocean_Simulation_ImmutableCB), threadID); + device->BindConstantBufferCS(m_pPerFrameCB, CB_GETBINDSLOT(Ocean_Simulation_PerFrameCB), threadID); + + // Run the CS + UINT group_count_x = (m_param.dmap_dim + BLOCK_SIZE_X - 1) / BLOCK_SIZE_X; + UINT group_count_y = (m_param.dmap_dim + BLOCK_SIZE_Y - 1) / BLOCK_SIZE_Y; + device->Dispatch(group_count_x, group_count_y, 1, threadID); + + //// Unbind resources for CS + //cs0_uavs[0] = NULL; + //m_pd3dImmediateContext->CSSetUnorderedAccessViews(0, 1, cs0_uavs, (UINT*)(&cs0_uavs[0])); + //cs0_srvs[0] = NULL; + //cs0_srvs[1] = NULL; + //m_pd3dImmediateContext->CSSetShaderResources(0, 2, cs0_srvs); + + device->UnBindUnorderedAccessResources(0, 1, threadID); + device->UnBindResources(TEXSLOT_ONDEMAND0, 2, threadID); + + + // ------------------------------------ Perform FFT ------------------------------------------- + fft_512x512_c2c(&m_fft_plan, m_pBuffer_Float_Dxyz, m_pBuffer_Float_Dxyz, m_pBuffer_Float2_Ht); + + // --------------------------------- Wrap Dx, Dy and Dz --------------------------------------- + // Push RT + //ID3D11RenderTargetView* old_target; + //ID3D11DepthStencilView* old_depth; + //m_pd3dImmediateContext->OMGetRenderTargets(1, &old_target, &old_depth); + //VIEWPORT old_viewport; + //UINT num_viewport = 1; + //m_pd3dImmediateContext->RSGetViewports(&num_viewport, &old_viewport); + + ViewPort new_vp; + new_vp.TopLeftX = 0; + new_vp.TopLeftX = 0; + new_vp.Width = (float)m_param.dmap_dim; + new_vp.Height = (float)m_param.dmap_dim; + new_vp.MinDepth = 0.0f; + new_vp.MaxDepth = 1.0f; + device->BindViewports(1, &new_vp, threadID); + + // Set RT + device->BindRenderTargets(1, (Texture**)&m_pDisplacementMap, nullptr, threadID); + + // VS & PS + device->BindVS(m_pQuadVS, threadID); + device->BindPS(m_pUpdateDisplacementPS, threadID); + + device->BindConstantBufferPS(m_pImmutableCB, CB_GETBINDSLOT(Ocean_Simulation_ImmutableCB), threadID); + device->BindConstantBufferPS(m_pPerFrameCB, CB_GETBINDSLOT(Ocean_Simulation_PerFrameCB), threadID); + + // Buffer resources + GPUResource* ps_srvs[1] = { m_pBuffer_Float_Dxyz }; + device->BindResourcesPS(ps_srvs, TEXSLOT_ONDEMAND0, 1, threadID); + + // IA setup + GPUBuffer* vbs[1] = { m_pQuadVB }; + UINT strides[1] = { sizeof(XMFLOAT4) }; + UINT offsets[1] = { 0 }; + device->BindVertexBuffers(&vbs[0], 0, 1, &strides[0], &offsets[0], threadID); + + device->BindVertexLayout(m_pQuadLayout, threadID); + device->BindPrimitiveTopology(TRIANGLESTRIP, threadID); + + // Perform draw call + device->Draw(4, 0, threadID); + + // Unbind + device->UnBindResources(TEXSLOT_ONDEMAND0, 1, threadID); + + + // ----------------------------------- Generate Normal ---------------------------------------- + // Set RT + device->BindRenderTargets(1, (Texture**)&m_pGradientMap, nullptr, threadID); + + // VS & PS + device->BindVS(m_pQuadVS, threadID); + device->BindPS(m_pGenGradientFoldingPS, threadID); + + // Texture resource and sampler + ps_srvs[0] = m_pDisplacementMap; + device->BindResourcesPS(ps_srvs, TEXSLOT_ONDEMAND0, 1, threadID); + + device->BindSamplerPS(&m_pPointSamplerState, SSLOT_ONDEMAND0, threadID); + + // Perform draw call + device->Draw(4, 0, threadID); + + // Unbind + device->UnBindResources(TEXSLOT_ONDEMAND0, 1, threadID); + + //// Pop RT + //m_pd3dImmediateContext->RSSetViewports(1, &old_viewport); + //m_pd3dImmediateContext->OMSetRenderTargets(1, &old_target, old_depth); + //SAFE_DELETE(old_target); + //SAFE_DELETE(old_depth); + + device->GenerateMips(m_pGradientMap, threadID); + + // Define CS_DEBUG_BUFFER to enable writing a buffer into a file. +#ifdef CS_DEBUG_BUFFER + { + m_pd3dImmediateContext->CopyResource(m_pDebugBuffer, m_pBuffer_Float_Dxyz); + MAPPED_SUBRESOURCE mapped_res; + m_pd3dImmediateContext->Map(m_pDebugBuffer, 0, MAP_READ, 0, &mapped_res); + + // set a break point below, and drag MappedResource.pData into in your Watch window + // and cast it as (float*) + + // Write to disk + XMFLOAT2* v = (XMFLOAT2*)mapped_res.pData; + + FILE* fp = fopen(".\\tmp\\Ht_raw.dat", "wb"); + fwrite(v, 512 * 512 * sizeof(float) * 2 * 3, 1, fp); + fclose(fp); + + m_pd3dImmediateContext->Unmap(m_pDebugBuffer, 0); + } +#endif + + device->EventEnd(threadID); +} + +Texture2D* wiOcean::getDisplacementMap() +{ + return m_pDisplacementMap; +} + +Texture2D* wiOcean::getGradientMap() +{ + return m_pGradientMap; +} + + +const wiOceanParameter& wiOcean::getParameters() +{ + return m_param; +} + + + + + + +// Rendering functions: + + +void wiOcean::initRenderResource() +{ + GraphicsDevice* device = wiRenderer::GetDevice(); + wiOceanParameter ocean_param = m_param; + + g_PatchLength = ocean_param.patch_length; + g_DisplaceMapDim = ocean_param.dmap_dim; + g_WindDir = ocean_param.wind_dir; + + // D3D buffers + createSurfaceMesh(); + createFresnelMap(); + loadTextures(); + + { + VertexLayoutDesc layout[] = + { + { "POSITION", 0, FORMAT_R32G32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, + }; + UINT numElements = ARRAYSIZE(layout); + VertexShaderInfo* vsinfo = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSurfaceVS.cso", wiResourceManager::VERTEXSHADER, layout, numElements)); + if (vsinfo != nullptr) { + g_pOceanSurfVS = vsinfo->vertexShader; + g_pMeshLayout = vsinfo->vertexLayout; + } + } + + g_pOceanSurfPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSurfacePS.cso", wiResourceManager::PIXELSHADER)); + g_pWireframePS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSurfaceSimplePS.cso", wiResourceManager::PIXELSHADER)); + + + // Constants + GPUBufferDesc cb_desc; + cb_desc.Usage = USAGE_DYNAMIC; + cb_desc.BindFlags = BIND_CONSTANT_BUFFER; + cb_desc.CPUAccessFlags = CPU_ACCESS_WRITE; + cb_desc.MiscFlags = 0; + cb_desc.ByteWidth = sizeof(Ocean_Rendering_PatchCB); + cb_desc.StructureByteStride = 0; + g_pPerCallCB = new GPUBuffer; + device->CreateBuffer(&cb_desc, nullptr, g_pPerCallCB); + + Ocean_Rendering_ShadingCB shading_data; + // Grid side length * 2 + shading_data.g_TexelLength_x2 = g_PatchLength / g_DisplaceMapDim * 2;; + // Color + shading_data.g_SkyColor = g_SkyColor; + shading_data.g_WaterbodyColor = g_WaterbodyColor; + // Texcoord + shading_data.g_UVScale = 1.0f / g_PatchLength; + shading_data.g_UVOffset = 0.5f / g_DisplaceMapDim; + // Perlin + shading_data.g_PerlinSize = g_PerlinSize; + shading_data.g_PerlinAmplitude = g_PerlinAmplitude; + shading_data.g_PerlinGradient = g_PerlinGradient; + shading_data.g_PerlinOctave = g_PerlinOctave; + // Multiple reflection workaround + shading_data.g_BendParam = g_BendParam; + // Sun streaks + shading_data.g_SunColor = g_SunColor; + shading_data.g_SunDir = g_SunDir; + shading_data.g_Shineness = g_Shineness; + + SubresourceData cb_init_data; + cb_init_data.pSysMem = &shading_data; + cb_init_data.SysMemPitch = 0; + cb_init_data.SysMemSlicePitch = 0; + + cb_desc.Usage = USAGE_IMMUTABLE; + cb_desc.CPUAccessFlags = 0; + cb_desc.ByteWidth = sizeof(Ocean_Rendering_ShadingCB); + cb_desc.StructureByteStride = 0; + g_pShadingCB = new GPUBuffer; + device->CreateBuffer(&cb_desc, &cb_init_data, g_pShadingCB); + + // State blocks + RasterizerStateDesc ras_desc; + ras_desc.FillMode = FILL_SOLID; + ras_desc.CullMode = CULL_NONE; + ras_desc.FrontCounterClockwise = FALSE; + ras_desc.DepthBias = 0; + ras_desc.SlopeScaledDepthBias = 0.0f; + ras_desc.DepthBiasClamp = 0.0f; + ras_desc.DepthClipEnable = TRUE; + ras_desc.ScissorEnable = FALSE; + ras_desc.MultisampleEnable = TRUE; + ras_desc.AntialiasedLineEnable = FALSE; + + g_pRSState_Solid = new RasterizerState; + device->CreateRasterizerState(&ras_desc, g_pRSState_Solid); + + ras_desc.FillMode = FILL_WIREFRAME; + + g_pRSState_Wireframe = new RasterizerState; + device->CreateRasterizerState(&ras_desc, g_pRSState_Wireframe); + + DepthStencilStateDesc depth_desc; + memset(&depth_desc, 0, sizeof(DepthStencilStateDesc)); + depth_desc.DepthEnable = FALSE; + depth_desc.StencilEnable = FALSE; + g_pDSState_Disable = new DepthStencilState; + device->CreateDepthStencilState(&depth_desc, g_pDSState_Disable); + + BlendStateDesc blend_desc; + memset(&blend_desc, 0, sizeof(BlendStateDesc)); + blend_desc.AlphaToCoverageEnable = FALSE; + blend_desc.IndependentBlendEnable = FALSE; + blend_desc.RenderTarget[0].BlendEnable = TRUE; + blend_desc.RenderTarget[0].SrcBlend = BLEND_SRC_ALPHA; + blend_desc.RenderTarget[0].DestBlend = BLEND_INV_SRC_ALPHA; + blend_desc.RenderTarget[0].BlendOp = BLEND_OP_ADD; + blend_desc.RenderTarget[0].SrcBlendAlpha = BLEND_ONE; + blend_desc.RenderTarget[0].DestBlendAlpha = BLEND_ZERO; + blend_desc.RenderTarget[0].BlendOpAlpha = BLEND_OP_ADD; + blend_desc.RenderTarget[0].RenderTargetWriteMask = COLOR_WRITE_ENABLE_ALL; + g_pBState_Transparent = new BlendState; + device->CreateBlendState(&blend_desc, g_pBState_Transparent); +} + +void wiOcean::cleanupRenderResource() +{ + SAFE_DELETE(g_pMeshIB); + SAFE_DELETE(g_pMeshVB); + SAFE_DELETE(g_pMeshLayout); + + SAFE_DELETE(g_pOceanSurfVS); + SAFE_DELETE(g_pOceanSurfPS); + SAFE_DELETE(g_pWireframePS); + + SAFE_DELETE(g_pFresnelMap); + SAFE_DELETE(g_pPerlinMap); + + SAFE_DELETE(g_pPerCallCB); + SAFE_DELETE(g_pPerFrameCB); + SAFE_DELETE(g_pShadingCB); + + SAFE_DELETE(g_pRSState_Solid); + SAFE_DELETE(g_pRSState_Wireframe); + SAFE_DELETE(g_pDSState_Disable); + SAFE_DELETE(g_pBState_Transparent); + + g_render_list.clear(); +} + +#define MESH_INDEX_2D(x, y) (((y) + vert_rect.bottom) * (g_MeshDim + 1) + (x) + vert_rect.left) + +// Generate boundary mesh for a patch. Return the number of generated indices +int wiOcean::generateBoundaryMesh(int left_degree, int right_degree, int bottom_degree, int top_degree, + RECT vert_rect, DWORD* output) +{ + // Triangle list for bottom boundary + int i, j; + int counter = 0; + int width = vert_rect.right - vert_rect.left; + + if (bottom_degree > 0) + { + int b_step = width / bottom_degree; + + for (i = 0; i < width; i += b_step) + { + output[counter++] = MESH_INDEX_2D(i, 0); + output[counter++] = MESH_INDEX_2D(i + b_step / 2, 1); + output[counter++] = MESH_INDEX_2D(i + b_step, 0); + + for (j = 0; j < b_step / 2; j++) + { + if (i == 0 && j == 0 && left_degree > 0) + continue; + + output[counter++] = MESH_INDEX_2D(i, 0); + output[counter++] = MESH_INDEX_2D(i + j, 1); + output[counter++] = MESH_INDEX_2D(i + j + 1, 1); + } + + for (j = b_step / 2; j < b_step; j++) + { + if (i == width - b_step && j == b_step - 1 && right_degree > 0) + continue; + + output[counter++] = MESH_INDEX_2D(i + b_step, 0); + output[counter++] = MESH_INDEX_2D(i + j, 1); + output[counter++] = MESH_INDEX_2D(i + j + 1, 1); + } + } + } + + // Right boundary + int height = vert_rect.top - vert_rect.bottom; + + if (right_degree > 0) + { + int r_step = height / right_degree; + + for (i = 0; i < height; i += r_step) + { + output[counter++] = MESH_INDEX_2D(width, i); + output[counter++] = MESH_INDEX_2D(width - 1, i + r_step / 2); + output[counter++] = MESH_INDEX_2D(width, i + r_step); + + for (j = 0; j < r_step / 2; j++) + { + if (i == 0 && j == 0 && bottom_degree > 0) + continue; + + output[counter++] = MESH_INDEX_2D(width, i); + output[counter++] = MESH_INDEX_2D(width - 1, i + j); + output[counter++] = MESH_INDEX_2D(width - 1, i + j + 1); + } + + for (j = r_step / 2; j < r_step; j++) + { + if (i == height - r_step && j == r_step - 1 && top_degree > 0) + continue; + + output[counter++] = MESH_INDEX_2D(width, i + r_step); + output[counter++] = MESH_INDEX_2D(width - 1, i + j); + output[counter++] = MESH_INDEX_2D(width - 1, i + j + 1); + } + } + } + + // Top boundary + if (top_degree > 0) + { + int t_step = width / top_degree; + + for (i = 0; i < width; i += t_step) + { + output[counter++] = MESH_INDEX_2D(i, height); + output[counter++] = MESH_INDEX_2D(i + t_step / 2, height - 1); + output[counter++] = MESH_INDEX_2D(i + t_step, height); + + for (j = 0; j < t_step / 2; j++) + { + if (i == 0 && j == 0 && left_degree > 0) + continue; + + output[counter++] = MESH_INDEX_2D(i, height); + output[counter++] = MESH_INDEX_2D(i + j, height - 1); + output[counter++] = MESH_INDEX_2D(i + j + 1, height - 1); + } + + for (j = t_step / 2; j < t_step; j++) + { + if (i == width - t_step && j == t_step - 1 && right_degree > 0) + continue; + + output[counter++] = MESH_INDEX_2D(i + t_step, height); + output[counter++] = MESH_INDEX_2D(i + j, height - 1); + output[counter++] = MESH_INDEX_2D(i + j + 1, height - 1); + } + } + } + + // Left boundary + if (left_degree > 0) + { + int l_step = height / left_degree; + + for (i = 0; i < height; i += l_step) + { + output[counter++] = MESH_INDEX_2D(0, i); + output[counter++] = MESH_INDEX_2D(1, i + l_step / 2); + output[counter++] = MESH_INDEX_2D(0, i + l_step); + + for (j = 0; j < l_step / 2; j++) + { + if (i == 0 && j == 0 && bottom_degree > 0) + continue; + + output[counter++] = MESH_INDEX_2D(0, i); + output[counter++] = MESH_INDEX_2D(1, i + j); + output[counter++] = MESH_INDEX_2D(1, i + j + 1); + } + + for (j = l_step / 2; j < l_step; j++) + { + if (i == height - l_step && j == l_step - 1 && top_degree > 0) + continue; + + output[counter++] = MESH_INDEX_2D(0, i + l_step); + output[counter++] = MESH_INDEX_2D(1, i + j); + output[counter++] = MESH_INDEX_2D(1, i + j + 1); + } + } + } + + return counter; +} + +// Generate boundary mesh for a patch. Return the number of generated indices +int wiOcean::generateInnerMesh(RECT vert_rect, DWORD* output) +{ + int i, j; + int counter = 0; + int width = vert_rect.right - vert_rect.left; + int height = vert_rect.top - vert_rect.bottom; + + bool reverse = false; + for (i = 0; i < height; i++) + { + if (reverse == false) + { + output[counter++] = MESH_INDEX_2D(0, i); + output[counter++] = MESH_INDEX_2D(0, i + 1); + for (j = 0; j < width; j++) + { + output[counter++] = MESH_INDEX_2D(j + 1, i); + output[counter++] = MESH_INDEX_2D(j + 1, i + 1); + } + } + else + { + output[counter++] = MESH_INDEX_2D(width, i); + output[counter++] = MESH_INDEX_2D(width, i + 1); + for (j = width - 1; j >= 0; j--) + { + output[counter++] = MESH_INDEX_2D(j, i); + output[counter++] = MESH_INDEX_2D(j, i + 1); + } + } + + reverse = !reverse; + } + + return counter; +} + +void wiOcean::createSurfaceMesh() +{ + GraphicsDevice* device = wiRenderer::GetDevice(); + + // --------------------------------- Vertex Buffer ------------------------------- + int num_verts = (g_MeshDim + 1) * (g_MeshDim + 1); + ocean_vertex* pV = new ocean_vertex[num_verts]; + assert(pV); + + int i, j; + for (i = 0; i <= g_MeshDim; i++) + { + for (j = 0; j <= g_MeshDim; j++) + { + pV[i * (g_MeshDim + 1) + j].index_x = (float)j; + pV[i * (g_MeshDim + 1) + j].index_y = (float)i; + } + } + + GPUBufferDesc vb_desc; + vb_desc.ByteWidth = num_verts * sizeof(ocean_vertex); + vb_desc.Usage = USAGE_IMMUTABLE; + vb_desc.BindFlags = BIND_VERTEX_BUFFER; + vb_desc.CPUAccessFlags = 0; + vb_desc.MiscFlags = 0; + vb_desc.StructureByteStride = sizeof(ocean_vertex); + + SubresourceData init_data; + init_data.pSysMem = pV; + init_data.SysMemPitch = 0; + init_data.SysMemSlicePitch = 0; + + g_pMeshVB = new GPUBuffer; + device->CreateBuffer(&vb_desc, &init_data, g_pMeshVB); + + SAFE_DELETE_ARRAY(pV); + + + // --------------------------------- Index Buffer ------------------------------- + // The index numbers for all mesh LODs (up to 256x256) + const int index_size_lookup[] = { 0, 0, 4284, 18828, 69444, 254412, 956916, 3689820, 14464836 }; + + memset(&g_mesh_patterns[0][0][0][0][0], 0, sizeof(g_mesh_patterns)); + + g_Lods = 0; + for (i = g_MeshDim; i > 1; i >>= 1) + g_Lods++; + + // Generate patch meshes. Each patch contains two parts: the inner mesh which is a regular + // grids in a triangle strip. The boundary mesh is constructed w.r.t. the edge degrees to + // meet water-tight requirement. + DWORD* index_array = new DWORD[index_size_lookup[g_Lods]]; + assert(index_array); + + int offset = 0; + int level_size = g_MeshDim; + + // Enumerate patterns + for (int level = 0; level <= g_Lods - 2; level++) + { + int left_degree = level_size; + + for (int left_type = 0; left_type < 3; left_type++) + { + int right_degree = level_size; + + for (int right_type = 0; right_type < 3; right_type++) + { + int bottom_degree = level_size; + + for (int bottom_type = 0; bottom_type < 3; bottom_type++) + { + int top_degree = level_size; + + for (int top_type = 0; top_type < 3; top_type++) + { + QuadRenderParam* pattern = &g_mesh_patterns[level][left_type][right_type][bottom_type][top_type]; + + // Inner mesh (triangle strip) + RECT inner_rect; + inner_rect.left = (left_degree == level_size) ? 0 : 1; + inner_rect.right = (right_degree == level_size) ? level_size : level_size - 1; + inner_rect.bottom = (bottom_degree == level_size) ? 0 : 1; + inner_rect.top = (top_degree == level_size) ? level_size : level_size - 1; + + int num_new_indices = generateInnerMesh(inner_rect, index_array + offset); + + pattern->inner_start_index = offset; + pattern->num_inner_verts = (level_size + 1) * (level_size + 1); + pattern->num_inner_faces = num_new_indices - 2; + offset += num_new_indices; + + // Boundary mesh (triangle list) + int l_degree = (left_degree == level_size) ? 0 : left_degree; + int r_degree = (right_degree == level_size) ? 0 : right_degree; + int b_degree = (bottom_degree == level_size) ? 0 : bottom_degree; + int t_degree = (top_degree == level_size) ? 0 : top_degree; + + RECT outer_rect = { 0, level_size, level_size, 0 }; + num_new_indices = generateBoundaryMesh(l_degree, r_degree, b_degree, t_degree, outer_rect, index_array + offset); + + pattern->boundary_start_index = offset; + pattern->num_boundary_verts = (level_size + 1) * (level_size + 1); + pattern->num_boundary_faces = num_new_indices / 3; + offset += num_new_indices; + + top_degree /= 2; + } + bottom_degree /= 2; + } + right_degree /= 2; + } + left_degree /= 2; + } + level_size /= 2; + } + + assert(offset == index_size_lookup[g_Lods]); + + GPUBufferDesc ib_desc; + ib_desc.ByteWidth = index_size_lookup[g_Lods] * sizeof(DWORD); + ib_desc.Usage = USAGE_IMMUTABLE; + ib_desc.BindFlags = BIND_INDEX_BUFFER; + ib_desc.CPUAccessFlags = 0; + ib_desc.MiscFlags = 0; + ib_desc.StructureByteStride = sizeof(DWORD); + + init_data.pSysMem = index_array; + + g_pMeshIB = new GPUBuffer; + device->CreateBuffer(&ib_desc, &init_data, g_pMeshIB); + + SAFE_DELETE_ARRAY(index_array); +} + +void wiOcean::createFresnelMap() +{ + static const int FRESNEL_TEX_SIZE = 256; + static const float g_SkyBlending = 16.0f; + + uint32_t* buffer = new uint32_t[FRESNEL_TEX_SIZE]; + for (int i = 0; i < FRESNEL_TEX_SIZE; i++) + { + float cos_a = i / (FLOAT)FRESNEL_TEX_SIZE; + // Using water's refraction index 1.33 + uint32_t fresnel = (uint32_t)(XMVectorGetX(XMFresnelTerm(XMVectorSet(cos_a, cos_a, cos_a, cos_a), XMVectorSet(1.33f, 1.33f, 1.33f, 1.33f))) * 255); + + uint32_t sky_blend = (uint32_t)(powf(1 / (1 + cos_a), g_SkyBlending) * 255); + + buffer[i] = (sky_blend << 8) | fresnel; + } + + + + Texture1DDesc tex_desc; + tex_desc.Width = FRESNEL_TEX_SIZE; + tex_desc.MipLevels = 1; + tex_desc.ArraySize = 1; + tex_desc.Format = FORMAT_R8G8B8A8_UNORM; + tex_desc.Usage = USAGE_IMMUTABLE; + tex_desc.BindFlags = BIND_SHADER_RESOURCE; + tex_desc.CPUAccessFlags = 0; + tex_desc.MiscFlags = 0; + + SubresourceData init_data; + init_data.pSysMem = buffer; + init_data.SysMemPitch = 0; + init_data.SysMemSlicePitch = 0; + + HRESULT hr = wiRenderer::GetDevice()->CreateTexture1D(&tex_desc, &init_data, &g_pFresnelMap); + assert(SUCCEEDED(hr)); + + delete[] buffer; +} + +void wiOcean::loadTextures() +{ + wiRenderer::GetDevice()->CreateTextureFromFile("perlin.dds", &g_pPerlinMap, true, GRAPHICSTHREAD_IMMEDIATE); +} + +bool wiOcean::checkNodeVisibility(const QuadNode& quad_node, const Camera& camera) +{ + // Plane equation setup + + XMMATRIX matProj = camera.GetRealProjection(); + + // Left plane + float fov_x = atan(1.0f / XMVectorGetX(matProj.r[0])); + XMVECTOR plane_left = XMVectorSet(cos(fov_x), 0, sin(fov_x), 0); + // Right plane + XMVECTOR plane_right = XMVectorSet(-cos(fov_x), 0, sin(fov_x), 0); + + // Bottom plane + float fov_y = atan(1.0f / XMVectorGetY(matProj.r[1])); + XMVECTOR plane_bottom = XMVectorSet(0, cos(fov_y), sin(fov_y), 0); + // Top plane + XMVECTOR plane_top = XMVectorSet(0, -cos(fov_y), sin(fov_y), 0); + + // Test quad corners against view frustum in view space + XMVECTOR corner_verts[4]; + corner_verts[0] = XMVectorSet(quad_node.bottom_left.x, quad_node.bottom_left.y, 0, 1); + corner_verts[1] = corner_verts[0] + XMVectorSet(quad_node.length, 0, 0, 0); + corner_verts[2] = corner_verts[0] + XMVectorSet(quad_node.length, quad_node.length, 0, 0); + corner_verts[3] = corner_verts[0] + XMVectorSet(0, quad_node.length, 0, 0); + + XMMATRIX matView = XMMATRIX(1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1) * camera.GetView(); + corner_verts[0] = XMVector4Transform(corner_verts[0], matView); + corner_verts[1] = XMVector4Transform(corner_verts[1], matView); + corner_verts[2] = XMVector4Transform(corner_verts[2], matView); + corner_verts[3] = XMVector4Transform(corner_verts[3], matView); + + // Test against eye plane + if (XMVectorGetZ(corner_verts[0]) < 0 && XMVectorGetZ(corner_verts[1]) < 0 && XMVectorGetZ(corner_verts[2]) < 0 && XMVectorGetZ(corner_verts[3]) < 0) + return false; + + // Test against left plane + float dist_0 = XMVectorGetX(XMVector4Dot(corner_verts[0], plane_left)); + float dist_1 = XMVectorGetX(XMVector4Dot(corner_verts[1], plane_left)); + float dist_2 = XMVectorGetX(XMVector4Dot(corner_verts[2], plane_left)); + float dist_3 = XMVectorGetX(XMVector4Dot(corner_verts[3], plane_left)); + if (dist_0 < 0 && dist_1 < 0 && dist_2 < 0 && dist_3 < 0) + return false; + + // Test against right plane + dist_0 = XMVectorGetX(XMVector4Dot(corner_verts[0], plane_right)); + dist_1 = XMVectorGetX(XMVector4Dot(corner_verts[1], plane_right)); + dist_2 = XMVectorGetX(XMVector4Dot(corner_verts[2], plane_right)); + dist_3 = XMVectorGetX(XMVector4Dot(corner_verts[3], plane_right)); + if (dist_0 < 0 && dist_1 < 0 && dist_2 < 0 && dist_3 < 0) + return false; + + // Test against bottom plane + dist_0 = XMVectorGetX(XMVector4Dot(corner_verts[0], plane_bottom)); + dist_1 = XMVectorGetX(XMVector4Dot(corner_verts[1], plane_bottom)); + dist_2 = XMVectorGetX(XMVector4Dot(corner_verts[2], plane_bottom)); + dist_3 = XMVectorGetX(XMVector4Dot(corner_verts[3], plane_bottom)); + if (dist_0 < 0 && dist_1 < 0 && dist_2 < 0 && dist_3 < 0) + return false; + + // Test against top plane + dist_0 = XMVectorGetX(XMVector4Dot(corner_verts[0], plane_top)); + dist_1 = XMVectorGetX(XMVector4Dot(corner_verts[1], plane_top)); + dist_2 = XMVectorGetX(XMVector4Dot(corner_verts[2], plane_top)); + dist_3 = XMVectorGetX(XMVector4Dot(corner_verts[3], plane_top)); + if (dist_0 < 0 && dist_1 < 0 && dist_2 < 0 && dist_3 < 0) + return false; + + return true; +} + +float wiOcean::estimateGridCoverage(const QuadNode& quad_node, const Camera& camera, float screen_area) +{ + // Estimate projected area + + // Test 16 points on the quad and find out the biggest one. + const static float sample_pos[16][2] = + { + { 0, 0 }, + { 0, 1 }, + { 1, 0 }, + { 1, 1 }, + { 0.5f, 0.333f }, + { 0.25f, 0.667f }, + { 0.75f, 0.111f }, + { 0.125f, 0.444f }, + { 0.625f, 0.778f }, + { 0.375f, 0.222f }, + { 0.875f, 0.556f }, + { 0.0625f, 0.889f }, + { 0.5625f, 0.037f }, + { 0.3125f, 0.37f }, + { 0.8125f, 0.704f }, + { 0.1875f, 0.148f }, + }; + + XMMATRIX matProj = camera.GetRealProjection(); + XMFLOAT3 eye = camera.translation; + XMVECTOR eye_point = XMVectorSet(eye.x, eye.z, eye.y, 0); + float grid_len_world = quad_node.length / g_MeshDim; + + float max_area_proj = 0; + for (int i = 0; i < 16; i++) + { + XMVECTOR test_point = XMVectorSet(quad_node.bottom_left.x + quad_node.length * sample_pos[i][0], quad_node.bottom_left.y + quad_node.length * sample_pos[i][1], 0, 0); + XMVECTOR eye_vec = test_point - eye_point; + float dist = XMVectorGetX(XMVector3Length(eye_vec)); + + float area_world = grid_len_world * grid_len_world;// * abs(eye_point.z) / sqrt(nearest_sqr_dist); + float area_proj = area_world * XMVectorGetX(matProj.r[0]) * XMVectorGetY(matProj.r[1]) / (dist * dist); + + if (max_area_proj < area_proj) + max_area_proj = area_proj; + } + + float pixel_coverage = max_area_proj * screen_area * 0.25f; + + return pixel_coverage; +} + +bool wiOcean::isLeaf(const QuadNode& quad_node) +{ + return (quad_node.sub_node[0] == -1 && quad_node.sub_node[1] == -1 && quad_node.sub_node[2] == -1 && quad_node.sub_node[3] == -1); +} + +int wiOcean::searchLeaf(const vector& node_list, const XMFLOAT2& point) +{ + int index = -1; + + int size = (int)node_list.size(); + QuadNode node = node_list[size - 1]; + + while (!isLeaf(node)) + { + bool found = false; + + for (int i = 0; i < 4; i++) + { + index = node.sub_node[i]; + if (index == -1) + continue; + + QuadNode sub_node = node_list[index]; + if (point.x >= sub_node.bottom_left.x && point.x <= sub_node.bottom_left.x + sub_node.length && + point.y >= sub_node.bottom_left.y && point.y <= sub_node.bottom_left.y + sub_node.length) + { + node = sub_node; + found = true; + break; + } + } + + if (!found) + return -1; + } + + return index; +} + +wiOcean::QuadRenderParam& wiOcean::selectMeshPattern(const QuadNode& quad_node) +{ + // Check 4 adjacent quad. + XMVECTOR bottom_left = XMLoadFloat2(&quad_node.bottom_left); + XMVECTOR tmp; + + XMFLOAT2 point_left; + tmp = bottom_left + XMVectorSet(-g_PatchLength * 0.5f, quad_node.length * 0.5f, 0, 0); + XMStoreFloat2(&point_left, tmp); + int left_adj_index = searchLeaf(g_render_list, point_left); + + XMFLOAT2 point_right; + tmp = bottom_left + XMVectorSet(quad_node.length + g_PatchLength * 0.5f, quad_node.length * 0.5f, 0, 0); + XMStoreFloat2(&point_right, tmp); + int right_adj_index = searchLeaf(g_render_list, point_right); + + XMFLOAT2 point_bottom; + tmp = bottom_left + XMVectorSet(quad_node.length * 0.5f, -g_PatchLength * 0.5f, 0, 0); + XMStoreFloat2(&point_right, tmp); + int bottom_adj_index = searchLeaf(g_render_list, point_bottom); + + XMFLOAT2 point_top; + tmp = bottom_left + XMVectorSet(quad_node.length * 0.5f, quad_node.length + g_PatchLength * 0.5f, 0, 0); + XMStoreFloat2(&point_right, tmp); + int top_adj_index = searchLeaf(g_render_list, point_top); + + int left_type = 0; + if (left_adj_index != -1 && g_render_list[left_adj_index].length > quad_node.length * 0.999f) + { + QuadNode adj_node = g_render_list[left_adj_index]; + float scale = adj_node.length / quad_node.length * (g_MeshDim >> quad_node.lod) / (g_MeshDim >> adj_node.lod); + if (scale > 3.999f) + left_type = 2; + else if (scale > 1.999f) + left_type = 1; + } + + int right_type = 0; + if (right_adj_index != -1 && g_render_list[right_adj_index].length > quad_node.length * 0.999f) + { + QuadNode adj_node = g_render_list[right_adj_index]; + float scale = adj_node.length / quad_node.length * (g_MeshDim >> quad_node.lod) / (g_MeshDim >> adj_node.lod); + if (scale > 3.999f) + right_type = 2; + else if (scale > 1.999f) + right_type = 1; + } + + int bottom_type = 0; + if (bottom_adj_index != -1 && g_render_list[bottom_adj_index].length > quad_node.length * 0.999f) + { + QuadNode adj_node = g_render_list[bottom_adj_index]; + float scale = adj_node.length / quad_node.length * (g_MeshDim >> quad_node.lod) / (g_MeshDim >> adj_node.lod); + if (scale > 3.999f) + bottom_type = 2; + else if (scale > 1.999f) + bottom_type = 1; + } + + int top_type = 0; + if (top_adj_index != -1 && g_render_list[top_adj_index].length > quad_node.length * 0.999f) + { + QuadNode adj_node = g_render_list[top_adj_index]; + float scale = adj_node.length / quad_node.length * (g_MeshDim >> quad_node.lod) / (g_MeshDim >> adj_node.lod); + if (scale > 3.999f) + top_type = 2; + else if (scale > 1.999f) + top_type = 1; + } + + // Check lookup table, [L][R][B][T] + return g_mesh_patterns[quad_node.lod][left_type][right_type][bottom_type][top_type]; +} + +// Return value: if successful pushed into the list, return the position. If failed, return -1. +int wiOcean::buildNodeList(QuadNode& quad_node, const Camera& camera) +{ + // Check against view frustum + if (!checkNodeVisibility(quad_node, camera)) + return -1; + + // Estimate the min grid coverage + auto res = wiRenderer::GetInternalResolution(); + float min_coverage = estimateGridCoverage(quad_node, camera, (float)res.x*res.y); + + // Recursively attatch sub-nodes. + bool visible = true; + XMVECTOR bottom_left = XMLoadFloat2(&quad_node.bottom_left); + XMFLOAT2 tmp; + if (min_coverage > g_UpperGridCoverage && quad_node.length > g_PatchLength) + { + // Recursive rendering for sub-quads. + QuadNode sub_node_0 = { quad_node.bottom_left, quad_node.length / 2, 0,{ -1, -1, -1, -1 } }; + quad_node.sub_node[0] = buildNodeList(sub_node_0, camera); + + XMStoreFloat2(&tmp, bottom_left + XMVectorSet(quad_node.length / 2, 0, 0, 0)); + QuadNode sub_node_1 = { tmp, quad_node.length / 2, 0,{ -1, -1, -1, -1 } }; + quad_node.sub_node[1] = buildNodeList(sub_node_1, camera); + + XMStoreFloat2(&tmp, bottom_left + XMVectorSet(quad_node.length / 2, quad_node.length / 2, 0, 0)); + QuadNode sub_node_2 = { tmp, quad_node.length / 2, 0,{ -1, -1, -1, -1 } }; + quad_node.sub_node[2] = buildNodeList(sub_node_2, camera); + + XMStoreFloat2(&tmp, bottom_left + XMVectorSet(0, quad_node.length / 2, 0, 0)); + QuadNode sub_node_3 = { tmp, quad_node.length / 2, 0,{ -1, -1, -1, -1 } }; + quad_node.sub_node[3] = buildNodeList(sub_node_3, camera); + + visible = !isLeaf(quad_node); + } + + if (visible) + { + // Estimate mesh LOD + int lod = 0; + for (lod = 0; lod < g_Lods - 1; lod++) + { + if (min_coverage > g_UpperGridCoverage) + break; + min_coverage *= 4; + } + + // We don't use 1x1 and 2x2 patch. So the highest level is g_Lods - 2. + quad_node.lod = min(lod, g_Lods - 2); + } + else + return -1; + + // Insert into the list + int position = (int)g_render_list.size(); + g_render_list.push_back(quad_node); + + return position; +} + +void wiOcean::Render(const Camera* camera, float time) +{ + GraphicsDevice* device = wiRenderer::GetDevice(); + bool wire = wiRenderer::IsWireRender(); + + // Build rendering list + g_render_list.clear(); + float ocean_extent = g_PatchLength * (1 << g_FurthestCover); + QuadNode root_node = { XMFLOAT2(-ocean_extent * 0.5f, -ocean_extent * 0.5f), ocean_extent, 0,{ -1,-1,-1,-1 } }; + buildNodeList(root_node, *camera); + + // Matrices + XMMATRIX matView = XMMATRIX(1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1) * camera->GetView(); + XMMATRIX matProj = camera->GetProjection(); + + // VS & PS + device->BindVS(g_pOceanSurfVS, threadID); + device->BindPS(wire ? g_pWireframePS : g_pOceanSurfPS, threadID); + + // Textures + GPUResource* vs_srvs[2] = { m_pDisplacementMap, g_pPerlinMap }; + device->BindResourcesVS(&vs_srvs[0], TEXSLOT_ONDEMAND0, 2, threadID); + + GPUResource* ps_srvs[4] = { g_pPerlinMap, m_pGradientMap, g_pFresnelMap, wiRenderer::GetEnviromentMap() }; + device->BindResourcesPS(&ps_srvs[0], TEXSLOT_ONDEMAND1, 4, threadID); + + // IA setup + device->BindIndexBuffer(g_pMeshIB, INDEXBUFFER_FORMAT::INDEXFORMAT_32BIT, 0, threadID); + + GPUBuffer* vbs[1] = { g_pMeshVB }; + UINT strides[1] = { sizeof(ocean_vertex) }; + UINT offsets[1] = { 0 }; + device->BindVertexBuffers(&vbs[0], 0, 1, &strides[0], &offsets[0], threadID); + + device->BindVertexLayout(g_pMeshLayout, threadID); + + // State blocks + device->BindRasterizerState(wire ? g_pRSState_Wireframe : g_pRSState_Solid, threadID); + + // Constants + device->BindConstantBufferVS(g_pShadingCB, CB_GETBINDSLOT(Ocean_Rendering_ShadingCB), threadID); + device->BindConstantBufferPS(g_pShadingCB, CB_GETBINDSLOT(Ocean_Rendering_ShadingCB), threadID); + + // We assume the center of the ocean surface at (0, 0, 0). + for (int i = 0; i < (int)g_render_list.size(); i++) + { + QuadNode& node = g_render_list[i]; + + if (!isLeaf(node)) + continue; + + // Check adjacent patches and select mesh pattern + QuadRenderParam& render_param = selectMeshPattern(node); + + // Find the right LOD to render + int level_size = g_MeshDim; + for (int lod = 0; lod < node.lod; lod++) + level_size >>= 1; + + // Matrices and constants + Ocean_Rendering_PatchCB call_consts; + + // Expand of the local coordinate to world space patch size + XMMATRIX matScale = XMMatrixScaling(node.length / level_size, node.length / level_size, 0); + call_consts.g_matLocal = XMMatrixTranspose(matScale); + + // WVP matrix + XMMATRIX matWorld = XMMatrixTranslation(node.bottom_left.x, node.bottom_left.y, 0); + XMMATRIX matWVP = matWorld * matView * matProj; + call_consts.g_matWorldViewProj = XMMatrixTranspose(matWVP); + + // Texcoord for perlin noise + XMVECTOR uv_base = XMLoadFloat2(&node.bottom_left) / g_PatchLength * g_PerlinSize; + XMStoreFloat2(&call_consts.g_UVBase, uv_base); + + // Constant g_PerlinSpeed need to be adjusted mannually + XMVECTOR perlin_move = -XMLoadFloat2(&g_WindDir) * time * g_PerlinSpeed; + XMStoreFloat2(&call_consts.g_PerlinMovement, perlin_move); + + // Eye point + XMMATRIX matInvWV = XMMatrixInverse(nullptr, matWorld * matView); + XMVECTOR vLocalEye = XMVector3TransformCoord(XMVectorSet(0, 0, 0, 1), matInvWV); + XMStoreFloat3(&call_consts.g_LocalEye, vLocalEye); + + device->UpdateBuffer(g_pPerCallCB, &call_consts, threadID); + + device->BindConstantBufferVS(g_pPerCallCB, CB_GETBINDSLOT(Ocean_Rendering_PatchCB), threadID); + device->BindConstantBufferPS(g_pPerCallCB, CB_GETBINDSLOT(Ocean_Rendering_PatchCB), threadID); + + // Perform draw call + if (render_param.num_inner_faces > 0) + { + // Inner mesh of the patch + device->BindPrimitiveTopology(TRIANGLESTRIP, threadID); + device->DrawIndexed(render_param.num_inner_faces + 2, render_param.inner_start_index, 0, threadID); + } + + if (render_param.num_boundary_faces > 0) + { + // Boundary mesh of the patch + device->BindPrimitiveTopology(TRIANGLELIST, threadID); + device->DrawIndexed(render_param.num_boundary_faces * 3, render_param.boundary_start_index, 0, threadID); + } + } + + //// Unbind + //vs_srvs[0] = NULL; + //vs_srvs[1] = NULL; + //pd3dContext->VSSetShaderResources(0, 2, &vs_srvs[0]); + + //ps_srvs[0] = NULL; + //ps_srvs[1] = NULL; + //ps_srvs[2] = NULL; + //ps_srvs[3] = NULL; + //pd3dContext->PSSetShaderResources(1, 4, &ps_srvs[0]); +} + diff --git a/WickedEngine/wiOcean.h b/WickedEngine/wiOcean.h new file mode 100644 index 000000000..e18ea18d5 --- /dev/null +++ b/WickedEngine/wiOcean.h @@ -0,0 +1,257 @@ +// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. +// +// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED +// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS +// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA +// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR +// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS +// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY +// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, +// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +// +// Please direct any bugs or questions to SDKFeedback@nvidia.com + +#ifndef _OCEAN_SIMULATOR_H +#define _OCEAN_SIMULATOR_H + +#include "CommonInclude.h" +#include "wiGraphicsAPI.h" +#include "wiFFTGenerator.h" + +#include + +//#define CS_DEBUG_BUFFER + +struct Camera; + +struct wiOceanParameter +{ + // Must be power of 2. + int dmap_dim; + // Typical value is 1000 ~ 2000 + float patch_length; + + // Adjust the time interval for simulation. + float time_scale; + // Amplitude for transverse wave. Around 1.0 + float wave_amplitude; + // Wind direction. Normalization not required. + XMFLOAT2 wind_dir; + // Around 100 ~ 1000 + float wind_speed; + // This value damps out the waves against the wind direction. + // Smaller value means higher wind dependency. + float wind_dependency; + // The amplitude for longitudinal wave. Must be positive. + float choppy_scale; + + wiOceanParameter() + { + choppy_scale = 1; + dmap_dim = 512; + patch_length = 1000; + time_scale = 1; + wave_amplitude = 1.0f; + wind_dependency = 0.5f; + wind_dir = XMFLOAT2(1, 1); + wind_speed = 100; + } +}; + + +class wiOcean +{ +public: + wiOcean(const wiOceanParameter& params); + ~wiOcean(); + + // -------------------------- Initialization & simulation routines ------------------------ + + // Update ocean wave when tick arrives. + void updateDisplacementMap(float time); + + // Texture access + wiGraphicsTypes::Texture2D* getDisplacementMap(); + wiGraphicsTypes::Texture2D* getGradientMap(); + + const wiOceanParameter& getParameters(); + + void Render(const Camera* camera, float time); + + +protected: + wiOceanParameter m_param; + + // Simulation params: + + wiGraphicsTypes::Texture2D* m_pDisplacementMap; // (RGBA32F) + wiGraphicsTypes::Texture2D* m_pGradientMap; // (RGBA16F) + + // Initialize the vector field. + void initHeightMap(XMFLOAT2* out_h0, float* out_omega); + + + // ----------------------------------- CS simulation data --------------------------------- + + // Initial height field H(0) generated by Phillips spectrum & Gauss distribution. + wiGraphicsTypes::GPUBuffer* m_pBuffer_Float2_H0; + + // Angular frequency + wiGraphicsTypes::GPUBuffer* m_pBuffer_Float_Omega; + + // Height field H(t), choppy field Dx(t) and Dy(t) in frequency domain, updated each frame. + wiGraphicsTypes::GPUBuffer* m_pBuffer_Float2_Ht; + + // Height & choppy buffer in the space domain, corresponding to H(t), Dx(t) and Dy(t) + wiGraphicsTypes::GPUBuffer* m_pBuffer_Float_Dxyz; + + wiGraphicsTypes::GPUBuffer* m_pQuadVB; + + // Shaders, layouts and constants + wiGraphicsTypes::ComputeShader* m_pUpdateSpectrumCS; + + wiGraphicsTypes::VertexShader* m_pQuadVS; + wiGraphicsTypes::PixelShader* m_pUpdateDisplacementPS; + wiGraphicsTypes::PixelShader* m_pGenGradientFoldingPS; + + wiGraphicsTypes::VertexLayout* m_pQuadLayout; + + wiGraphicsTypes::GPUBuffer* m_pImmutableCB; + wiGraphicsTypes::GPUBuffer* m_pPerFrameCB; + + wiGraphicsTypes::Sampler m_pPointSamplerState; + + // FFT wrap-up + CSFFT512x512_Plan m_fft_plan; + +#ifdef CS_DEBUG_BUFFER + wiGraphicsTypes::GPUBuffer* m_pDebugBuffer; +#endif + + + + // Rendering params: + struct ocean_vertex + { + float index_x; + float index_y; + }; + + // Mesh properties: + + // Mesh grid dimension, must be 2^n. 4x4 ~ 256x256 + int g_MeshDim = 128; + // Side length of square shaped mesh patch + float g_PatchLength; + // Dimension of displacement map + int g_DisplaceMapDim; + // Subdivision thredshold. Any quad covers more pixels than this value needs to be subdivided. + float g_UpperGridCoverage = 64.0f; + // Draw distance = g_PatchLength * 2^g_FurthestCover + int g_FurthestCover = 8; + + + // Shading properties: + // Two colors for waterbody and sky color + XMFLOAT3 g_SkyColor = XMFLOAT3(0.38f, 0.45f, 0.56f); + XMFLOAT3 g_WaterbodyColor = XMFLOAT3(0.07f, 0.15f, 0.2f); + // Blending term for sky cubemap + float g_SkyBlending = 16.0f; + + // Perlin wave parameters + float g_PerlinSize = 1.0f; + float g_PerlinSpeed = 0.06f; + XMFLOAT3 g_PerlinAmplitude = XMFLOAT3(35, 42, 57); + XMFLOAT3 g_PerlinGradient = XMFLOAT3(1.4f, 1.6f, 2.2f); + XMFLOAT3 g_PerlinOctave = XMFLOAT3(1.12f, 0.59f, 0.23f); + XMFLOAT2 g_WindDir; + + XMFLOAT3 g_BendParam = XMFLOAT3(0.1f, -0.4f, 0.2f); + + // Sunspot parameters + XMFLOAT3 g_SunDir = XMFLOAT3(0.936016f, -0.343206f, 0.0780013f); + XMFLOAT3 g_SunColor = XMFLOAT3(1.0f, 1.0f, 0.6f); + float g_Shineness = 400.0f; + + + + + struct QuadNode + { + XMFLOAT2 bottom_left; + float length; + int lod; + + int sub_node[4]; + }; + + struct QuadRenderParam + { + UINT num_inner_verts; + UINT num_inner_faces; + UINT inner_start_index; + + UINT num_boundary_verts; + UINT num_boundary_faces; + UINT boundary_start_index; + }; + + // Quad-tree LOD, 0 to 9 (1x1 ~ 512x512) + int g_Lods = 0; + // Pattern lookup array. Filled at init time. + QuadRenderParam g_mesh_patterns[9][3][3][3][3]; + // Pick a proper mesh pattern according to the adjacent patches. + QuadRenderParam& selectMeshPattern(const QuadNode& quad_node); + + // Rendering list + std::vector g_render_list; + int buildNodeList(QuadNode& quad_node, const Camera& camera); + + // D3D11 buffers and layout + wiGraphicsTypes::GPUBuffer* g_pMeshVB = nullptr; + wiGraphicsTypes::GPUBuffer* g_pMeshIB = nullptr; + wiGraphicsTypes::VertexLayout* g_pMeshLayout = nullptr; + + // Color look up 1D texture + wiGraphicsTypes::Texture1D* g_pFresnelMap = nullptr; + + // Distant perlin wave + wiGraphicsTypes::Texture2D* g_pPerlinMap = nullptr; + + // HLSL shaders + wiGraphicsTypes::VertexShader* g_pOceanSurfVS = nullptr; + wiGraphicsTypes::PixelShader* g_pOceanSurfPS = nullptr; + wiGraphicsTypes::PixelShader* g_pWireframePS = nullptr; + + wiGraphicsTypes::GPUBuffer* g_pPerCallCB = nullptr; + wiGraphicsTypes::GPUBuffer* g_pPerFrameCB = nullptr; + wiGraphicsTypes::GPUBuffer* g_pShadingCB = nullptr; + + // State blocks + wiGraphicsTypes::RasterizerState* g_pRSState_Solid = nullptr; + wiGraphicsTypes::RasterizerState* g_pRSState_Wireframe = nullptr; + wiGraphicsTypes::DepthStencilState* g_pDSState_Disable = nullptr; + wiGraphicsTypes::BlendState* g_pBState_Transparent = nullptr; + + + // init & cleanup + void initRenderResource(); + void cleanupRenderResource(); + // create a triangle strip mesh for ocean surface. + void createSurfaceMesh(); + // create color/fresnel lookup table. + void createFresnelMap(); + // create perlin noise texture for far-sight rendering + void loadTextures(); + + int generateBoundaryMesh(int left_degree, int right_degree, int bottom_degree, int top_degree, + RECT vert_rect, DWORD* output); + int generateInnerMesh(RECT vert_rect, DWORD* output); + bool checkNodeVisibility(const QuadNode& quad_node, const Camera& camera); + float estimateGridCoverage(const QuadNode& quad_node, const Camera& camera, float screen_area); + bool isLeaf(const QuadNode& quad_node); + int searchLeaf(const std::vector& node_list, const XMFLOAT2& point); +}; + +#endif // _OCEAN_SIMULATOR_H diff --git a/WickedEngine/wiOceanSimulator.cpp b/WickedEngine/wiOceanSimulator.cpp deleted file mode 100644 index e8f0c1a64..000000000 --- a/WickedEngine/wiOceanSimulator.cpp +++ /dev/null @@ -1,609 +0,0 @@ -// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. -// -// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED -// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS -// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA -// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR -// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS -// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY -// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, -// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// Please direct any bugs or questions to SDKFeedback@nvidia.com - -#include "wiOceanSimulator.h" -#include "wiRenderer.h" -#include "wiResourceManager.h" - -using namespace wiGraphicsTypes; - -static const GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE; // todo: make dynamic - -// Disable warning "conditional expression is constant" -#pragma warning(disable:4127) - - -#define HALF_SQRT_2 0.7071068f -#define GRAV_ACCEL 981.0f // The acceleration of gravity, cm/s^2 - -#define BLOCK_SIZE_X 16 -#define BLOCK_SIZE_Y 16 - -// Generating gaussian random number with mean 0 and standard deviation 1. -float Gauss() -{ - float u1 = rand() / (float)RAND_MAX; - float u2 = rand() / (float)RAND_MAX; - if (u1 < 1e-6f) - u1 = 1e-6f; - return sqrtf(-2 * logf(u1)) * cosf(2 * XM_PI * u2); -} - -// Phillips Spectrum -// K: normalized wave vector, W: wind direction, v: wind velocity, a: amplitude constant -float Phillips(XMFLOAT2 K, XMFLOAT2 W, float v, float a, float dir_depend) -{ - // largest possible wave from constant wind of velocity v - float l = v * v / GRAV_ACCEL; - // damp out waves with very small length w << l - float w = l / 1000; - - float Ksqr = K.x * K.x + K.y * K.y; - float Kcos = K.x * W.x + K.y * W.y; - float phillips = a * expf(-1 / (l * l * Ksqr)) / (Ksqr * Ksqr * Ksqr) * (Kcos * Kcos); - - // filter out waves moving opposite to wind - if (Kcos < 0) - phillips *= dir_depend; - - // damp out waves with very small length w << l - return phillips * expf(-Ksqr * w * w); -} - -void createBufferAndUAV(void* data, UINT byte_width, UINT byte_stride, GPUBuffer** ppBuffer) -{ - *ppBuffer = new GPUBuffer; - - // Create buffer - GPUBufferDesc buf_desc; - buf_desc.ByteWidth = byte_width; - buf_desc.Usage = USAGE_DEFAULT; - buf_desc.BindFlags = BIND_UNORDERED_ACCESS | BIND_SHADER_RESOURCE; - buf_desc.CPUAccessFlags = 0; - buf_desc.MiscFlags = RESOURCE_MISC_BUFFER_STRUCTURED; - buf_desc.StructureByteStride = byte_stride; - - SubresourceData init_data; - init_data.pSysMem = data; - - wiRenderer::GetDevice()->CreateBuffer(&buf_desc, data != NULL ? &init_data : NULL, *ppBuffer); - - - //assert(*ppBuffer); - - //// Create undordered access view - //D3D11_UNORDERED_ACCESS_VIEW_DESC uav_desc; - //uav_desc.Format = DXGI_FORMAT_UNKNOWN; - //uav_desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER; - //uav_desc.Buffer.FirstElement = 0; - //uav_desc.Buffer.NumElements = byte_width / byte_stride; - //uav_desc.Buffer.Flags = 0; - - //pd3dDevice->CreateUnorderedAccessView(*ppBuffer, &uav_desc, ppUAV); - //assert(*ppUAV); - - //// Create shader resource view - //D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc; - //srv_desc.Format = DXGI_FORMAT_UNKNOWN; - //srv_desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER; - //srv_desc.Buffer.FirstElement = 0; - //srv_desc.Buffer.NumElements = byte_width / byte_stride; - - //pd3dDevice->CreateShaderResourceView(*ppBuffer, &srv_desc, ppSRV); - //assert(*ppSRV); -} - -void createTextureAndViews(UINT width, UINT height, FORMAT format, Texture2D** ppTex) -{ - // Create 2D texture - Texture2DDesc tex_desc; - tex_desc.Width = width; - tex_desc.Height = height; - tex_desc.MipLevels = 0; - tex_desc.ArraySize = 1; - tex_desc.Format = format; - tex_desc.SampleDesc.Count = 1; - tex_desc.SampleDesc.Quality = 0; - tex_desc.Usage = USAGE_DEFAULT; - tex_desc.BindFlags = BIND_SHADER_RESOURCE | BIND_RENDER_TARGET; - tex_desc.CPUAccessFlags = 0; - tex_desc.MiscFlags = RESOURCE_MISC_GENERATE_MIPS; - - *ppTex = new Texture2D; - wiRenderer::GetDevice()->CreateTexture2D(&tex_desc, NULL, ppTex); - - - //assert(*ppTex); - - //// Create shader resource view - //(*ppTex)->GetDesc(&tex_desc); - //if (ppSRV) - //{ - // D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc; - // srv_desc.Format = format; - // srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - // srv_desc.Texture2D.MipLevels = tex_desc.MipLevels; - // srv_desc.Texture2D.MostDetailedMip = 0; - - // pd3dDevice->CreateShaderResourceView(*ppTex, &srv_desc, ppSRV); - // assert(*ppSRV); - //} - - //// Create render target view - //if (ppRTV) - //{ - // D3D11_RENDER_TARGET_VIEW_DESC rtv_desc; - // rtv_desc.Format = format; - // rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; - // rtv_desc.Texture2D.MipSlice = 0; - - // pd3dDevice->CreateRenderTargetView(*ppTex, &rtv_desc, ppRTV); - // assert(*ppRTV); - //} -} - -OceanSimulator::OceanSimulator(OceanParameter& params) -{ - // Height map H(0) - int height_map_size = (params.dmap_dim + 4) * (params.dmap_dim + 1); - XMFLOAT2* h0_data = new XMFLOAT2[height_map_size * sizeof(XMFLOAT2)]; - float* omega_data = new float[height_map_size * sizeof(float)]; - initHeightMap(params, h0_data, omega_data); - - m_param = params; - int hmap_dim = params.dmap_dim; - int input_full_size = (hmap_dim + 4) * (hmap_dim + 1); - // This value should be (hmap_dim / 2 + 1) * hmap_dim, but we use full sized buffer here for simplicity. - int input_half_size = hmap_dim * hmap_dim; - int output_size = hmap_dim * hmap_dim; - - // For filling the buffer with zeroes. - char* zero_data = new char[3 * output_size * sizeof(float) * 2]; - memset(zero_data, 0, 3 * output_size * sizeof(float) * 2); - - // RW buffer allocations - // H0 - UINT float2_stride = 2 * sizeof(float); - createBufferAndUAV(h0_data, input_full_size * float2_stride, float2_stride, &m_pBuffer_Float2_H0); - - // Notice: The following 3 buffers should be half sized buffer because of conjugate symmetric input. But - // we use full sized buffers due to the CS4.0 restriction. - - // Put H(t), Dx(t) and Dy(t) into one buffer because CS4.0 allows only 1 UAV at a time - createBufferAndUAV(zero_data, 3 * input_half_size * float2_stride, float2_stride, &m_pBuffer_Float2_Ht); - - // omega - createBufferAndUAV(omega_data, input_full_size * sizeof(float), sizeof(float), &m_pBuffer_Float_Omega); - - // Notice: The following 3 should be real number data. But here we use the complex numbers and C2C FFT - // due to the CS4.0 restriction. - // Put Dz, Dx and Dy into one buffer because CS4.0 allows only 1 UAV at a time - createBufferAndUAV(zero_data, 3 * output_size * float2_stride, float2_stride, &m_pBuffer_Float_Dxyz); - - SAFE_DELETE_ARRAY(zero_data); - SAFE_DELETE_ARRAY(h0_data); - SAFE_DELETE_ARRAY(omega_data); - - // D3D11 Textures - createTextureAndViews(hmap_dim, hmap_dim, FORMAT_R32G32B32A32_FLOAT, &m_pDisplacementMap); - createTextureAndViews(hmap_dim, hmap_dim, FORMAT_R16G16B16A16_FLOAT, &m_pGradientMap); - - // Samplers - SamplerDesc sam_desc; - sam_desc.Filter = FILTER_MIN_MAG_LINEAR_MIP_POINT; - sam_desc.AddressU = TEXTURE_ADDRESS_WRAP; - sam_desc.AddressV = TEXTURE_ADDRESS_WRAP; - sam_desc.AddressW = TEXTURE_ADDRESS_WRAP; - sam_desc.MipLODBias = 0; - sam_desc.MaxAnisotropy = 1; - sam_desc.ComparisonFunc = COMPARISON_NEVER; - sam_desc.BorderColor[0] = 1.0f; - sam_desc.BorderColor[1] = 1.0f; - sam_desc.BorderColor[2] = 1.0f; - sam_desc.BorderColor[3] = 1.0f; - sam_desc.MinLOD = -FLT_MAX; - sam_desc.MaxLOD = FLT_MAX; - wiRenderer::GetDevice()->CreateSamplerState(&sam_desc, &m_pPointSamplerState); - - - m_pUpdateSpectrumCS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSimulatorCS.cso", wiResourceManager::COMPUTESHADER)); - - { - VertexLayoutDesc layout[] = - { - { "POSITION", 0, FORMAT_R32G32B32A32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, - }; - UINT numElements = ARRAYSIZE(layout); - VertexShaderInfo* vsinfo = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanQuadVS.cso", wiResourceManager::VERTEXSHADER, layout, numElements)); - if (vsinfo != nullptr) { - m_pQuadVS = vsinfo->vertexShader; - m_pQuadLayout = vsinfo->vertexLayout; - } - } - - m_pUpdateDisplacementPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanUpdateDisplacementPS.cso", wiResourceManager::PIXELSHADER)); - m_pGenGradientFoldingPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanGradientFoldingPS.cso", wiResourceManager::PIXELSHADER)); - - - - //// Compute shaders - //ID3DBlob* pBlobUpdateSpectrumCS = NULL; - - //CompileShaderFromFile(L"ocean_simulator_cs.hlsl", "UpdateSpectrumCS", "cs_4_0", &pBlobUpdateSpectrumCS); - //assert(pBlobUpdateSpectrumCS); - - //m_pd3dDevice->CreateComputeShader(pBlobUpdateSpectrumCS->GetBufferPointer(), pBlobUpdateSpectrumCS->GetBufferSize(), NULL, &m_pUpdateSpectrumCS); - //assert(m_pUpdateSpectrumCS); - - //SAFE_RELEASE(pBlobUpdateSpectrumCS); - - //// Vertex & pixel shaders - //ID3DBlob* pBlobQuadVS = NULL; - //ID3DBlob* pBlobUpdateDisplacementPS = NULL; - //ID3DBlob* pBlobGenGradientFoldingPS = NULL; - - //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "QuadVS", "vs_4_0", &pBlobQuadVS); - //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "UpdateDisplacementPS", "ps_4_0", &pBlobUpdateDisplacementPS); - //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "GenGradientFoldingPS", "ps_4_0", &pBlobGenGradientFoldingPS); - //assert(pBlobQuadVS); - //assert(pBlobUpdateDisplacementPS); - //assert(pBlobGenGradientFoldingPS); - - //m_pd3dDevice->CreateVertexShader(pBlobQuadVS->GetBufferPointer(), pBlobQuadVS->GetBufferSize(), NULL, &m_pQuadVS); - //m_pd3dDevice->CreatePixelShader(pBlobUpdateDisplacementPS->GetBufferPointer(), pBlobUpdateDisplacementPS->GetBufferSize(), NULL, &m_pUpdateDisplacementPS); - //m_pd3dDevice->CreatePixelShader(pBlobGenGradientFoldingPS->GetBufferPointer(), pBlobGenGradientFoldingPS->GetBufferSize(), NULL, &m_pGenGradientFoldingPS); - //assert(m_pQuadVS); - //assert(m_pUpdateDisplacementPS); - //assert(m_pGenGradientFoldingPS); - //SAFE_RELEASE(pBlobUpdateDisplacementPS); - //SAFE_RELEASE(pBlobGenGradientFoldingPS); - - //// Input layout - //D3D11_INPUT_ELEMENT_DESC quad_layout_desc[] = - //{ - // { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - //}; - //m_pd3dDevice->CreateInputLayout(quad_layout_desc, 1, pBlobQuadVS->GetBufferPointer(), pBlobQuadVS->GetBufferSize(), &m_pQuadLayout); - //assert(m_pQuadLayout); - - //SAFE_RELEASE(pBlobQuadVS); - - // Quad vertex buffer - GPUBufferDesc vb_desc; - vb_desc.ByteWidth = 4 * sizeof(XMFLOAT4); - vb_desc.Usage = USAGE_IMMUTABLE; - vb_desc.BindFlags = BIND_VERTEX_BUFFER; - vb_desc.CPUAccessFlags = 0; - vb_desc.MiscFlags = 0; - - float quad_verts[] = - { - -1, -1, 0, 1, - -1, 1, 0, 1, - 1, -1, 0, 1, - 1, 1, 0, 1, - }; - SubresourceData init_data; - init_data.pSysMem = &quad_verts[0]; - init_data.SysMemPitch = 0; - init_data.SysMemSlicePitch = 0; - - m_pQuadVB = new GPUBuffer; - wiRenderer::GetDevice()->CreateBuffer(&vb_desc, &init_data, m_pQuadVB); - - // Constant buffers - UINT actual_dim = m_param.dmap_dim; - UINT input_width = actual_dim + 4; - // We use full sized data here. The value "output_width" should be actual_dim/2+1 though. - UINT output_width = actual_dim; - UINT output_height = actual_dim; - UINT dtx_offset = actual_dim * actual_dim; - UINT dty_offset = actual_dim * actual_dim * 2; - UINT immutable_consts[] = { actual_dim, input_width, output_width, output_height, dtx_offset, dty_offset }; - SubresourceData init_cb0; - init_cb0.pSysMem = &immutable_consts[0]; - - GPUBufferDesc cb_desc; - cb_desc.Usage = USAGE_IMMUTABLE; - cb_desc.BindFlags = BIND_CONSTANT_BUFFER; - cb_desc.CPUAccessFlags = 0; - cb_desc.MiscFlags = 0; - cb_desc.ByteWidth = PAD16(sizeof(immutable_consts)); - m_pImmutableCB = new GPUBuffer; - wiRenderer::GetDevice()->CreateBuffer(&cb_desc, &init_cb0, m_pImmutableCB); - - cb_desc.Usage = USAGE_DYNAMIC; - cb_desc.BindFlags = BIND_CONSTANT_BUFFER; - cb_desc.CPUAccessFlags = CPU_ACCESS_WRITE; - cb_desc.MiscFlags = 0; - cb_desc.ByteWidth = PAD16(sizeof(float) * 3); - m_pPerFrameCB = new GPUBuffer; - wiRenderer::GetDevice()->CreateBuffer(&cb_desc, NULL, m_pPerFrameCB); - - // FFT - fft512x512_create_plan(&m_fft_plan, 3); - -#ifdef CS_DEBUG_BUFFER - GPUBufferDesc buf_desc; - buf_desc.ByteWidth = 3 * input_half_size * float2_stride; - buf_desc.Usage = USAGE_STAGING; - buf_desc.BindFlags = 0; - buf_desc.CPUAccessFlags = CPU_ACCESS_READ; - buf_desc.MiscFlags = RESOURCE_MISC_BUFFER_STRUCTURED; - buf_desc.StructureByteStride = float2_stride; - - m_pDebugBuffer = new GPUBuffer; - wiRenderer::GetDevice()->CreateBuffer(&buf_desc, NULL, m_pDebugBuffer); -#endif -} - -OceanSimulator::~OceanSimulator() -{ - fft512x512_destroy_plan(&m_fft_plan); - - SAFE_DELETE(m_pBuffer_Float2_H0); - SAFE_DELETE(m_pBuffer_Float_Omega); - SAFE_DELETE(m_pBuffer_Float2_Ht); - SAFE_DELETE(m_pBuffer_Float_Dxyz); - - SAFE_DELETE(m_pQuadVB); - - SAFE_DELETE(m_pDisplacementMap); - SAFE_DELETE(m_pGradientMap); - - SAFE_DELETE(m_pUpdateSpectrumCS); - SAFE_DELETE(m_pQuadVS); - SAFE_DELETE(m_pUpdateDisplacementPS); - SAFE_DELETE(m_pGenGradientFoldingPS); - - SAFE_DELETE(m_pQuadLayout); - - SAFE_DELETE(m_pImmutableCB); - SAFE_DELETE(m_pPerFrameCB); - -#ifdef CS_DEBUG_BUFFER - SAFE_DELETE(m_pDebugBuffer); -#endif -} - - -// Initialize the vector field. -// wlen_x: width of wave tile, in meters -// wlen_y: length of wave tile, in meters -void OceanSimulator::initHeightMap(OceanParameter& params, XMFLOAT2* out_h0, float* out_omega) -{ - int i, j; - XMFLOAT2 K, Kn; - - XMFLOAT2 wind_dir; - XMStoreFloat2(&wind_dir, XMVector2Normalize(XMLoadFloat2(¶ms.wind_dir))); - float a = params.wave_amplitude * 1e-7f; // It is too small. We must scale it for editing. - float v = params.wind_speed; - float dir_depend = params.wind_dependency; - - int height_map_dim = params.dmap_dim; - float patch_length = params.patch_length; - - // initialize random generator. - srand(0); - - for (i = 0; i <= height_map_dim; i++) - { - // K is wave-vector, range [-|DX/W, |DX/W], [-|DY/H, |DY/H] - K.y = (-height_map_dim / 2.0f + i) * (2 * XM_PI / patch_length); - - for (j = 0; j <= height_map_dim; j++) - { - K.x = (-height_map_dim / 2.0f + j) * (2 * XM_PI / patch_length); - - float phil = (K.x == 0 && K.y == 0) ? 0 : sqrtf(Phillips(K, wind_dir, v, a, dir_depend)); - - out_h0[i * (height_map_dim + 4) + j].x = float(phil * Gauss() * HALF_SQRT_2); - out_h0[i * (height_map_dim + 4) + j].y = float(phil * Gauss() * HALF_SQRT_2); - - // The angular frequency is following the dispersion relation: - // out_omega^2 = g*k - // The equation of Gerstner wave: - // x = x0 - K/k * A * sin(dot(K, x0) - sqrt(g * k) * t), x is a 2D vector. - // z = A * cos(dot(K, x0) - sqrt(g * k) * t) - // Gerstner wave shows that a point on a simple sinusoid wave is doing a uniform circular - // motion with the center (x0, y0, z0), radius A, and the circular plane is parallel to - // vector K. - out_omega[i * (height_map_dim + 4) + j] = sqrtf(GRAV_ACCEL * sqrtf(K.x * K.x + K.y * K.y)); - } - } -} - -void OceanSimulator::updateDisplacementMap(float time) -{ - GraphicsDevice* device = wiRenderer::GetDevice(); - - device->EventBegin("OceanSimulator", threadID); - - // ---------------------------- H(0) -> H(t), D(x, t), D(y, t) -------------------------------- - // Compute shader - device->BindCS(m_pUpdateSpectrumCS, threadID); - - // Buffers - GPUResource* cs0_srvs[2] = { - m_pBuffer_Float2_H0, - m_pBuffer_Float_Omega - }; - device->BindResourcesCS(cs0_srvs, 0, 2, threadID); - - GPUUnorderedResource* cs0_uavs[1] = { m_pBuffer_Float2_Ht }; - device->BindUnorderedAccessResourcesCS(cs0_uavs, 0, 1, threadID); - - // Consts - //D3D11_MAPPED_SUBRESOURCE mapped_res; - //m_pd3dImmediateContext->Map(m_pPerFrameCB, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_res); - //assert(mapped_res.pData); - //float per_frame_data = (float*)mapped_res.pData; - //// g_Time - //per_frame_data[0] = time * m_param.time_scale; - //// g_ChoppyScale - //per_frame_data[1] = m_param.choppy_scale; - //// g_GridLen - //per_frame_data[2] = m_param.dmap_dim / m_param.patch_length; - //m_pd3dImmediateContext->Unmap(m_pPerFrameCB, 0); - - float per_frame_data[3] = { - time * m_param.time_scale, // g_Time - m_param.choppy_scale, // g_ChoppyScale - m_param.dmap_dim / m_param.patch_length, // g_GridLen - }; - device->UpdateBuffer(m_pPerFrameCB, per_frame_data, threadID, sizeof(per_frame_data)); - - //ID3D11Buffer* cs_cbs[2] = { m_pImmutableCB, m_pPerFrameCB }; - //m_pd3dImmediateContext->CSSetConstantBuffers(0, 2, cs_cbs); - device->BindConstantBufferCS(m_pImmutableCB, 0, threadID); - device->BindConstantBufferCS(m_pPerFrameCB, 1, threadID); - - // Run the CS - UINT group_count_x = (m_param.dmap_dim + BLOCK_SIZE_X - 1) / BLOCK_SIZE_X; - UINT group_count_y = (m_param.dmap_dim + BLOCK_SIZE_Y - 1) / BLOCK_SIZE_Y; - device->Dispatch(group_count_x, group_count_y, 1, threadID); - - //// Unbind resources for CS - //cs0_uavs[0] = NULL; - //m_pd3dImmediateContext->CSSetUnorderedAccessViews(0, 1, cs0_uavs, (UINT*)(&cs0_uavs[0])); - //cs0_srvs[0] = NULL; - //cs0_srvs[1] = NULL; - //m_pd3dImmediateContext->CSSetShaderResources(0, 2, cs0_srvs); - - device->UnBindUnorderedAccessResources(0, 1, threadID); - device->UnBindResources(0, 2, threadID); - - - // ------------------------------------ Perform FFT ------------------------------------------- - fft_512x512_c2c(&m_fft_plan, m_pBuffer_Float_Dxyz, m_pBuffer_Float_Dxyz, m_pBuffer_Float2_Ht); - - // --------------------------------- Wrap Dx, Dy and Dz --------------------------------------- - // Push RT - //ID3D11RenderTargetView* old_target; - //ID3D11DepthStencilView* old_depth; - //m_pd3dImmediateContext->OMGetRenderTargets(1, &old_target, &old_depth); - //D3D11_VIEWPORT old_viewport; - //UINT num_viewport = 1; - //m_pd3dImmediateContext->RSGetViewports(&num_viewport, &old_viewport); - - ViewPort new_vp; - new_vp.TopLeftX = 0; - new_vp.TopLeftX = 0; - new_vp.Width = (float)m_param.dmap_dim; - new_vp.Height = (float)m_param.dmap_dim; - new_vp.MinDepth = 0.0f; - new_vp.MaxDepth = 1.0f; - device->BindViewports(1, &new_vp, threadID); - - // Set RT - device->BindRenderTargets(1, (Texture**)&m_pDisplacementMap, nullptr, threadID); - - // VS & PS - device->BindVS(m_pQuadVS, threadID); - device->BindPS(m_pUpdateDisplacementPS, threadID); - - // Constants - //ID3D11Buffer* ps_cbs[2] = { m_pImmutableCB, m_pPerFrameCB }; - //m_pd3dImmediateContext->PSSetConstantBuffers(0, 2, ps_cbs); - device->BindConstantBufferPS(m_pImmutableCB, 0, threadID); - device->BindConstantBufferPS(m_pPerFrameCB, 1, threadID); - - // Buffer resources - GPUResource* ps_srvs[1] = { m_pBuffer_Float_Dxyz }; - device->BindResourcesPS(ps_srvs, 0, 1, threadID); - - // IA setup - GPUBuffer* vbs[1] = { m_pQuadVB }; - UINT strides[1] = { sizeof(XMFLOAT4) }; - UINT offsets[1] = { 0 }; - device->BindVertexBuffers(&vbs[0], 0, 1, &strides[0], &offsets[0], threadID); - - device->BindVertexLayout(m_pQuadLayout, threadID); - device->BindPrimitiveTopology(TRIANGLESTRIP, threadID); - - // Perform draw call - device->Draw(4, 0, threadID); - - // Unbind - device->UnBindResources(0, 1, threadID); - - - // ----------------------------------- Generate Normal ---------------------------------------- - // Set RT - device->BindRenderTargets(1, (Texture**)&m_pGradientMap, nullptr, threadID); - - // VS & PS - device->BindVS(m_pQuadVS, threadID); - device->BindPS(m_pGenGradientFoldingPS, threadID); - - // Texture resource and sampler - ps_srvs[0] = m_pDisplacementMap; - device->BindResourcesPS(ps_srvs, 0, 1, threadID); - - device->BindSamplerPS(&m_pPointSamplerState, 0, threadID); - - // Perform draw call - device->Draw(4, 0, threadID); - - // Unbind - device->UnBindResources(0, 1, threadID); - - //// Pop RT - //m_pd3dImmediateContext->RSSetViewports(1, &old_viewport); - //m_pd3dImmediateContext->OMSetRenderTargets(1, &old_target, old_depth); - //SAFE_RELEASE(old_target); - //SAFE_RELEASE(old_depth); - - device->GenerateMips(m_pGradientMap, threadID); - - // Define CS_DEBUG_BUFFER to enable writing a buffer into a file. -#ifdef CS_DEBUG_BUFFER - { - m_pd3dImmediateContext->CopyResource(m_pDebugBuffer, m_pBuffer_Float_Dxyz); - D3D11_MAPPED_SUBRESOURCE mapped_res; - m_pd3dImmediateContext->Map(m_pDebugBuffer, 0, D3D11_MAP_READ, 0, &mapped_res); - - // set a break point below, and drag MappedResource.pData into in your Watch window - // and cast it as (float*) - - // Write to disk - XMFLOAT2* v = (XMFLOAT2*)mapped_res.pData; - - FILE* fp = fopen(".\\tmp\\Ht_raw.dat", "wb"); - fwrite(v, 512 * 512 * sizeof(float) * 2 * 3, 1, fp); - fclose(fp); - - m_pd3dImmediateContext->Unmap(m_pDebugBuffer, 0); - } -#endif - - device->EventEnd(threadID); -} - -Texture2D* OceanSimulator::getD3D11DisplacementMap() -{ - return m_pDisplacementMap; -} - -Texture2D* OceanSimulator::getD3D11GradientMap() -{ - return m_pGradientMap; -} - - -const OceanParameter& OceanSimulator::getParameters() -{ - return m_param; -} diff --git a/WickedEngine/wiOceanSimulator.h b/WickedEngine/wiOceanSimulator.h deleted file mode 100644 index a94c2a60c..000000000 --- a/WickedEngine/wiOceanSimulator.h +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. -// -// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED -// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS -// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA -// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR -// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS -// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY -// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, -// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// Please direct any bugs or questions to SDKFeedback@nvidia.com - -#ifndef _OCEAN_SIMULATOR_H -#define _OCEAN_SIMULATOR_H - -#include "CommonInclude.h" -#include "wiGraphicsAPI.h" -#include "wiFFTGenerator.h" - -//#define CS_DEBUG_BUFFER -#define PAD16(n) (((n)+15)/16*16) - -struct OceanParameter -{ - // Must be power of 2. - int dmap_dim; - // Typical value is 1000 ~ 2000 - float patch_length; - - // Adjust the time interval for simulation. - float time_scale; - // Amplitude for transverse wave. Around 1.0 - float wave_amplitude; - // Wind direction. Normalization not required. - XMFLOAT2 wind_dir; - // Around 100 ~ 1000 - float wind_speed; - // This value damps out the waves against the wind direction. - // Smaller value means higher wind dependency. - float wind_dependency; - // The amplitude for longitudinal wave. Must be positive. - float choppy_scale; -}; - - -class OceanSimulator -{ -public: - OceanSimulator(OceanParameter& params); - ~OceanSimulator(); - - // -------------------------- Initialization & simulation routines ------------------------ - - // Update ocean wave when tick arrives. - void updateDisplacementMap(float time); - - // Texture access - wiGraphicsTypes::Texture2D* getD3D11DisplacementMap(); - wiGraphicsTypes::Texture2D* getD3D11GradientMap(); - - const OceanParameter& getParameters(); - - -protected: - OceanParameter m_param; - - // ---------------------------------- GPU shading asset ----------------------------------- - - // Displacement map - wiGraphicsTypes::Texture2D* m_pDisplacementMap; // (RGBA32F) - - // Gradient field - wiGraphicsTypes::Texture2D* m_pGradientMap; // (RGBA16F) - - // Initialize the vector field. - void initHeightMap(OceanParameter& params, XMFLOAT2* out_h0, float* out_omega); - - - // ----------------------------------- CS simulation data --------------------------------- - - // Initial height field H(0) generated by Phillips spectrum & Gauss distribution. - wiGraphicsTypes::GPUBuffer* m_pBuffer_Float2_H0; - - // Angular frequency - wiGraphicsTypes::GPUBuffer* m_pBuffer_Float_Omega; - - // Height field H(t), choppy field Dx(t) and Dy(t) in frequency domain, updated each frame. - wiGraphicsTypes::GPUBuffer* m_pBuffer_Float2_Ht; - - // Height & choppy buffer in the space domain, corresponding to H(t), Dx(t) and Dy(t) - wiGraphicsTypes::GPUBuffer* m_pBuffer_Float_Dxyz; - - wiGraphicsTypes::GPUBuffer* m_pQuadVB; - - // Shaders, layouts and constants - wiGraphicsTypes::ComputeShader* m_pUpdateSpectrumCS; - - wiGraphicsTypes::VertexShader* m_pQuadVS; - wiGraphicsTypes::PixelShader* m_pUpdateDisplacementPS; - wiGraphicsTypes::PixelShader* m_pGenGradientFoldingPS; - - wiGraphicsTypes::VertexLayout* m_pQuadLayout; - - wiGraphicsTypes::GPUBuffer* m_pImmutableCB; - wiGraphicsTypes::GPUBuffer* m_pPerFrameCB; - - wiGraphicsTypes::Sampler m_pPointSamplerState; - - // FFT wrap-up - CSFFT512x512_Plan m_fft_plan; - -#ifdef CS_DEBUG_BUFFER - wiGraphicsTypes::GPUBuffer* m_pDebugBuffer; -#endif -}; - -#endif // _OCEAN_SIMULATOR_H diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index 85ca413ac..c83360bad 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -24,6 +24,7 @@ #include "wiRectPacker.h" #include "wiBackLog.h" #include "wiProfiler.h" +#include #include @@ -76,6 +77,7 @@ int wiRenderer::visibleCount; wiRenderTarget wiRenderer::normalMapRT, wiRenderer::imagesRT, wiRenderer::imagesRTAdd; Camera *wiRenderer::cam = nullptr, *wiRenderer::refCam = nullptr, *wiRenderer::prevFrameCam = nullptr; PHYSICS* wiRenderer::physicsEngine = nullptr; +wiOcean* wiRenderer::ocean = nullptr; string wiRenderer::SHADERPATH = "shaders/"; #pragma endregion @@ -2125,6 +2127,12 @@ void wiRenderer::UpdateRenderData(GRAPHICSTHREAD threadID) hair->ComputeCulling(getCamera(), threadID); } + // Compute water simulation: + if (ocean != nullptr) + { + ocean->updateDisplacementMap(renderTime); + } + // Render out of date environment probes: RefreshEnvProbes(threadID); @@ -4632,6 +4640,11 @@ void wiRenderer::DrawWorldTransparent(Camera* camera, SHADERTYPE shaderType, Tex GetDevice()->BindResourcePS(resourceBuffers[RBTYPE_ENTITYINDEXLIST_TRANSPARENT], SBSLOT_ENTITYINDEXLIST, threadID); } + if (ocean != nullptr) + { + ocean->Render(camera, renderTime); + } + if (grass) { GetDevice()->BindDepthStencilState(depthStencils[DSSTYPE_HAIRALPHACOMPOSITION], STENCILREF_DEFAULT, threadID); // minimizes overdraw by depthcomp = less @@ -6567,3 +6580,13 @@ bool wiRenderer::GetAdvancedRefractionsEnabled() { return advancedRefractions && GetDevice()->CheckCapability(GraphicsDevice::GRAPHICSDEVICE_CAPABILITY_UNORDEREDACCESSTEXTURE_LOAD_FORMAT_EXT); } + +void wiRenderer::SetOceanEnabled(bool enabled, const wiOceanParameter& params) +{ + SAFE_DELETE(ocean); + + if (enabled) + { + ocean = new wiOcean(params); + } +} diff --git a/WickedEngine/wiRenderer.h b/WickedEngine/wiRenderer.h index 973508490..f8d1b27f6 100644 --- a/WickedEngine/wiRenderer.h +++ b/WickedEngine/wiRenderer.h @@ -42,6 +42,8 @@ struct Cullable; class PHYSICS; class wiRenderTarget; class wiWaterPlane; +class wiOcean; +struct wiOceanParameter; typedef std::map MeshCollection; typedef std::map MaterialCollection; @@ -534,6 +536,10 @@ public: static PHYSICS* physicsEngine; static void SynchronizeWithPhysicsEngine(float dt = 1.0f / 60.0f); + static wiOcean* ocean; + static void SetOceanEnabled(bool enabled, const wiOceanParameter& params); + static wiOcean* GetOcean() { return ocean; } + static Model* LoadModel(const std::string& dir, const std::string& name, const XMMATRIX& transform = XMMatrixIdentity(), const std::string& ident = "common"); static void LoadWorldInfo(const std::string& dir, const std::string& name); static void LoadDefaultLighting(); From e4ff903a5e49c80ebd3820998c8e59da2de03a16 Mon Sep 17 00:00:00 2001 From: Turanszki Janos Date: Sat, 11 Nov 2017 18:07:12 +0000 Subject: [PATCH 04/10] ocean rendering updates --- Editor/CameraWindow.cpp | 2 +- Editor/OceanWindow.cpp | 2 +- WickedEngine/fft_512x512_c2c_CS.hlsl | 18 +++++++++--------- WickedEngine/wiGraphicsDevice_DX11.cpp | 2 +- WickedEngine/wiOcean.cpp | 23 +++++++++++++---------- WickedEngine/wiOcean.h | 14 +++++++------- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/Editor/CameraWindow.cpp b/Editor/CameraWindow.cpp index 0b2268a13..a064ddfba 100644 --- a/Editor/CameraWindow.cpp +++ b/Editor/CameraWindow.cpp @@ -73,7 +73,7 @@ CameraWindow::CameraWindow(wiGUI* gui) :GUI(gui) - cameraWindow->Translate(XMFLOAT3(30, 30, 0)); + cameraWindow->Translate(XMFLOAT3(760, 500, 0)); cameraWindow->SetVisible(false); } diff --git a/Editor/OceanWindow.cpp b/Editor/OceanWindow.cpp index 87d236fa7..9f6dd15b2 100644 --- a/Editor/OceanWindow.cpp +++ b/Editor/OceanWindow.cpp @@ -27,7 +27,7 @@ OceanWindow::OceanWindow(wiGUI* gui) :GUI(gui) - oceanWindow->Translate(XMFLOAT3(30, 30, 0)); + oceanWindow->Translate(XMFLOAT3(800, 50, 0)); oceanWindow->SetVisible(false); } diff --git a/WickedEngine/fft_512x512_c2c_CS.hlsl b/WickedEngine/fft_512x512_c2c_CS.hlsl index 26286a559..338bb5de4 100644 --- a/WickedEngine/fft_512x512_c2c_CS.hlsl +++ b/WickedEngine/fft_512x512_c2c_CS.hlsl @@ -6,7 +6,7 @@ #define COHERENCY_GRANULARITY 128 - + void FT2(inout float2 a, inout float2 b) { float t; @@ -101,20 +101,20 @@ void main(uint3 thread_id : SV_DispatchThreadID) float2 D[8]; uint i; - uint imod = thread_id & (istride - 1); - uint iaddr = ((thread_id - imod) << 3) + imod; + uint imod = thread_id.x & (istride - 1); + uint iaddr = ((thread_id.x - imod) << 3) + imod; for (i = 0; i < 8; i++) D[i] = g_SrcData[iaddr + i * istride]; // Math FFT_forward_8(D); - uint p = thread_id & (istride - pstride); + uint p = thread_id.x & (istride - pstride); float phase = phase_base * (float)p; TWIDDLE_8(D, phase); // Store the result - uint omod = thread_id & (ostride - 1); - uint oaddr = ((thread_id - omod) << 3) + omod; + uint omod = thread_id.x & (ostride - 1); + uint oaddr = ((thread_id.x - omod) << 3) + omod; g_DstData[oaddr + 0 * ostride] = D[0]; g_DstData[oaddr + 1 * ostride] = D[4]; g_DstData[oaddr + 2 * ostride] = D[2]; @@ -136,7 +136,7 @@ void main(uint3 thread_id : SV_DispatchThreadID) // Fetch 8 complex numbers uint i; float2 D[8]; - uint iaddr = thread_id << 3; + uint iaddr = thread_id.x << 3; for (i = 0; i < 8; i++) D[i] = g_SrcData[iaddr + i]; @@ -144,8 +144,8 @@ void main(uint3 thread_id : SV_DispatchThreadID) FFT_forward_8(D); // Store the result - uint omod = thread_id & (ostride - 1); - uint oaddr = ((thread_id - omod) << 3) + omod; + uint omod = thread_id.x & (ostride - 1); + uint oaddr = ((thread_id.x - omod) << 3) + omod; g_DstData[oaddr + 0 * ostride] = D[0]; g_DstData[oaddr + 1 * ostride] = D[4]; g_DstData[oaddr + 2 * ostride] = D[2]; diff --git a/WickedEngine/wiGraphicsDevice_DX11.cpp b/WickedEngine/wiGraphicsDevice_DX11.cpp index 961185cb9..110a7842d 100644 --- a/WickedEngine/wiGraphicsDevice_DX11.cpp +++ b/WickedEngine/wiGraphicsDevice_DX11.cpp @@ -1405,7 +1405,7 @@ GraphicsDevice_DX11::GraphicsDevice_DX11(wiWindowRegistration::window_type windo } UINT createDeviceFlags = 0; - createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; + //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; D3D_DRIVER_TYPE driverTypes[] = { diff --git a/WickedEngine/wiOcean.cpp b/WickedEngine/wiOcean.cpp index 5ce5d3e61..578323d4c 100644 --- a/WickedEngine/wiOcean.cpp +++ b/WickedEngine/wiOcean.cpp @@ -679,14 +679,14 @@ void wiOcean::initRenderResource() RasterizerStateDesc ras_desc; ras_desc.FillMode = FILL_SOLID; ras_desc.CullMode = CULL_NONE; - ras_desc.FrontCounterClockwise = FALSE; + ras_desc.FrontCounterClockwise = false; ras_desc.DepthBias = 0; ras_desc.SlopeScaledDepthBias = 0.0f; ras_desc.DepthBiasClamp = 0.0f; - ras_desc.DepthClipEnable = TRUE; - ras_desc.ScissorEnable = FALSE; - ras_desc.MultisampleEnable = TRUE; - ras_desc.AntialiasedLineEnable = FALSE; + ras_desc.DepthClipEnable = true; + ras_desc.ScissorEnable = false; + ras_desc.MultisampleEnable = true; + ras_desc.AntialiasedLineEnable = false; g_pRSState_Solid = new RasterizerState; device->CreateRasterizerState(&ras_desc, g_pRSState_Solid); @@ -698,16 +698,18 @@ void wiOcean::initRenderResource() DepthStencilStateDesc depth_desc; memset(&depth_desc, 0, sizeof(DepthStencilStateDesc)); - depth_desc.DepthEnable = FALSE; - depth_desc.StencilEnable = FALSE; + depth_desc.DepthEnable = true; + depth_desc.DepthWriteMask = DEPTH_WRITE_MASK_ALL; + depth_desc.DepthFunc = COMPARISON_GREATER; + depth_desc.StencilEnable = false; g_pDSState_Disable = new DepthStencilState; device->CreateDepthStencilState(&depth_desc, g_pDSState_Disable); BlendStateDesc blend_desc; memset(&blend_desc, 0, sizeof(BlendStateDesc)); - blend_desc.AlphaToCoverageEnable = FALSE; - blend_desc.IndependentBlendEnable = FALSE; - blend_desc.RenderTarget[0].BlendEnable = TRUE; + blend_desc.AlphaToCoverageEnable = false; + blend_desc.IndependentBlendEnable = false; + blend_desc.RenderTarget[0].BlendEnable = true; blend_desc.RenderTarget[0].SrcBlend = BLEND_SRC_ALPHA; blend_desc.RenderTarget[0].DestBlend = BLEND_INV_SRC_ALPHA; blend_desc.RenderTarget[0].BlendOp = BLEND_OP_ADD; @@ -1439,6 +1441,7 @@ void wiOcean::Render(const Camera* camera, float time) // State blocks device->BindRasterizerState(wire ? g_pRSState_Wireframe : g_pRSState_Solid, threadID); + device->BindDepthStencilState(g_pDSState_Disable, 0, threadID); // Constants device->BindConstantBufferVS(g_pShadingCB, CB_GETBINDSLOT(Ocean_Rendering_ShadingCB), threadID); diff --git a/WickedEngine/wiOcean.h b/WickedEngine/wiOcean.h index e18ea18d5..e90c0177a 100644 --- a/WickedEngine/wiOcean.h +++ b/WickedEngine/wiOcean.h @@ -48,14 +48,14 @@ struct wiOceanParameter wiOceanParameter() { - choppy_scale = 1; dmap_dim = 512; - patch_length = 1000; - time_scale = 1; - wave_amplitude = 1.0f; - wind_dependency = 0.5f; - wind_dir = XMFLOAT2(1, 1); - wind_speed = 100; + patch_length = 2000.0f; + time_scale = 0.8f; + wave_amplitude = 0.35f; + wind_dir = XMFLOAT2(0.8f, 0.6f); + wind_speed = 600.0f; + wind_dependency = 0.07f; + choppy_scale = 1.3f; } }; From f918266848ff3fc41462fd8bc406f1a69a26f906 Mon Sep 17 00:00:00 2001 From: Turanszki Janos Date: Sat, 11 Nov 2017 23:36:06 +0000 Subject: [PATCH 05/10] updated ocean simulator --- Editor/Editor.cpp | 4 +- WickedEngine/wiFFTGenerator.cpp | 34 ++++-- WickedEngine/wiFFTGenerator.h | 23 +--- WickedEngine/wiInitializer.cpp | 3 + WickedEngine/wiOcean.cpp | 208 ++++++++++++-------------------- WickedEngine/wiOcean.h | 46 +++---- WickedEngine/wiRenderer.cpp | 4 +- 7 files changed, 124 insertions(+), 198 deletions(-) diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 3bef1efc1..4d7f97189 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -952,11 +952,11 @@ void EditorComponent::Update(float dt) } if (wiInputManager::GetInstance()->down(VK_UP)) { - yDif += buttonrotSpeed; + yDif -= buttonrotSpeed; } if (wiInputManager::GetInstance()->down(VK_DOWN)) { - yDif -= buttonrotSpeed; + yDif += buttonrotSpeed; } Camera* cam = wiRenderer::getCamera(); diff --git a/WickedEngine/wiFFTGenerator.cpp b/WickedEngine/wiFFTGenerator.cpp index 2da42821e..4dba3a694 100644 --- a/WickedEngine/wiFFTGenerator.cpp +++ b/WickedEngine/wiFFTGenerator.cpp @@ -9,13 +9,15 @@ using namespace wiGraphicsTypes; -static const GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE; // todo: make dynamic +ComputeShader* CSFFT_512x512_Data_t::pRadix008A_CS = nullptr; +ComputeShader* CSFFT_512x512_Data_t::pRadix008A_CS2 = nullptr; void radix008A(CSFFT512x512_Plan* fft_plan, GPUUnorderedResource* pUAV_Dst, GPUResource* pSRV_Src, UINT thread_count, - UINT istride) + UINT istride, + GRAPHICSTHREAD threadID) { // Setup execution configuration UINT grid = thread_count / COHERENCY_GRANULARITY; @@ -46,7 +48,8 @@ void radix008A(CSFFT512x512_Plan* fft_plan, void fft_512x512_c2c(CSFFT512x512_Plan* fft_plan, GPUUnorderedResource* pUAV_Dst, GPUResource* pSRV_Dst, - GPUResource* pSRV_Src) + GPUResource* pSRV_Src, + GRAPHICSTHREAD threadID) { const UINT thread_count = fft_plan->slices * (512 * 512) / 8; GPUUnorderedResource* pUAV_Tmp = fft_plan->pUAV_Tmp; @@ -57,32 +60,32 @@ void fft_512x512_c2c(CSFFT512x512_Plan* fft_plan, UINT istride = 512 * 512 / 8; cs_cbs = fft_plan->pRadix008A_CB[0]; device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); - radix008A(fft_plan, pUAV_Tmp, pSRV_Src, thread_count, istride); + radix008A(fft_plan, pUAV_Tmp, pSRV_Src, thread_count, istride, threadID); istride /= 8; cs_cbs = fft_plan->pRadix008A_CB[1]; device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); - radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride); + radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride, threadID); istride /= 8; cs_cbs = fft_plan->pRadix008A_CB[2]; device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); - radix008A(fft_plan, pUAV_Tmp, pSRV_Dst, thread_count, istride); + radix008A(fft_plan, pUAV_Tmp, pSRV_Dst, thread_count, istride, threadID); istride /= 8; cs_cbs = fft_plan->pRadix008A_CB[3]; device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); - radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride); + radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride, threadID); istride /= 8; cs_cbs = fft_plan->pRadix008A_CB[4]; device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); - radix008A(fft_plan, pUAV_Tmp, pSRV_Dst, thread_count, istride); + radix008A(fft_plan, pUAV_Tmp, pSRV_Dst, thread_count, istride, threadID); istride /= 8; cs_cbs = fft_plan->pRadix008A_CB[5]; device->BindConstantBufferCS(&cs_cbs[0], CB_GETBINDSLOT(FFTGeneratorCB), threadID); - radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride); + radix008A(fft_plan, pUAV_Dst, pSRV_Tmp, thread_count, istride, threadID); } void create_cbuffers_512x512(CSFFT512x512_Plan* plan, GraphicsDevice* device, UINT slices) @@ -185,9 +188,6 @@ void fft512x512_create_plan(CSFFT512x512_Plan* plan, UINT slices) plan->slices = slices; - plan->pRadix008A_CS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "fft_512x512_c2c_CS.cso", wiResourceManager::COMPUTESHADER)); - plan->pRadix008A_CS2 = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "fft_512x512_c2c_v2_CS.cso", wiResourceManager::COMPUTESHADER)); - // Constants // Create 6 cbuffers for 512x512 transform @@ -218,3 +218,13 @@ void fft512x512_destroy_plan(CSFFT512x512_Plan* plan) for (int i = 0; i < 6; i++) SAFE_DELETE(plan->pRadix008A_CB[i]); } + + + +void CSFFT_512x512_Data_t::LoadShaders() +{ + + pRadix008A_CS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "fft_512x512_c2c_CS.cso", wiResourceManager::COMPUTESHADER)); + pRadix008A_CS2 = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "fft_512x512_c2c_v2_CS.cso", wiResourceManager::COMPUTESHADER)); + +} diff --git a/WickedEngine/wiFFTGenerator.h b/WickedEngine/wiFFTGenerator.h index 655a57407..651eef567 100644 --- a/WickedEngine/wiFFTGenerator.h +++ b/WickedEngine/wiFFTGenerator.h @@ -1,20 +1,6 @@ #ifndef _FFT_GENERATOR_H_ #define _FFT_GENERATOR_H_ -// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. -// -// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED -// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS -// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA -// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR -// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS -// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY -// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, -// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// Please direct any bugs or questions to SDKFeedback@nvidia.com - #include "CommonInclude.h" #include "wiGraphicsAPI.h" @@ -29,8 +15,8 @@ typedef struct CSFFT_512x512_Data_t { - wiGraphicsTypes::ComputeShader* pRadix008A_CS; - wiGraphicsTypes::ComputeShader* pRadix008A_CS2; + static wiGraphicsTypes::ComputeShader* pRadix008A_CS; + static wiGraphicsTypes::ComputeShader* pRadix008A_CS2; // More than one array can be transformed at same time UINT slices; @@ -42,6 +28,8 @@ typedef struct CSFFT_512x512_Data_t wiGraphicsTypes::GPUBuffer* pBuffer_Tmp; wiGraphicsTypes::GPUUnorderedResource* pUAV_Tmp; wiGraphicsTypes::GPUResource* pSRV_Tmp; + + static void LoadShaders(); } CSFFT512x512_Plan; //////////////////////////////////////////////////////////////////////////////// @@ -62,7 +50,8 @@ void fft512x512_destroy_plan(CSFFT512x512_Plan* plan); void fft_512x512_c2c(CSFFT512x512_Plan* fft_plan, wiGraphicsTypes::GPUUnorderedResource* pUAV_Dst, wiGraphicsTypes::GPUResource* pSRV_Dst, - wiGraphicsTypes::GPUResource* pSRV_Src); + wiGraphicsTypes::GPUResource* pSRV_Src, + GRAPHICSTHREAD threadID); #endif // _FFT_GENERATOR_H_ diff --git a/WickedEngine/wiInitializer.cpp b/WickedEngine/wiInitializer.cpp index 217b72027..f4d697b15 100644 --- a/WickedEngine/wiInitializer.cpp +++ b/WickedEngine/wiInitializer.cpp @@ -7,6 +7,7 @@ #include "wiBackLog.h" #include "wiCpuInfo.h" #include "wiSound.h" +#include "wiOcean.h" #include "wiHelper.h" using namespace std; @@ -28,6 +29,8 @@ namespace wiInitializer wiFont::Initialize(); wiFont::SetUpStaticComponents(); + wiOcean::SetUpStatic(); + if (FAILED(wiSoundEffect::Initialize()) || FAILED(wiMusic::Initialize())) { stringstream ss(""); diff --git a/WickedEngine/wiOcean.cpp b/WickedEngine/wiOcean.cpp index 578323d4c..4762b5a56 100644 --- a/WickedEngine/wiOcean.cpp +++ b/WickedEngine/wiOcean.cpp @@ -6,7 +6,15 @@ using namespace wiGraphicsTypes; using namespace std; -static const GRAPHICSTHREAD threadID = GRAPHICSTHREAD_IMMEDIATE; // todo: make dynamic +ComputeShader* wiOcean::m_pUpdateSpectrumCS = nullptr; +VertexShader* wiOcean::m_pQuadVS = nullptr; +PixelShader* wiOcean::m_pUpdateDisplacementPS = nullptr; +PixelShader* wiOcean::m_pGenGradientFoldingPS = nullptr; +VertexShader* wiOcean::g_pOceanSurfVS = nullptr; +PixelShader* wiOcean::g_pWireframePS = nullptr; +PixelShader* wiOcean::g_pOceanSurfPS = nullptr; +VertexLayout* wiOcean::m_pQuadLayout = nullptr; +VertexLayout* wiOcean::g_pMeshLayout = nullptr; // Disable warning "conditional expression is constant" #pragma warning(disable:4127) @@ -207,68 +215,6 @@ wiOcean::wiOcean(const wiOceanParameter& params) wiRenderer::GetDevice()->CreateSamplerState(&sam_desc, &m_pPointSamplerState); - m_pUpdateSpectrumCS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSimulatorCS.cso", wiResourceManager::COMPUTESHADER)); - - { - VertexLayoutDesc layout[] = - { - { "POSITION", 0, FORMAT_R32G32B32A32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, - }; - UINT numElements = ARRAYSIZE(layout); - VertexShaderInfo* vsinfo = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanQuadVS.cso", wiResourceManager::VERTEXSHADER, layout, numElements)); - if (vsinfo != nullptr) { - m_pQuadVS = vsinfo->vertexShader; - m_pQuadLayout = vsinfo->vertexLayout; - } - } - - m_pUpdateDisplacementPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanUpdateDisplacementPS.cso", wiResourceManager::PIXELSHADER)); - m_pGenGradientFoldingPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanGradientFoldingPS.cso", wiResourceManager::PIXELSHADER)); - - - - //// Compute shaders - //ID3DBlob* pBlobUpdateSpectrumCS = NULL; - - //CompileShaderFromFile(L"ocean_simulator_cs.hlsl", "UpdateSpectrumCS", "cs_4_0", &pBlobUpdateSpectrumCS); - //assert(pBlobUpdateSpectrumCS); - - //m_device->CreateComputeShader(pBlobUpdateSpectrumCS->GetBufferPointer(), pBlobUpdateSpectrumCS->GetBufferSize(), NULL, &m_pUpdateSpectrumCS); - //assert(m_pUpdateSpectrumCS); - - //SAFE_DELETE(pBlobUpdateSpectrumCS); - - //// Vertex & pixel shaders - //ID3DBlob* pBlobQuadVS = NULL; - //ID3DBlob* pBlobUpdateDisplacementPS = NULL; - //ID3DBlob* pBlobGenGradientFoldingPS = NULL; - - //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "QuadVS", "vs_4_0", &pBlobQuadVS); - //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "UpdateDisplacementPS", "ps_4_0", &pBlobUpdateDisplacementPS); - //CompileShaderFromFile(L"ocean_simulator_vs_ps.hlsl", "GenGradientFoldingPS", "ps_4_0", &pBlobGenGradientFoldingPS); - //assert(pBlobQuadVS); - //assert(pBlobUpdateDisplacementPS); - //assert(pBlobGenGradientFoldingPS); - - //m_device->CreateVertexShader(pBlobQuadVS->GetBufferPointer(), pBlobQuadVS->GetBufferSize(), NULL, &m_pQuadVS); - //m_device->CreatePixelShader(pBlobUpdateDisplacementPS->GetBufferPointer(), pBlobUpdateDisplacementPS->GetBufferSize(), NULL, &m_pUpdateDisplacementPS); - //m_device->CreatePixelShader(pBlobGenGradientFoldingPS->GetBufferPointer(), pBlobGenGradientFoldingPS->GetBufferSize(), NULL, &m_pGenGradientFoldingPS); - //assert(m_pQuadVS); - //assert(m_pUpdateDisplacementPS); - //assert(m_pGenGradientFoldingPS); - //SAFE_DELETE(pBlobUpdateDisplacementPS); - //SAFE_DELETE(pBlobGenGradientFoldingPS); - - //// Input layout - //INPUT_ELEMENT_DESC quad_layout_desc[] = - //{ - // { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, - //}; - //m_device->CreateInputLayout(quad_layout_desc, 1, pBlobQuadVS->GetBufferPointer(), pBlobQuadVS->GetBufferSize(), &m_pQuadLayout); - //assert(m_pQuadLayout); - - //SAFE_DELETE(pBlobQuadVS); - // Quad vertex buffer GPUBufferDesc vb_desc; vb_desc.ByteWidth = 4 * sizeof(XMFLOAT4); @@ -324,23 +270,7 @@ wiOcean::wiOcean(const wiOceanParameter& params) // FFT fft512x512_create_plan(&m_fft_plan, 3); -#ifdef CS_DEBUG_BUFFER - GPUBufferDesc buf_desc; - buf_desc.ByteWidth = 3 * input_half_size * float2_stride; - buf_desc.Usage = USAGE_STAGING; - buf_desc.BindFlags = 0; - buf_desc.CPUAccessFlags = CPU_ACCESS_READ; - buf_desc.MiscFlags = RESOURCE_MISC_BUFFER_STRUCTURED; - buf_desc.StructureByteStride = float2_stride; - - m_pDebugBuffer = new GPUBuffer; - wiRenderer::GetDevice()->CreateBuffer(&buf_desc, NULL, m_pDebugBuffer); -#endif - initRenderResource(); - createSurfaceMesh(); - createFresnelMap(); - loadTextures(); } wiOcean::~wiOcean() @@ -357,19 +287,10 @@ wiOcean::~wiOcean() SAFE_DELETE(m_pDisplacementMap); SAFE_DELETE(m_pGradientMap); - SAFE_DELETE(m_pUpdateSpectrumCS); - SAFE_DELETE(m_pQuadVS); - SAFE_DELETE(m_pUpdateDisplacementPS); - SAFE_DELETE(m_pGenGradientFoldingPS); - - SAFE_DELETE(m_pQuadLayout); SAFE_DELETE(m_pImmutableCB); SAFE_DELETE(m_pPerFrameCB); -#ifdef CS_DEBUG_BUFFER - SAFE_DELETE(m_pDebugBuffer); -#endif cleanupRenderResource(); } @@ -428,7 +349,7 @@ void wiOcean::initHeightMap(XMFLOAT2* out_h0, float* out_omega) } } -void wiOcean::updateDisplacementMap(float time) +void wiOcean::UpdateDisplacementMap(float time, GRAPHICSTHREAD threadID) { GraphicsDevice* device = wiRenderer::GetDevice(); @@ -474,7 +395,7 @@ void wiOcean::updateDisplacementMap(float time) // ------------------------------------ Perform FFT ------------------------------------------- - fft_512x512_c2c(&m_fft_plan, m_pBuffer_Float_Dxyz, m_pBuffer_Float_Dxyz, m_pBuffer_Float2_Ht); + fft_512x512_c2c(&m_fft_plan, m_pBuffer_Float_Dxyz, m_pBuffer_Float_Dxyz, m_pBuffer_Float2_Ht, threadID); // --------------------------------- Wrap Dx, Dy and Dz --------------------------------------- // Push RT @@ -552,26 +473,6 @@ void wiOcean::updateDisplacementMap(float time) device->GenerateMips(m_pGradientMap, threadID); - // Define CS_DEBUG_BUFFER to enable writing a buffer into a file. -#ifdef CS_DEBUG_BUFFER - { - m_pd3dImmediateContext->CopyResource(m_pDebugBuffer, m_pBuffer_Float_Dxyz); - MAPPED_SUBRESOURCE mapped_res; - m_pd3dImmediateContext->Map(m_pDebugBuffer, 0, MAP_READ, 0, &mapped_res); - - // set a break point below, and drag MappedResource.pData into in your Watch window - // and cast it as (float*) - - // Write to disk - XMFLOAT2* v = (XMFLOAT2*)mapped_res.pData; - - FILE* fp = fopen(".\\tmp\\Ht_raw.dat", "wb"); - fwrite(v, 512 * 512 * sizeof(float) * 2 * 3, 1, fp); - fclose(fp); - - m_pd3dImmediateContext->Unmap(m_pDebugBuffer, 0); - } -#endif device->EventEnd(threadID); } @@ -614,22 +515,6 @@ void wiOcean::initRenderResource() createFresnelMap(); loadTextures(); - { - VertexLayoutDesc layout[] = - { - { "POSITION", 0, FORMAT_R32G32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, - }; - UINT numElements = ARRAYSIZE(layout); - VertexShaderInfo* vsinfo = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSurfaceVS.cso", wiResourceManager::VERTEXSHADER, layout, numElements)); - if (vsinfo != nullptr) { - g_pOceanSurfVS = vsinfo->vertexShader; - g_pMeshLayout = vsinfo->vertexLayout; - } - } - - g_pOceanSurfPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSurfacePS.cso", wiResourceManager::PIXELSHADER)); - g_pWireframePS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSurfaceSimplePS.cso", wiResourceManager::PIXELSHADER)); - // Constants GPUBufferDesc cb_desc; @@ -725,11 +610,6 @@ void wiOcean::cleanupRenderResource() { SAFE_DELETE(g_pMeshIB); SAFE_DELETE(g_pMeshVB); - SAFE_DELETE(g_pMeshLayout); - - SAFE_DELETE(g_pOceanSurfVS); - SAFE_DELETE(g_pOceanSurfPS); - SAFE_DELETE(g_pWireframePS); SAFE_DELETE(g_pFresnelMap); SAFE_DELETE(g_pPerlinMap); @@ -1108,7 +988,7 @@ void wiOcean::createFresnelMap() void wiOcean::loadTextures() { - wiRenderer::GetDevice()->CreateTextureFromFile("perlin.dds", &g_pPerlinMap, true, GRAPHICSTHREAD_IMMEDIATE); + wiRenderer::GetDevice()->CreateTextureFromFile("perlin_noise.dds", &g_pPerlinMap, true, GRAPHICSTHREAD_IMMEDIATE); } bool wiOcean::checkNodeVisibility(const QuadNode& quad_node, const Camera& camera) @@ -1403,7 +1283,7 @@ int wiOcean::buildNodeList(QuadNode& quad_node, const Camera& camera) return position; } -void wiOcean::Render(const Camera* camera, float time) +void wiOcean::Render(const Camera* camera, float time, GRAPHICSTHREAD threadID) { GraphicsDevice* device = wiRenderer::GetDevice(); bool wire = wiRenderer::IsWireRender(); @@ -1521,3 +1401,65 @@ void wiOcean::Render(const Camera* camera, float time) //pd3dContext->PSSetShaderResources(1, 4, &ps_srvs[0]); } + +void wiOcean::LoadShaders() +{ + + m_pUpdateSpectrumCS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSimulatorCS.cso", wiResourceManager::COMPUTESHADER)); + + { + VertexLayoutDesc layout[] = + { + { "POSITION", 0, FORMAT_R32G32B32A32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, + }; + UINT numElements = ARRAYSIZE(layout); + VertexShaderInfo* vsinfo = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanQuadVS.cso", wiResourceManager::VERTEXSHADER, layout, numElements)); + if (vsinfo != nullptr) { + m_pQuadVS = vsinfo->vertexShader; + m_pQuadLayout = vsinfo->vertexLayout; + } + } + + m_pUpdateDisplacementPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanUpdateDisplacementPS.cso", wiResourceManager::PIXELSHADER)); + m_pGenGradientFoldingPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanGradientFoldingPS.cso", wiResourceManager::PIXELSHADER)); + + + { + VertexLayoutDesc layout[] = + { + { "POSITION", 0, FORMAT_R32G32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, + }; + UINT numElements = ARRAYSIZE(layout); + VertexShaderInfo* vsinfo = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSurfaceVS.cso", wiResourceManager::VERTEXSHADER, layout, numElements)); + if (vsinfo != nullptr) { + g_pOceanSurfVS = vsinfo->vertexShader; + g_pMeshLayout = vsinfo->vertexLayout; + } + } + + g_pOceanSurfPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSurfacePS.cso", wiResourceManager::PIXELSHADER)); + g_pWireframePS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSurfaceSimplePS.cso", wiResourceManager::PIXELSHADER)); + +} + +void wiOcean::SetUpStatic() +{ + LoadShaders(); + CSFFT_512x512_Data_t::LoadShaders(); +} + +void wiOcean::CleanUpStatic() +{ + SAFE_DELETE(m_pUpdateSpectrumCS); + SAFE_DELETE(m_pQuadVS); + SAFE_DELETE(m_pUpdateDisplacementPS); + SAFE_DELETE(m_pGenGradientFoldingPS); + + SAFE_DELETE(m_pQuadLayout); + + SAFE_DELETE(g_pMeshLayout); + + SAFE_DELETE(g_pOceanSurfVS); + SAFE_DELETE(g_pOceanSurfPS); + SAFE_DELETE(g_pWireframePS); +} diff --git a/WickedEngine/wiOcean.h b/WickedEngine/wiOcean.h index e90c0177a..ec909b58f 100644 --- a/WickedEngine/wiOcean.h +++ b/WickedEngine/wiOcean.h @@ -1,17 +1,3 @@ -// Copyright (c) 2011 NVIDIA Corporation. All rights reserved. -// -// TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED -// *AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS -// OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA -// OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, SPECIAL, INCIDENTAL, INDIRECT, OR -// CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS -// OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY -// OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, -// EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -// -// Please direct any bugs or questions to SDKFeedback@nvidia.com - #ifndef _OCEAN_SIMULATOR_H #define _OCEAN_SIMULATOR_H @@ -21,7 +7,6 @@ #include -//#define CS_DEBUG_BUFFER struct Camera; @@ -69,7 +54,8 @@ public: // -------------------------- Initialization & simulation routines ------------------------ // Update ocean wave when tick arrives. - void updateDisplacementMap(float time); + void UpdateDisplacementMap(float time, GRAPHICSTHREAD threadID); + void Render(const Camera* camera, float time, GRAPHICSTHREAD threadID); // Texture access wiGraphicsTypes::Texture2D* getDisplacementMap(); @@ -77,8 +63,9 @@ public: const wiOceanParameter& getParameters(); - void Render(const Camera* camera, float time); - + static void LoadShaders(); + static void SetUpStatic(); + static void CleanUpStatic(); protected: wiOceanParameter m_param; @@ -109,13 +96,12 @@ protected: wiGraphicsTypes::GPUBuffer* m_pQuadVB; // Shaders, layouts and constants - wiGraphicsTypes::ComputeShader* m_pUpdateSpectrumCS; + static wiGraphicsTypes::ComputeShader* m_pUpdateSpectrumCS; + static wiGraphicsTypes::VertexShader* m_pQuadVS; + static wiGraphicsTypes::PixelShader* m_pUpdateDisplacementPS; + static wiGraphicsTypes::PixelShader* m_pGenGradientFoldingPS; - wiGraphicsTypes::VertexShader* m_pQuadVS; - wiGraphicsTypes::PixelShader* m_pUpdateDisplacementPS; - wiGraphicsTypes::PixelShader* m_pGenGradientFoldingPS; - - wiGraphicsTypes::VertexLayout* m_pQuadLayout; + static wiGraphicsTypes::VertexLayout* m_pQuadLayout; wiGraphicsTypes::GPUBuffer* m_pImmutableCB; wiGraphicsTypes::GPUBuffer* m_pPerFrameCB; @@ -125,10 +111,6 @@ protected: // FFT wrap-up CSFFT512x512_Plan m_fft_plan; -#ifdef CS_DEBUG_BUFFER - wiGraphicsTypes::GPUBuffer* m_pDebugBuffer; -#endif - // Rendering params: @@ -211,7 +193,7 @@ protected: // D3D11 buffers and layout wiGraphicsTypes::GPUBuffer* g_pMeshVB = nullptr; wiGraphicsTypes::GPUBuffer* g_pMeshIB = nullptr; - wiGraphicsTypes::VertexLayout* g_pMeshLayout = nullptr; + static wiGraphicsTypes::VertexLayout* g_pMeshLayout; // Color look up 1D texture wiGraphicsTypes::Texture1D* g_pFresnelMap = nullptr; @@ -220,9 +202,9 @@ protected: wiGraphicsTypes::Texture2D* g_pPerlinMap = nullptr; // HLSL shaders - wiGraphicsTypes::VertexShader* g_pOceanSurfVS = nullptr; - wiGraphicsTypes::PixelShader* g_pOceanSurfPS = nullptr; - wiGraphicsTypes::PixelShader* g_pWireframePS = nullptr; + static wiGraphicsTypes::VertexShader* g_pOceanSurfVS; + static wiGraphicsTypes::PixelShader* g_pOceanSurfPS; + static wiGraphicsTypes::PixelShader* g_pWireframePS; wiGraphicsTypes::GPUBuffer* g_pPerCallCB = nullptr; wiGraphicsTypes::GPUBuffer* g_pPerFrameCB = nullptr; diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index c83360bad..8eee7be75 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -2130,7 +2130,7 @@ void wiRenderer::UpdateRenderData(GRAPHICSTHREAD threadID) // Compute water simulation: if (ocean != nullptr) { - ocean->updateDisplacementMap(renderTime); + ocean->UpdateDisplacementMap(renderTime, threadID); } // Render out of date environment probes: @@ -4642,7 +4642,7 @@ void wiRenderer::DrawWorldTransparent(Camera* camera, SHADERTYPE shaderType, Tex if (ocean != nullptr) { - ocean->Render(camera, renderTime); + ocean->Render(camera, renderTime, threadID); } if (grass) From 1eea400afcdb4dc11d1145822b09cd22c93c37a7 Mon Sep 17 00:00:00 2001 From: Turanszki Janos Date: Sun, 12 Nov 2017 00:08:35 +0000 Subject: [PATCH 06/10] updated ocean --- Editor/OceanWindow.cpp | 50 +++++++++++++++++++++++++++++++++ Editor/OceanWindow.h | 5 ++++ WickedEngine/wiFFTGenerator.cpp | 2 -- WickedEngine/wiOcean.cpp | 25 ++++++++--------- WickedEngine/wiOcean.h | 4 --- 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/Editor/OceanWindow.cpp b/Editor/OceanWindow.cpp index 9f6dd15b2..4e8a484c3 100644 --- a/Editor/OceanWindow.cpp +++ b/Editor/OceanWindow.cpp @@ -26,6 +26,56 @@ OceanWindow::OceanWindow(wiGUI* gui) :GUI(gui) oceanWindow->AddWidget(enabledCheckBox); + patchSizeSlider = new wiSlider(1, 2000, 1000, 100000, "Patch size: "); + patchSizeSlider->SetSize(XMFLOAT2(100, 30)); + patchSizeSlider->SetPos(XMFLOAT2(x, y += inc)); + patchSizeSlider->SetValue(params.patch_length); + patchSizeSlider->OnSlide([&](wiEventArgs args) { + params.patch_length = args.fValue; + wiRenderer::SetOceanEnabled(enabledCheckBox->GetCheck(), params); + }); + oceanWindow->AddWidget(patchSizeSlider); + + waveAmplitudeSlider = new wiSlider(0, 10, 1000, 100000, "Wave amplitude: "); + waveAmplitudeSlider->SetSize(XMFLOAT2(100, 30)); + waveAmplitudeSlider->SetPos(XMFLOAT2(x, y += inc)); + waveAmplitudeSlider->SetValue(params.wave_amplitude); + waveAmplitudeSlider->OnSlide([&](wiEventArgs args) { + params.wave_amplitude = args.fValue; + wiRenderer::SetOceanEnabled(enabledCheckBox->GetCheck(), params); + }); + oceanWindow->AddWidget(waveAmplitudeSlider); + + choppyScaleSlider = new wiSlider(0, 10, 1000, 100000, "Choppiness: "); + choppyScaleSlider->SetSize(XMFLOAT2(100, 30)); + choppyScaleSlider->SetPos(XMFLOAT2(x, y += inc)); + choppyScaleSlider->SetValue(params.choppy_scale); + choppyScaleSlider->OnSlide([&](wiEventArgs args) { + params.choppy_scale = args.fValue; + wiRenderer::SetOceanEnabled(enabledCheckBox->GetCheck(), params); + }); + oceanWindow->AddWidget(choppyScaleSlider); + + windDependencySlider = new wiSlider(0, 1, 1000, 100000, "Wind dependency: "); + windDependencySlider->SetSize(XMFLOAT2(100, 30)); + windDependencySlider->SetPos(XMFLOAT2(x, y += inc)); + windDependencySlider->SetValue(params.wind_dependency); + windDependencySlider->OnSlide([&](wiEventArgs args) { + params.wind_dependency = args.fValue; + wiRenderer::SetOceanEnabled(enabledCheckBox->GetCheck(), params); + }); + oceanWindow->AddWidget(windDependencySlider); + + timeScaleSlider = new wiSlider(0, 4, 1000, 100000, "Time scale: "); + timeScaleSlider->SetSize(XMFLOAT2(100, 30)); + timeScaleSlider->SetPos(XMFLOAT2(x, y += inc)); + timeScaleSlider->SetValue(params.time_scale); + timeScaleSlider->OnSlide([&](wiEventArgs args) { + params.time_scale = args.fValue; + wiRenderer::SetOceanEnabled(enabledCheckBox->GetCheck(), params); + }); + oceanWindow->AddWidget(timeScaleSlider); + oceanWindow->Translate(XMFLOAT3(800, 50, 0)); oceanWindow->SetVisible(false); diff --git a/Editor/OceanWindow.h b/Editor/OceanWindow.h index a7c2c0d77..61d351514 100644 --- a/Editor/OceanWindow.h +++ b/Editor/OceanWindow.h @@ -20,5 +20,10 @@ public: wiWindow* oceanWindow; wiCheckBox* enabledCheckBox; + wiSlider* patchSizeSlider; + wiSlider* waveAmplitudeSlider; + wiSlider* choppyScaleSlider; + wiSlider* windDependencySlider; + wiSlider* timeScaleSlider; }; diff --git a/WickedEngine/wiFFTGenerator.cpp b/WickedEngine/wiFFTGenerator.cpp index 4dba3a694..805135af2 100644 --- a/WickedEngine/wiFFTGenerator.cpp +++ b/WickedEngine/wiFFTGenerator.cpp @@ -212,8 +212,6 @@ void fft512x512_create_plan(CSFFT512x512_Plan* plan, UINT slices) void fft512x512_destroy_plan(CSFFT512x512_Plan* plan) { SAFE_DELETE(plan->pBuffer_Tmp); - SAFE_DELETE(plan->pRadix008A_CS); - SAFE_DELETE(plan->pRadix008A_CS2); for (int i = 0; i < 6; i++) SAFE_DELETE(plan->pRadix008A_CB[i]); diff --git a/WickedEngine/wiOcean.cpp b/WickedEngine/wiOcean.cpp index 4762b5a56..f4202d5d9 100644 --- a/WickedEngine/wiOcean.cpp +++ b/WickedEngine/wiOcean.cpp @@ -504,11 +504,8 @@ const wiOceanParameter& wiOcean::getParameters() void wiOcean::initRenderResource() { GraphicsDevice* device = wiRenderer::GetDevice(); - wiOceanParameter ocean_param = m_param; - g_PatchLength = ocean_param.patch_length; - g_DisplaceMapDim = ocean_param.dmap_dim; - g_WindDir = ocean_param.wind_dir; + g_WindDir = m_param.wind_dir; // D3D buffers createSurfaceMesh(); @@ -529,13 +526,13 @@ void wiOcean::initRenderResource() Ocean_Rendering_ShadingCB shading_data; // Grid side length * 2 - shading_data.g_TexelLength_x2 = g_PatchLength / g_DisplaceMapDim * 2;; + shading_data.g_TexelLength_x2 = m_param.patch_length / m_param.dmap_dim * 2;; // Color shading_data.g_SkyColor = g_SkyColor; shading_data.g_WaterbodyColor = g_WaterbodyColor; // Texcoord - shading_data.g_UVScale = 1.0f / g_PatchLength; - shading_data.g_UVOffset = 0.5f / g_DisplaceMapDim; + shading_data.g_UVScale = 1.0f / m_param.patch_length; + shading_data.g_UVOffset = 0.5f / m_param.dmap_dim; // Perlin shading_data.g_PerlinSize = g_PerlinSize; shading_data.g_PerlinAmplitude = g_PerlinAmplitude; @@ -1156,22 +1153,22 @@ wiOcean::QuadRenderParam& wiOcean::selectMeshPattern(const QuadNode& quad_node) XMVECTOR tmp; XMFLOAT2 point_left; - tmp = bottom_left + XMVectorSet(-g_PatchLength * 0.5f, quad_node.length * 0.5f, 0, 0); + tmp = bottom_left + XMVectorSet(-m_param.patch_length * 0.5f, quad_node.length * 0.5f, 0, 0); XMStoreFloat2(&point_left, tmp); int left_adj_index = searchLeaf(g_render_list, point_left); XMFLOAT2 point_right; - tmp = bottom_left + XMVectorSet(quad_node.length + g_PatchLength * 0.5f, quad_node.length * 0.5f, 0, 0); + tmp = bottom_left + XMVectorSet(quad_node.length + m_param.patch_length * 0.5f, quad_node.length * 0.5f, 0, 0); XMStoreFloat2(&point_right, tmp); int right_adj_index = searchLeaf(g_render_list, point_right); XMFLOAT2 point_bottom; - tmp = bottom_left + XMVectorSet(quad_node.length * 0.5f, -g_PatchLength * 0.5f, 0, 0); + tmp = bottom_left + XMVectorSet(quad_node.length * 0.5f, -m_param.patch_length * 0.5f, 0, 0); XMStoreFloat2(&point_right, tmp); int bottom_adj_index = searchLeaf(g_render_list, point_bottom); XMFLOAT2 point_top; - tmp = bottom_left + XMVectorSet(quad_node.length * 0.5f, quad_node.length + g_PatchLength * 0.5f, 0, 0); + tmp = bottom_left + XMVectorSet(quad_node.length * 0.5f, quad_node.length + m_param.patch_length * 0.5f, 0, 0); XMStoreFloat2(&point_right, tmp); int top_adj_index = searchLeaf(g_render_list, point_top); @@ -1238,7 +1235,7 @@ int wiOcean::buildNodeList(QuadNode& quad_node, const Camera& camera) bool visible = true; XMVECTOR bottom_left = XMLoadFloat2(&quad_node.bottom_left); XMFLOAT2 tmp; - if (min_coverage > g_UpperGridCoverage && quad_node.length > g_PatchLength) + if (min_coverage > g_UpperGridCoverage && quad_node.length > m_param.patch_length) { // Recursive rendering for sub-quads. QuadNode sub_node_0 = { quad_node.bottom_left, quad_node.length / 2, 0,{ -1, -1, -1, -1 } }; @@ -1290,7 +1287,7 @@ void wiOcean::Render(const Camera* camera, float time, GRAPHICSTHREAD threadID) // Build rendering list g_render_list.clear(); - float ocean_extent = g_PatchLength * (1 << g_FurthestCover); + float ocean_extent = m_param.patch_length * (1 << g_FurthestCover); QuadNode root_node = { XMFLOAT2(-ocean_extent * 0.5f, -ocean_extent * 0.5f), ocean_extent, 0,{ -1,-1,-1,-1 } }; buildNodeList(root_node, *camera); @@ -1356,7 +1353,7 @@ void wiOcean::Render(const Camera* camera, float time, GRAPHICSTHREAD threadID) call_consts.g_matWorldViewProj = XMMatrixTranspose(matWVP); // Texcoord for perlin noise - XMVECTOR uv_base = XMLoadFloat2(&node.bottom_left) / g_PatchLength * g_PerlinSize; + XMVECTOR uv_base = XMLoadFloat2(&node.bottom_left) / m_param.patch_length * g_PerlinSize; XMStoreFloat2(&call_consts.g_UVBase, uv_base); // Constant g_PerlinSpeed need to be adjusted mannually diff --git a/WickedEngine/wiOcean.h b/WickedEngine/wiOcean.h index ec909b58f..d04a94208 100644 --- a/WickedEngine/wiOcean.h +++ b/WickedEngine/wiOcean.h @@ -124,10 +124,6 @@ protected: // Mesh grid dimension, must be 2^n. 4x4 ~ 256x256 int g_MeshDim = 128; - // Side length of square shaped mesh patch - float g_PatchLength; - // Dimension of displacement map - int g_DisplaceMapDim; // Subdivision thredshold. Any quad covers more pixels than this value needs to be subdivided. float g_UpperGridCoverage = 64.0f; // Draw distance = g_PatchLength * 2^g_FurthestCover From c120530bb807ba7fc97786c5ee71a603b997cc0a Mon Sep 17 00:00:00 2001 From: Turanszki Janos Date: Sun, 12 Nov 2017 13:58:33 +0000 Subject: [PATCH 07/10] moved ocean simulator texture updates to compute shader --- Editor/CameraWindow.cpp | 2 +- Editor/Editor.cpp | 7 + Editor/OceanWindow.cpp | 3 + WickedEngine/WickedEngine_SHADERS.vcxproj | 12 +- .../WickedEngine_SHADERS.vcxproj.filters | 12 +- WickedEngine/oceanGradientFoldingPS.hlsl | 33 ---- WickedEngine/oceanQuadVS.hlsl | 11 +- WickedEngine/oceanSurfaceHF.hlsli | 17 +- .../oceanUpdateDisplacementMapCS.hlsl | 19 ++ WickedEngine/oceanUpdateDisplacementPS.hlsl | 18 -- .../oceanUpdateGradientFoldingCS.hlsl | 38 ++++ WickedEngine/oceanWaveGenHF.hlsli | 10 +- WickedEngine/wiGraphicsDevice_DX11.cpp | 2 +- WickedEngine/wiOcean.cpp | 163 +++--------------- WickedEngine/wiOcean.h | 11 +- 15 files changed, 120 insertions(+), 238 deletions(-) delete mode 100644 WickedEngine/oceanGradientFoldingPS.hlsl create mode 100644 WickedEngine/oceanUpdateDisplacementMapCS.hlsl delete mode 100644 WickedEngine/oceanUpdateDisplacementPS.hlsl create mode 100644 WickedEngine/oceanUpdateGradientFoldingCS.hlsl diff --git a/Editor/CameraWindow.cpp b/Editor/CameraWindow.cpp index a064ddfba..0d2cdd479 100644 --- a/Editor/CameraWindow.cpp +++ b/Editor/CameraWindow.cpp @@ -73,7 +73,7 @@ CameraWindow::CameraWindow(wiGUI* gui) :GUI(gui) - cameraWindow->Translate(XMFLOAT3(760, 500, 0)); + cameraWindow->Translate(XMFLOAT3(800, 500, 0)); cameraWindow->SetVisible(false); } diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 4d7f97189..cb9761c4e 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -1402,6 +1402,13 @@ void EditorComponent::Compose() { renderPath->Compose(); + if (wiRenderer::GetOcean()) + { + wiImageEffects fx(500, 500, 500, 500); + fx.blendFlag = BLENDMODE_OPAQUE; + wiImage::Draw(wiRenderer::GetOcean()->getDisplacementMap(), fx, GRAPHICSTHREAD_IMMEDIATE); + } + //__super::Compose(); for (auto& x : wiRenderer::GetScene().models) diff --git a/Editor/OceanWindow.cpp b/Editor/OceanWindow.cpp index 4e8a484c3..4b9198f31 100644 --- a/Editor/OceanWindow.cpp +++ b/Editor/OceanWindow.cpp @@ -9,6 +9,9 @@ OceanWindow::OceanWindow(wiGUI* gui) :GUI(gui) float screenW = (float)wiRenderer::GetDevice()->GetScreenWidth(); float screenH = (float)wiRenderer::GetDevice()->GetScreenHeight(); + //params.patch_length = 200.0f; + //params.wave_amplitude = 0.0f; + oceanWindow = new wiWindow(GUI, "Ocean Window"); oceanWindow->SetSize(XMFLOAT2(600, 300)); GUI->AddWidget(oceanWindow); diff --git a/WickedEngine/WickedEngine_SHADERS.vcxproj b/WickedEngine/WickedEngine_SHADERS.vcxproj index 4b923b7f6..1e26b10ad 100644 --- a/WickedEngine/WickedEngine_SHADERS.vcxproj +++ b/WickedEngine/WickedEngine_SHADERS.vcxproj @@ -452,9 +452,6 @@ Vertex - - Pixel - Vertex @@ -471,8 +468,13 @@ Vertex - - Pixel + + Compute + 5.0 + + + Compute + 5.0 Pixel diff --git a/WickedEngine/WickedEngine_SHADERS.vcxproj.filters b/WickedEngine/WickedEngine_SHADERS.vcxproj.filters index 09bebcd73..1ba51d6d4 100644 --- a/WickedEngine/WickedEngine_SHADERS.vcxproj.filters +++ b/WickedEngine/WickedEngine_SHADERS.vcxproj.filters @@ -666,12 +666,6 @@ VS - - PS - - - PS - VS @@ -681,6 +675,12 @@ PS + + CS + + + CS + diff --git a/WickedEngine/oceanGradientFoldingPS.hlsl b/WickedEngine/oceanGradientFoldingPS.hlsl deleted file mode 100644 index 59ecdc3fc..000000000 --- a/WickedEngine/oceanGradientFoldingPS.hlsl +++ /dev/null @@ -1,33 +0,0 @@ -#include "oceanWaveGenHF.hlsli" - -// Displacement -> Normal, Folding -float4 main(VS_QUAD_OUTPUT In) : SV_Target -{ - // Sample neighbour texels - float2 one_texel = float2(1.0f / (float)g_OutWidth, 1.0f / (float)g_OutHeight); - - float2 tc_left = float2(In.TexCoord.x - one_texel.x, In.TexCoord.y); - float2 tc_right = float2(In.TexCoord.x + one_texel.x, In.TexCoord.y); - float2 tc_back = float2(In.TexCoord.x, In.TexCoord.y - one_texel.y); - float2 tc_front = float2(In.TexCoord.x, In.TexCoord.y + one_texel.y); - - float3 displace_left = g_samplerDisplacementMap.Sample(LinearSampler, tc_left).xyz; - float3 displace_right = g_samplerDisplacementMap.Sample(LinearSampler, tc_right).xyz; - float3 displace_back = g_samplerDisplacementMap.Sample(LinearSampler, tc_back).xyz; - float3 displace_front = g_samplerDisplacementMap.Sample(LinearSampler, tc_front).xyz; - - // Do not store the actual normal value. Using gradient instead, which preserves two differential values. - float2 gradient = { -(displace_right.z - displace_left.z), -(displace_front.z - displace_back.z) }; - - - // Calculate Jacobian corelation from the partial differential of height field - float2 Dx = (displace_right.xy - displace_left.xy) * g_ChoppyScale * g_GridLen; - float2 Dy = (displace_front.xy - displace_back.xy) * g_ChoppyScale * g_GridLen; - float J = (1.0f + Dx.x) * (1.0f + Dy.y) - Dx.y * Dy.x; - - // Practical subsurface scale calculation: max[0, (1 - J) + Amplitude * (2 * Coverage - 1)]. - float fold = max(1.0f - J, 0); - - // Output - return float4(gradient, 0, fold); -} diff --git a/WickedEngine/oceanQuadVS.hlsl b/WickedEngine/oceanQuadVS.hlsl index da1c5caea..f71494cf4 100644 --- a/WickedEngine/oceanQuadVS.hlsl +++ b/WickedEngine/oceanQuadVS.hlsl @@ -1,12 +1,11 @@ #include "oceanWaveGenHF.hlsli" +#include "fullScreenTriangleHF.hlsli" -VS_QUAD_OUTPUT main(float4 vPos : POSITION) +VS_QUAD_OUTPUT main(uint vI : SV_VertexID) { - VS_QUAD_OUTPUT Output; + VS_QUAD_OUTPUT Out; - Output.Position = vPos; - Output.TexCoord.x = 0.5f + vPos.x * 0.5f; - Output.TexCoord.y = 0.5f - vPos.y * 0.5f; + FullScreenTriangle(vI, Out.Position, Out.TexCoord); - return Output; + return Out; } diff --git a/WickedEngine/oceanSurfaceHF.hlsli b/WickedEngine/oceanSurfaceHF.hlsli index 7106f1364..0cf75ae09 100644 --- a/WickedEngine/oceanSurfaceHF.hlsli +++ b/WickedEngine/oceanSurfaceHF.hlsli @@ -1,30 +1,19 @@ #ifndef _OCEAN_SURFACE_HF_ #define _OCEAN_SURFACE_HF_ +#include "globals.hlsli" #include "ShaderInterop_Ocean.h" -//----------------------------------------------------------------------------- -// Global variables -//----------------------------------------------------------------------------- -#define PATCH_BLEND_BEGIN 800 -#define PATCH_BLEND_END 20000 +#define PATCH_BLEND_BEGIN 100 +#define PATCH_BLEND_END 2000 -//----------------------------------------------------------------------------------- -// Texture & Samplers -//----------------------------------------------------------------------------------- #define g_texDisplacement texture_0 // FFT wave displacement map in VS #define g_texPerlin texture_1 // FFT wave gradient map in PS #define g_texGradient texture_2 // Perlin wave displacement & gradient map in both VS & PS TEXTURE1D(g_texFresnel, float4, TEXSLOT_ONDEMAND3); // Fresnel factor lookup table #define g_texReflectCube texture_env_global - -//----------------------------------------------------------------------------- -// Name: OceanSurfVS -// Type: Vertex shader -// Desc: Ocean shading vertex shader. Check SDK document for more details -//----------------------------------------------------------------------------- struct VS_OUTPUT { float4 Position : SV_POSITION; diff --git a/WickedEngine/oceanUpdateDisplacementMapCS.hlsl b/WickedEngine/oceanUpdateDisplacementMapCS.hlsl new file mode 100644 index 000000000..e15ef7506 --- /dev/null +++ b/WickedEngine/oceanUpdateDisplacementMapCS.hlsl @@ -0,0 +1,19 @@ +#include "oceanWaveGenHF.hlsli" + +STRUCTUREDBUFFER(g_InputDxyz, float2, TEXSLOT_ONDEMAND0); +RWTEXTURE2D(output, float4, 0); + +[numthreads(32, 32, 1)] +void main(uint3 DTid : SV_DispatchThreadID) +{ + uint addr = g_OutWidth * DTid.y + DTid.x; + + // cos(pi * (m1 + m2)) + int sign_correction = ((DTid.x + DTid.y) & 1) ? -1 : 1; + + float dx = g_InputDxyz[addr + g_DtxAddressOffset].x * sign_correction * g_ChoppyScale; + float dy = g_InputDxyz[addr + g_DtyAddressOffset].x * sign_correction * g_ChoppyScale; + float dz = g_InputDxyz[addr].x * sign_correction; + + output[DTid.xy] = float4(dx, dy, dz, 1); +} diff --git a/WickedEngine/oceanUpdateDisplacementPS.hlsl b/WickedEngine/oceanUpdateDisplacementPS.hlsl deleted file mode 100644 index 4d0d455b4..000000000 --- a/WickedEngine/oceanUpdateDisplacementPS.hlsl +++ /dev/null @@ -1,18 +0,0 @@ -#include "oceanWaveGenHF.hlsli" - -// Post-FFT data wrap up: Dx, Dy, Dz -> Displacement -float4 main(VS_QUAD_OUTPUT In) : SV_Target -{ - uint index_x = (uint)(In.TexCoord.x * (float)g_OutWidth); - uint index_y = (uint)(In.TexCoord.y * (float)g_OutHeight); - uint addr = g_OutWidth * index_y + index_x; - - // cos(pi * (m1 + m2)) - int sign_correction = ((index_x + index_y) & 1) ? -1 : 1; - - float dx = g_InputDxyz[addr + g_DtxAddressOffset].x * sign_correction * g_ChoppyScale; - float dy = g_InputDxyz[addr + g_DtyAddressOffset].x * sign_correction * g_ChoppyScale; - float dz = g_InputDxyz[addr].x * sign_correction; - - return float4(dx, dy, dz, 1); -} diff --git a/WickedEngine/oceanUpdateGradientFoldingCS.hlsl b/WickedEngine/oceanUpdateGradientFoldingCS.hlsl new file mode 100644 index 000000000..27bea224b --- /dev/null +++ b/WickedEngine/oceanUpdateGradientFoldingCS.hlsl @@ -0,0 +1,38 @@ +#include "oceanWaveGenHF.hlsli" + +#define xDisplacementMap texture_0 +RWTEXTURE2D(output, float4, 0); + +[numthreads(32, 32, 1)] +void main( uint3 DTid : SV_DispatchThreadID ) +{ + // Sample neighbour texels + float2 one_texel = float2(1.0f / (float)g_OutWidth, 1.0f / (float)g_OutHeight); + + float2 uv = (float2)DTid.xy / float2(g_OutWidth, g_OutHeight); + + float2 tc_left = float2(uv.x - one_texel.x, uv.y); + float2 tc_right = float2(uv.x + one_texel.x, uv.y); + float2 tc_back = float2(uv.x, uv.y - one_texel.y); + float2 tc_front = float2(uv.x, uv.y + one_texel.y); + + float3 displace_left = xDisplacementMap.SampleLevel(sampler_linear_clamp, tc_left, 0).xyz; + float3 displace_right = xDisplacementMap.SampleLevel(sampler_linear_clamp, tc_right, 0).xyz; + float3 displace_back = xDisplacementMap.SampleLevel(sampler_linear_clamp, tc_back, 0).xyz; + float3 displace_front = xDisplacementMap.SampleLevel(sampler_linear_clamp, tc_front, 0).xyz; + + // Do not store the actual normal value. Using gradient instead, which preserves two differential values. + float2 gradient = { -(displace_right.z - displace_left.z), -(displace_front.z - displace_back.z) }; + + + // Calculate Jacobian corelation from the partial differential of height field + float2 Dx = (displace_right.xy - displace_left.xy) * g_ChoppyScale * g_GridLen; + float2 Dy = (displace_front.xy - displace_back.xy) * g_ChoppyScale * g_GridLen; + float J = (1.0f + Dx.x) * (1.0f + Dy.y) - Dx.y * Dy.x; + + // Practical subsurface scale calculation: max[0, (1 - J) + Amplitude * (2 * Coverage - 1)]. + float fold = max(1.0f - J, 0); + + // Output + output[DTid.xy] = float4(gradient, 0, fold); +} \ No newline at end of file diff --git a/WickedEngine/oceanWaveGenHF.hlsli b/WickedEngine/oceanWaveGenHF.hlsli index 2298cb598..f6f6825f0 100644 --- a/WickedEngine/oceanWaveGenHF.hlsli +++ b/WickedEngine/oceanWaveGenHF.hlsli @@ -12,12 +12,10 @@ struct VS_QUAD_OUTPUT //----------------------------------------- Pixel Shaders ------------------------------------------ -// Textures and sampling states -#define g_samplerDisplacementMap texture_0 +//// Textures and sampling states +//#define g_samplerDisplacementMap texture_0 +// +//SAMPLERSTATE(LinearSampler, SSLOT_ONDEMAND0); -SAMPLERSTATE(LinearSampler, SSLOT_ONDEMAND0); - -// The following three should contains only real numbers. But we have only C2C FFT now. -STRUCTUREDBUFFER(g_InputDxyz, float2, TEXSLOT_ONDEMAND0); #endif // _OCEAN_SIMULATOR_HF_ diff --git a/WickedEngine/wiGraphicsDevice_DX11.cpp b/WickedEngine/wiGraphicsDevice_DX11.cpp index 110a7842d..961185cb9 100644 --- a/WickedEngine/wiGraphicsDevice_DX11.cpp +++ b/WickedEngine/wiGraphicsDevice_DX11.cpp @@ -1405,7 +1405,7 @@ GraphicsDevice_DX11::GraphicsDevice_DX11(wiWindowRegistration::window_type windo } UINT createDeviceFlags = 0; - //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; + createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; D3D_DRIVER_TYPE driverTypes[] = { diff --git a/WickedEngine/wiOcean.cpp b/WickedEngine/wiOcean.cpp index f4202d5d9..b532554cb 100644 --- a/WickedEngine/wiOcean.cpp +++ b/WickedEngine/wiOcean.cpp @@ -7,13 +7,11 @@ using namespace wiGraphicsTypes; using namespace std; ComputeShader* wiOcean::m_pUpdateSpectrumCS = nullptr; -VertexShader* wiOcean::m_pQuadVS = nullptr; -PixelShader* wiOcean::m_pUpdateDisplacementPS = nullptr; -PixelShader* wiOcean::m_pGenGradientFoldingPS = nullptr; +ComputeShader* wiOcean::m_pUpdateDisplacementMapCS = nullptr; +ComputeShader* wiOcean::m_pUpdateGradientFoldingCS = nullptr; VertexShader* wiOcean::g_pOceanSurfVS = nullptr; PixelShader* wiOcean::g_pWireframePS = nullptr; PixelShader* wiOcean::g_pOceanSurfPS = nullptr; -VertexLayout* wiOcean::m_pQuadLayout = nullptr; VertexLayout* wiOcean::g_pMeshLayout = nullptr; // Disable warning "conditional expression is constant" @@ -112,7 +110,7 @@ void createTextureAndViews(UINT width, UINT height, FORMAT format, Texture2D** p tex_desc.SampleDesc.Count = 1; tex_desc.SampleDesc.Quality = 0; tex_desc.Usage = USAGE_DEFAULT; - tex_desc.BindFlags = BIND_SHADER_RESOURCE | BIND_RENDER_TARGET; + tex_desc.BindFlags = BIND_SHADER_RESOURCE | BIND_UNORDERED_ACCESS | BIND_RENDER_TARGET; tex_desc.CPUAccessFlags = 0; tex_desc.MiscFlags = RESOURCE_MISC_GENERATE_MIPS; @@ -197,46 +195,6 @@ wiOcean::wiOcean(const wiOceanParameter& params) createTextureAndViews(hmap_dim, hmap_dim, FORMAT_R32G32B32A32_FLOAT, &m_pDisplacementMap); createTextureAndViews(hmap_dim, hmap_dim, FORMAT_R16G16B16A16_FLOAT, &m_pGradientMap); - // Samplers - SamplerDesc sam_desc; - sam_desc.Filter = FILTER_MIN_MAG_LINEAR_MIP_POINT; - sam_desc.AddressU = TEXTURE_ADDRESS_WRAP; - sam_desc.AddressV = TEXTURE_ADDRESS_WRAP; - sam_desc.AddressW = TEXTURE_ADDRESS_WRAP; - sam_desc.MipLODBias = 0; - sam_desc.MaxAnisotropy = 1; - sam_desc.ComparisonFunc = COMPARISON_NEVER; - sam_desc.BorderColor[0] = 1.0f; - sam_desc.BorderColor[1] = 1.0f; - sam_desc.BorderColor[2] = 1.0f; - sam_desc.BorderColor[3] = 1.0f; - sam_desc.MinLOD = -FLT_MAX; - sam_desc.MaxLOD = FLT_MAX; - wiRenderer::GetDevice()->CreateSamplerState(&sam_desc, &m_pPointSamplerState); - - - // Quad vertex buffer - GPUBufferDesc vb_desc; - vb_desc.ByteWidth = 4 * sizeof(XMFLOAT4); - vb_desc.Usage = USAGE_IMMUTABLE; - vb_desc.BindFlags = BIND_VERTEX_BUFFER; - vb_desc.CPUAccessFlags = 0; - vb_desc.MiscFlags = 0; - - float quad_verts[] = - { - -1, -1, 0, 1, - -1, 1, 0, 1, - 1, -1, 0, 1, - 1, 1, 0, 1, - }; - SubresourceData init_data; - init_data.pSysMem = &quad_verts[0]; - init_data.SysMemPitch = 0; - init_data.SysMemSlicePitch = 0; - - m_pQuadVB = new GPUBuffer; - wiRenderer::GetDevice()->CreateBuffer(&vb_desc, &init_data, m_pQuadVB); // Constant buffers UINT actual_dim = m_param.dmap_dim; @@ -282,8 +240,6 @@ wiOcean::~wiOcean() SAFE_DELETE(m_pBuffer_Float2_Ht); SAFE_DELETE(m_pBuffer_Float_Dxyz); - SAFE_DELETE(m_pQuadVB); - SAFE_DELETE(m_pDisplacementMap); SAFE_DELETE(m_pGradientMap); @@ -356,7 +312,6 @@ void wiOcean::UpdateDisplacementMap(float time, GRAPHICSTHREAD threadID) device->EventBegin("OceanSimulator", threadID); // ---------------------------- H(0) -> H(t), D(x, t), D(y, t) -------------------------------- - // Compute shader device->BindCS(m_pUpdateSpectrumCS, threadID); // Buffers @@ -383,13 +338,6 @@ void wiOcean::UpdateDisplacementMap(float time, GRAPHICSTHREAD threadID) UINT group_count_y = (m_param.dmap_dim + BLOCK_SIZE_Y - 1) / BLOCK_SIZE_Y; device->Dispatch(group_count_x, group_count_y, 1, threadID); - //// Unbind resources for CS - //cs0_uavs[0] = NULL; - //m_pd3dImmediateContext->CSSetUnorderedAccessViews(0, 1, cs0_uavs, (UINT*)(&cs0_uavs[0])); - //cs0_srvs[0] = NULL; - //cs0_srvs[1] = NULL; - //m_pd3dImmediateContext->CSSetShaderResources(0, 2, cs0_srvs); - device->UnBindUnorderedAccessResources(0, 1, threadID); device->UnBindResources(TEXSLOT_ONDEMAND0, 2, threadID); @@ -397,80 +345,34 @@ void wiOcean::UpdateDisplacementMap(float time, GRAPHICSTHREAD threadID) // ------------------------------------ Perform FFT ------------------------------------------- fft_512x512_c2c(&m_fft_plan, m_pBuffer_Float_Dxyz, m_pBuffer_Float_Dxyz, m_pBuffer_Float2_Ht, threadID); - // --------------------------------- Wrap Dx, Dy and Dz --------------------------------------- - // Push RT - //ID3D11RenderTargetView* old_target; - //ID3D11DepthStencilView* old_depth; - //m_pd3dImmediateContext->OMGetRenderTargets(1, &old_target, &old_depth); - //VIEWPORT old_viewport; - //UINT num_viewport = 1; - //m_pd3dImmediateContext->RSGetViewports(&num_viewport, &old_viewport); - ViewPort new_vp; - new_vp.TopLeftX = 0; - new_vp.TopLeftX = 0; - new_vp.Width = (float)m_param.dmap_dim; - new_vp.Height = (float)m_param.dmap_dim; - new_vp.MinDepth = 0.0f; - new_vp.MaxDepth = 1.0f; - device->BindViewports(1, &new_vp, threadID); - // Set RT - device->BindRenderTargets(1, (Texture**)&m_pDisplacementMap, nullptr, threadID); + device->BindConstantBufferCS(m_pImmutableCB, CB_GETBINDSLOT(Ocean_Simulation_ImmutableCB), threadID); + device->BindConstantBufferCS(m_pPerFrameCB, CB_GETBINDSLOT(Ocean_Simulation_PerFrameCB), threadID); - // VS & PS - device->BindVS(m_pQuadVS, threadID); - device->BindPS(m_pUpdateDisplacementPS, threadID); - device->BindConstantBufferPS(m_pImmutableCB, CB_GETBINDSLOT(Ocean_Simulation_ImmutableCB), threadID); - device->BindConstantBufferPS(m_pPerFrameCB, CB_GETBINDSLOT(Ocean_Simulation_PerFrameCB), threadID); + // Update displacement map: + device->BindCS(m_pUpdateDisplacementMapCS, threadID); + GPUUnorderedResource* cs_uavs[] = { m_pDisplacementMap }; + device->BindUnorderedAccessResourcesCS(cs_uavs, 0, 1, threadID); + GPUResource* cs_srvs[1] = { m_pBuffer_Float_Dxyz }; + device->BindResourcesCS(cs_srvs, TEXSLOT_ONDEMAND0, 1, threadID); + device->Dispatch(m_param.dmap_dim / 32, m_param.dmap_dim / 32, 1, threadID); - // Buffer resources - GPUResource* ps_srvs[1] = { m_pBuffer_Float_Dxyz }; - device->BindResourcesPS(ps_srvs, TEXSLOT_ONDEMAND0, 1, threadID); - // IA setup - GPUBuffer* vbs[1] = { m_pQuadVB }; - UINT strides[1] = { sizeof(XMFLOAT4) }; - UINT offsets[1] = { 0 }; - device->BindVertexBuffers(&vbs[0], 0, 1, &strides[0], &offsets[0], threadID); - - device->BindVertexLayout(m_pQuadLayout, threadID); - device->BindPrimitiveTopology(TRIANGLESTRIP, threadID); - - // Perform draw call - device->Draw(4, 0, threadID); + // Update gradient map: + device->BindCS(m_pUpdateGradientFoldingCS, threadID); + cs_uavs[0] = { m_pGradientMap }; + device->BindUnorderedAccessResourcesCS(cs_uavs, 0, 1, threadID); + cs_srvs[0] = m_pDisplacementMap; + device->BindResourcesCS(cs_srvs, TEXSLOT_ONDEMAND0, 1, threadID); + device->Dispatch(m_param.dmap_dim / 32, m_param.dmap_dim / 32, 1, threadID); // Unbind + device->UnBindUnorderedAccessResources(0, 1, threadID); device->UnBindResources(TEXSLOT_ONDEMAND0, 1, threadID); - // ----------------------------------- Generate Normal ---------------------------------------- - // Set RT - device->BindRenderTargets(1, (Texture**)&m_pGradientMap, nullptr, threadID); - - // VS & PS - device->BindVS(m_pQuadVS, threadID); - device->BindPS(m_pGenGradientFoldingPS, threadID); - - // Texture resource and sampler - ps_srvs[0] = m_pDisplacementMap; - device->BindResourcesPS(ps_srvs, TEXSLOT_ONDEMAND0, 1, threadID); - - device->BindSamplerPS(&m_pPointSamplerState, SSLOT_ONDEMAND0, threadID); - - // Perform draw call - device->Draw(4, 0, threadID); - - // Unbind - device->UnBindResources(TEXSLOT_ONDEMAND0, 1, threadID); - - //// Pop RT - //m_pd3dImmediateContext->RSSetViewports(1, &old_viewport); - //m_pd3dImmediateContext->OMSetRenderTargets(1, &old_target, old_depth); - //SAFE_DELETE(old_target); - //SAFE_DELETE(old_depth); - device->GenerateMips(m_pGradientMap, threadID); @@ -1403,22 +1305,8 @@ void wiOcean::LoadShaders() { m_pUpdateSpectrumCS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanSimulatorCS.cso", wiResourceManager::COMPUTESHADER)); - - { - VertexLayoutDesc layout[] = - { - { "POSITION", 0, FORMAT_R32G32B32A32_FLOAT, 0, 0, INPUT_PER_VERTEX_DATA, 0 }, - }; - UINT numElements = ARRAYSIZE(layout); - VertexShaderInfo* vsinfo = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanQuadVS.cso", wiResourceManager::VERTEXSHADER, layout, numElements)); - if (vsinfo != nullptr) { - m_pQuadVS = vsinfo->vertexShader; - m_pQuadLayout = vsinfo->vertexLayout; - } - } - - m_pUpdateDisplacementPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanUpdateDisplacementPS.cso", wiResourceManager::PIXELSHADER)); - m_pGenGradientFoldingPS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanGradientFoldingPS.cso", wiResourceManager::PIXELSHADER)); + m_pUpdateDisplacementMapCS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanUpdateDisplacementMapCS.cso", wiResourceManager::COMPUTESHADER)); + m_pUpdateGradientFoldingCS = static_cast(wiResourceManager::GetShaderManager()->add(wiRenderer::SHADERPATH + "oceanUpdateGradientFoldingCS.cso", wiResourceManager::COMPUTESHADER)); { @@ -1448,11 +1336,8 @@ void wiOcean::SetUpStatic() void wiOcean::CleanUpStatic() { SAFE_DELETE(m_pUpdateSpectrumCS); - SAFE_DELETE(m_pQuadVS); - SAFE_DELETE(m_pUpdateDisplacementPS); - SAFE_DELETE(m_pGenGradientFoldingPS); - - SAFE_DELETE(m_pQuadLayout); + SAFE_DELETE(m_pUpdateDisplacementMapCS); + SAFE_DELETE(m_pUpdateGradientFoldingCS); SAFE_DELETE(g_pMeshLayout); diff --git a/WickedEngine/wiOcean.h b/WickedEngine/wiOcean.h index d04a94208..23bcc0b87 100644 --- a/WickedEngine/wiOcean.h +++ b/WickedEngine/wiOcean.h @@ -93,21 +93,14 @@ protected: // Height & choppy buffer in the space domain, corresponding to H(t), Dx(t) and Dy(t) wiGraphicsTypes::GPUBuffer* m_pBuffer_Float_Dxyz; - wiGraphicsTypes::GPUBuffer* m_pQuadVB; - // Shaders, layouts and constants static wiGraphicsTypes::ComputeShader* m_pUpdateSpectrumCS; - static wiGraphicsTypes::VertexShader* m_pQuadVS; - static wiGraphicsTypes::PixelShader* m_pUpdateDisplacementPS; - static wiGraphicsTypes::PixelShader* m_pGenGradientFoldingPS; - - static wiGraphicsTypes::VertexLayout* m_pQuadLayout; + static wiGraphicsTypes::ComputeShader* m_pUpdateDisplacementMapCS; + static wiGraphicsTypes::ComputeShader* m_pUpdateGradientFoldingCS; wiGraphicsTypes::GPUBuffer* m_pImmutableCB; wiGraphicsTypes::GPUBuffer* m_pPerFrameCB; - wiGraphicsTypes::Sampler m_pPointSamplerState; - // FFT wrap-up CSFFT512x512_Plan m_fft_plan; From b5836181ae4c975686e3611bea7ded4bf09e71dc Mon Sep 17 00:00:00 2001 From: Turanszki Janos Date: Sun, 12 Nov 2017 14:05:02 +0000 Subject: [PATCH 08/10] ocean updates --- WickedEngine/ShaderInterop_Ocean.h | 2 ++ WickedEngine/WickedEngine_SHADERS.vcxproj | 4 ---- .../WickedEngine_SHADERS.vcxproj.filters | 6 ------ WickedEngine/oceanQuadVS.hlsl | 11 ---------- WickedEngine/oceanSimulatorCS.hlsl | 16 ++------------ .../oceanUpdateDisplacementMapCS.hlsl | 4 ++-- .../oceanUpdateGradientFoldingCS.hlsl | 5 +++-- WickedEngine/oceanWaveGenHF.hlsli | 21 ------------------- WickedEngine/wiOcean.cpp | 11 ++++------ 9 files changed, 13 insertions(+), 67 deletions(-) delete mode 100644 WickedEngine/oceanQuadVS.hlsl delete mode 100644 WickedEngine/oceanWaveGenHF.hlsli diff --git a/WickedEngine/ShaderInterop_Ocean.h b/WickedEngine/ShaderInterop_Ocean.h index db8f09a09..079ba974d 100644 --- a/WickedEngine/ShaderInterop_Ocean.h +++ b/WickedEngine/ShaderInterop_Ocean.h @@ -2,6 +2,8 @@ #define _SHADERINTEROP_OCEAN_H_ #include "ShaderInterop.h" +#define OCEAN_COMPUTE_TILESIZE 16 + // Simulation constants: CBUFFER(Ocean_Simulation_ImmutableCB, CBSLOT_OTHER_OCEAN_SIMULATION_IMMUTABLE) diff --git a/WickedEngine/WickedEngine_SHADERS.vcxproj b/WickedEngine/WickedEngine_SHADERS.vcxproj index 1e26b10ad..fb22c02cf 100644 --- a/WickedEngine/WickedEngine_SHADERS.vcxproj +++ b/WickedEngine/WickedEngine_SHADERS.vcxproj @@ -33,7 +33,6 @@ - @@ -452,9 +451,6 @@ Vertex - - Vertex - Compute 5.0 diff --git a/WickedEngine/WickedEngine_SHADERS.vcxproj.filters b/WickedEngine/WickedEngine_SHADERS.vcxproj.filters index 1ba51d6d4..a19b82645 100644 --- a/WickedEngine/WickedEngine_SHADERS.vcxproj.filters +++ b/WickedEngine/WickedEngine_SHADERS.vcxproj.filters @@ -115,9 +115,6 @@ HF - - HF - HF @@ -663,9 +660,6 @@ CS - - VS - VS diff --git a/WickedEngine/oceanQuadVS.hlsl b/WickedEngine/oceanQuadVS.hlsl deleted file mode 100644 index f71494cf4..000000000 --- a/WickedEngine/oceanQuadVS.hlsl +++ /dev/null @@ -1,11 +0,0 @@ -#include "oceanWaveGenHF.hlsli" -#include "fullScreenTriangleHF.hlsli" - -VS_QUAD_OUTPUT main(uint vI : SV_VertexID) -{ - VS_QUAD_OUTPUT Out; - - FullScreenTriangle(vI, Out.Position, Out.TexCoord); - - return Out; -} diff --git a/WickedEngine/oceanSimulatorCS.hlsl b/WickedEngine/oceanSimulatorCS.hlsl index e91be6f10..c42e4c508 100644 --- a/WickedEngine/oceanSimulatorCS.hlsl +++ b/WickedEngine/oceanSimulatorCS.hlsl @@ -1,25 +1,13 @@ -#include "oceanWaveGenHF.hlsli" +#include "ShaderInterop_Ocean.h" #define PI 3.1415926536f -#define BLOCK_SIZE_X 16 -#define BLOCK_SIZE_Y 16 STRUCTUREDBUFFER(g_InputH0, float2, TEXSLOT_ONDEMAND0); STRUCTUREDBUFFER(g_InputOmega, float, TEXSLOT_ONDEMAND1); RWSTRUCTUREDBUFFER(g_OutputHt, float2, 0); - -//---------------------------------------- Compute Shaders ----------------------------------------- - -// Pre-FFT data preparation: - -// Notice: In CS5.0, we can output up to 8 RWBuffers but in CS4.x only one output buffer is allowed, -// that way we have to allocate one big buffer and manage the offsets manually. The restriction is -// not caused by NVIDIA GPUs and does not present on NVIDIA GPUs when using other computing APIs like -// CUDA and OpenCL. - // H(0) -> H(t) -[numthreads(BLOCK_SIZE_X, BLOCK_SIZE_Y, 1)] +[numthreads(OCEAN_COMPUTE_TILESIZE, OCEAN_COMPUTE_TILESIZE, 1)] void main(uint3 DTid : SV_DispatchThreadID) { int in_index = DTid.y * g_InWidth + DTid.x; diff --git a/WickedEngine/oceanUpdateDisplacementMapCS.hlsl b/WickedEngine/oceanUpdateDisplacementMapCS.hlsl index e15ef7506..ff5605720 100644 --- a/WickedEngine/oceanUpdateDisplacementMapCS.hlsl +++ b/WickedEngine/oceanUpdateDisplacementMapCS.hlsl @@ -1,9 +1,9 @@ -#include "oceanWaveGenHF.hlsli" +#include "ShaderInterop_Ocean.h" STRUCTUREDBUFFER(g_InputDxyz, float2, TEXSLOT_ONDEMAND0); RWTEXTURE2D(output, float4, 0); -[numthreads(32, 32, 1)] +[numthreads(OCEAN_COMPUTE_TILESIZE, OCEAN_COMPUTE_TILESIZE, 1)] void main(uint3 DTid : SV_DispatchThreadID) { uint addr = g_OutWidth * DTid.y + DTid.x; diff --git a/WickedEngine/oceanUpdateGradientFoldingCS.hlsl b/WickedEngine/oceanUpdateGradientFoldingCS.hlsl index 27bea224b..c0d9b8edf 100644 --- a/WickedEngine/oceanUpdateGradientFoldingCS.hlsl +++ b/WickedEngine/oceanUpdateGradientFoldingCS.hlsl @@ -1,9 +1,10 @@ -#include "oceanWaveGenHF.hlsli" +#include "globals.hlsli" +#include "ShaderInterop_Ocean.h" #define xDisplacementMap texture_0 RWTEXTURE2D(output, float4, 0); -[numthreads(32, 32, 1)] +[numthreads(OCEAN_COMPUTE_TILESIZE, OCEAN_COMPUTE_TILESIZE, 1)] void main( uint3 DTid : SV_DispatchThreadID ) { // Sample neighbour texels diff --git a/WickedEngine/oceanWaveGenHF.hlsli b/WickedEngine/oceanWaveGenHF.hlsli deleted file mode 100644 index f6f6825f0..000000000 --- a/WickedEngine/oceanWaveGenHF.hlsli +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef _OCEAN_SIMULATOR_HF_ -#define _OCEAN_SIMULATOR_HF_ -#include "globals.hlsli" -#include "ShaderInterop_Ocean.h" - -//---------------------------------------- Vertex Shaders ------------------------------------------ -struct VS_QUAD_OUTPUT -{ - float4 Position : SV_POSITION; // vertex position - float2 TexCoord : TEXCOORD0; // vertex texture coords -}; - -//----------------------------------------- Pixel Shaders ------------------------------------------ - -//// Textures and sampling states -//#define g_samplerDisplacementMap texture_0 -// -//SAMPLERSTATE(LinearSampler, SSLOT_ONDEMAND0); - - -#endif // _OCEAN_SIMULATOR_HF_ diff --git a/WickedEngine/wiOcean.cpp b/WickedEngine/wiOcean.cpp index b532554cb..1214c7ab8 100644 --- a/WickedEngine/wiOcean.cpp +++ b/WickedEngine/wiOcean.cpp @@ -21,9 +21,6 @@ VertexLayout* wiOcean::g_pMeshLayout = nullptr; #define HALF_SQRT_2 0.7071068f #define GRAV_ACCEL 981.0f // The acceleration of gravity, cm/s^2 -#define BLOCK_SIZE_X 16 -#define BLOCK_SIZE_Y 16 - // Generating gaussian random number with mean 0 and standard deviation 1. float Gauss() { @@ -334,8 +331,8 @@ void wiOcean::UpdateDisplacementMap(float time, GRAPHICSTHREAD threadID) device->BindConstantBufferCS(m_pPerFrameCB, CB_GETBINDSLOT(Ocean_Simulation_PerFrameCB), threadID); // Run the CS - UINT group_count_x = (m_param.dmap_dim + BLOCK_SIZE_X - 1) / BLOCK_SIZE_X; - UINT group_count_y = (m_param.dmap_dim + BLOCK_SIZE_Y - 1) / BLOCK_SIZE_Y; + UINT group_count_x = (m_param.dmap_dim + OCEAN_COMPUTE_TILESIZE - 1) / OCEAN_COMPUTE_TILESIZE; + UINT group_count_y = (m_param.dmap_dim + OCEAN_COMPUTE_TILESIZE - 1) / OCEAN_COMPUTE_TILESIZE; device->Dispatch(group_count_x, group_count_y, 1, threadID); device->UnBindUnorderedAccessResources(0, 1, threadID); @@ -357,7 +354,7 @@ void wiOcean::UpdateDisplacementMap(float time, GRAPHICSTHREAD threadID) device->BindUnorderedAccessResourcesCS(cs_uavs, 0, 1, threadID); GPUResource* cs_srvs[1] = { m_pBuffer_Float_Dxyz }; device->BindResourcesCS(cs_srvs, TEXSLOT_ONDEMAND0, 1, threadID); - device->Dispatch(m_param.dmap_dim / 32, m_param.dmap_dim / 32, 1, threadID); + device->Dispatch(m_param.dmap_dim / OCEAN_COMPUTE_TILESIZE, m_param.dmap_dim / OCEAN_COMPUTE_TILESIZE, 1, threadID); // Update gradient map: @@ -366,7 +363,7 @@ void wiOcean::UpdateDisplacementMap(float time, GRAPHICSTHREAD threadID) device->BindUnorderedAccessResourcesCS(cs_uavs, 0, 1, threadID); cs_srvs[0] = m_pDisplacementMap; device->BindResourcesCS(cs_srvs, TEXSLOT_ONDEMAND0, 1, threadID); - device->Dispatch(m_param.dmap_dim / 32, m_param.dmap_dim / 32, 1, threadID); + device->Dispatch(m_param.dmap_dim / OCEAN_COMPUTE_TILESIZE, m_param.dmap_dim / OCEAN_COMPUTE_TILESIZE, 1, threadID); // Unbind device->UnBindUnorderedAccessResources(0, 1, threadID); From 78d8583a87602fc7d44489be19e511ef903d3961 Mon Sep 17 00:00:00 2001 From: Turanszki Janos Date: Sun, 12 Nov 2017 15:27:17 +0000 Subject: [PATCH 09/10] ocean updates --- Editor/Editor.cpp | 12 +- Editor/OceanWindow.cpp | 4 +- WickedEngine/wiGraphicsDevice_DX11.cpp | 2 +- WickedEngine/wiOcean.cpp | 330 ++++++++++++------------- WickedEngine/wiOcean.h | 34 ++- WickedEngine/wiRenderer.cpp | 2 + 6 files changed, 194 insertions(+), 190 deletions(-) diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index cb9761c4e..67b2b30f0 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -1402,12 +1402,12 @@ void EditorComponent::Compose() { renderPath->Compose(); - if (wiRenderer::GetOcean()) - { - wiImageEffects fx(500, 500, 500, 500); - fx.blendFlag = BLENDMODE_OPAQUE; - wiImage::Draw(wiRenderer::GetOcean()->getDisplacementMap(), fx, GRAPHICSTHREAD_IMMEDIATE); - } + //if (wiRenderer::GetOcean()) + //{ + // wiImageEffects fx(500, 500, 500, 500); + // fx.blendFlag = BLENDMODE_OPAQUE; + // wiImage::Draw(wiRenderer::GetOcean()->getDisplacementMap(), fx, GRAPHICSTHREAD_IMMEDIATE); + //} //__super::Compose(); diff --git a/Editor/OceanWindow.cpp b/Editor/OceanWindow.cpp index 4b9198f31..ba0e03a9c 100644 --- a/Editor/OceanWindow.cpp +++ b/Editor/OceanWindow.cpp @@ -9,8 +9,6 @@ OceanWindow::OceanWindow(wiGUI* gui) :GUI(gui) float screenW = (float)wiRenderer::GetDevice()->GetScreenWidth(); float screenH = (float)wiRenderer::GetDevice()->GetScreenHeight(); - //params.patch_length = 200.0f; - //params.wave_amplitude = 0.0f; oceanWindow = new wiWindow(GUI, "Ocean Window"); oceanWindow->SetSize(XMFLOAT2(600, 300)); @@ -39,7 +37,7 @@ OceanWindow::OceanWindow(wiGUI* gui) :GUI(gui) }); oceanWindow->AddWidget(patchSizeSlider); - waveAmplitudeSlider = new wiSlider(0, 10, 1000, 100000, "Wave amplitude: "); + waveAmplitudeSlider = new wiSlider(0, 100, 1000, 100000, "Wave amplitude: "); waveAmplitudeSlider->SetSize(XMFLOAT2(100, 30)); waveAmplitudeSlider->SetPos(XMFLOAT2(x, y += inc)); waveAmplitudeSlider->SetValue(params.wave_amplitude); diff --git a/WickedEngine/wiGraphicsDevice_DX11.cpp b/WickedEngine/wiGraphicsDevice_DX11.cpp index 961185cb9..110a7842d 100644 --- a/WickedEngine/wiGraphicsDevice_DX11.cpp +++ b/WickedEngine/wiGraphicsDevice_DX11.cpp @@ -1405,7 +1405,7 @@ GraphicsDevice_DX11::GraphicsDevice_DX11(wiWindowRegistration::window_type windo } UINT createDeviceFlags = 0; - createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; + //createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; D3D_DRIVER_TYPE driverTypes[] = { diff --git a/WickedEngine/wiOcean.cpp b/WickedEngine/wiOcean.cpp index 1214c7ab8..5a436668d 100644 --- a/WickedEngine/wiOcean.cpp +++ b/WickedEngine/wiOcean.cpp @@ -12,7 +12,18 @@ ComputeShader* wiOcean::m_pUpdateGradientFoldingCS = nullptr; VertexShader* wiOcean::g_pOceanSurfVS = nullptr; PixelShader* wiOcean::g_pWireframePS = nullptr; PixelShader* wiOcean::g_pOceanSurfPS = nullptr; + VertexLayout* wiOcean::g_pMeshLayout = nullptr; +Texture1D* wiOcean::g_pFresnelMap = nullptr; +Texture2D* wiOcean::g_pPerlinMap = nullptr; +GPUBuffer* wiOcean::g_pPerCallCB = nullptr; +GPUBuffer* wiOcean::g_pShadingCB = nullptr; +RasterizerState* wiOcean::g_pRSState_Solid = nullptr; +RasterizerState* wiOcean::g_pRSState_Wireframe = nullptr; +DepthStencilState* wiOcean::g_pDSState_Disable = nullptr; +BlendState* wiOcean::g_pBState_Transparent = nullptr; + +CSFFT512x512_Plan wiOcean::m_fft_plan; // Disable warning "conditional expression is constant" #pragma warning(disable:4127) @@ -148,13 +159,14 @@ void createTextureAndViews(UINT width, UINT height, FORMAT format, Texture2D** p wiOcean::wiOcean(const wiOceanParameter& params) { + m_param = params; + // Height map H(0) int height_map_size = (params.dmap_dim + 4) * (params.dmap_dim + 1); XMFLOAT2* h0_data = new XMFLOAT2[height_map_size * sizeof(XMFLOAT2)]; float* omega_data = new float[height_map_size * sizeof(float)]; initHeightMap(h0_data, omega_data); - m_param = params; int hmap_dim = params.dmap_dim; int input_full_size = (hmap_dim + 4) * (hmap_dim + 1); // This value should be (hmap_dim / 2 + 1) * hmap_dim, but we use full sized buffer here for simplicity. @@ -188,7 +200,7 @@ wiOcean::wiOcean(const wiOceanParameter& params) SAFE_DELETE_ARRAY(h0_data); SAFE_DELETE_ARRAY(omega_data); - // D3D11 Textures + createTextureAndViews(hmap_dim, hmap_dim, FORMAT_R32G32B32A32_FLOAT, &m_pDisplacementMap); createTextureAndViews(hmap_dim, hmap_dim, FORMAT_R16G16B16A16_FLOAT, &m_pGradientMap); @@ -222,15 +234,12 @@ wiOcean::wiOcean(const wiOceanParameter& params) m_pPerFrameCB = new GPUBuffer; wiRenderer::GetDevice()->CreateBuffer(&cb_desc, nullptr, m_pPerFrameCB); - // FFT - fft512x512_create_plan(&m_fft_plan, 3); initRenderResource(); } wiOcean::~wiOcean() { - fft512x512_destroy_plan(&m_fft_plan); SAFE_DELETE(m_pBuffer_Float2_H0); SAFE_DELETE(m_pBuffer_Float_Omega); @@ -368,6 +377,7 @@ void wiOcean::UpdateDisplacementMap(float time, GRAPHICSTHREAD threadID) // Unbind device->UnBindUnorderedAccessResources(0, 1, threadID); device->UnBindResources(TEXSLOT_ONDEMAND0, 1, threadID); + device->BindCS(nullptr, threadID); device->GenerateMips(m_pGradientMap, threadID); @@ -406,100 +416,7 @@ void wiOcean::initRenderResource() g_WindDir = m_param.wind_dir; - // D3D buffers createSurfaceMesh(); - createFresnelMap(); - loadTextures(); - - - // Constants - GPUBufferDesc cb_desc; - cb_desc.Usage = USAGE_DYNAMIC; - cb_desc.BindFlags = BIND_CONSTANT_BUFFER; - cb_desc.CPUAccessFlags = CPU_ACCESS_WRITE; - cb_desc.MiscFlags = 0; - cb_desc.ByteWidth = sizeof(Ocean_Rendering_PatchCB); - cb_desc.StructureByteStride = 0; - g_pPerCallCB = new GPUBuffer; - device->CreateBuffer(&cb_desc, nullptr, g_pPerCallCB); - - Ocean_Rendering_ShadingCB shading_data; - // Grid side length * 2 - shading_data.g_TexelLength_x2 = m_param.patch_length / m_param.dmap_dim * 2;; - // Color - shading_data.g_SkyColor = g_SkyColor; - shading_data.g_WaterbodyColor = g_WaterbodyColor; - // Texcoord - shading_data.g_UVScale = 1.0f / m_param.patch_length; - shading_data.g_UVOffset = 0.5f / m_param.dmap_dim; - // Perlin - shading_data.g_PerlinSize = g_PerlinSize; - shading_data.g_PerlinAmplitude = g_PerlinAmplitude; - shading_data.g_PerlinGradient = g_PerlinGradient; - shading_data.g_PerlinOctave = g_PerlinOctave; - // Multiple reflection workaround - shading_data.g_BendParam = g_BendParam; - // Sun streaks - shading_data.g_SunColor = g_SunColor; - shading_data.g_SunDir = g_SunDir; - shading_data.g_Shineness = g_Shineness; - - SubresourceData cb_init_data; - cb_init_data.pSysMem = &shading_data; - cb_init_data.SysMemPitch = 0; - cb_init_data.SysMemSlicePitch = 0; - - cb_desc.Usage = USAGE_IMMUTABLE; - cb_desc.CPUAccessFlags = 0; - cb_desc.ByteWidth = sizeof(Ocean_Rendering_ShadingCB); - cb_desc.StructureByteStride = 0; - g_pShadingCB = new GPUBuffer; - device->CreateBuffer(&cb_desc, &cb_init_data, g_pShadingCB); - - // State blocks - RasterizerStateDesc ras_desc; - ras_desc.FillMode = FILL_SOLID; - ras_desc.CullMode = CULL_NONE; - ras_desc.FrontCounterClockwise = false; - ras_desc.DepthBias = 0; - ras_desc.SlopeScaledDepthBias = 0.0f; - ras_desc.DepthBiasClamp = 0.0f; - ras_desc.DepthClipEnable = true; - ras_desc.ScissorEnable = false; - ras_desc.MultisampleEnable = true; - ras_desc.AntialiasedLineEnable = false; - - g_pRSState_Solid = new RasterizerState; - device->CreateRasterizerState(&ras_desc, g_pRSState_Solid); - - ras_desc.FillMode = FILL_WIREFRAME; - - g_pRSState_Wireframe = new RasterizerState; - device->CreateRasterizerState(&ras_desc, g_pRSState_Wireframe); - - DepthStencilStateDesc depth_desc; - memset(&depth_desc, 0, sizeof(DepthStencilStateDesc)); - depth_desc.DepthEnable = true; - depth_desc.DepthWriteMask = DEPTH_WRITE_MASK_ALL; - depth_desc.DepthFunc = COMPARISON_GREATER; - depth_desc.StencilEnable = false; - g_pDSState_Disable = new DepthStencilState; - device->CreateDepthStencilState(&depth_desc, g_pDSState_Disable); - - BlendStateDesc blend_desc; - memset(&blend_desc, 0, sizeof(BlendStateDesc)); - blend_desc.AlphaToCoverageEnable = false; - blend_desc.IndependentBlendEnable = false; - blend_desc.RenderTarget[0].BlendEnable = true; - blend_desc.RenderTarget[0].SrcBlend = BLEND_SRC_ALPHA; - blend_desc.RenderTarget[0].DestBlend = BLEND_INV_SRC_ALPHA; - blend_desc.RenderTarget[0].BlendOp = BLEND_OP_ADD; - blend_desc.RenderTarget[0].SrcBlendAlpha = BLEND_ONE; - blend_desc.RenderTarget[0].DestBlendAlpha = BLEND_ZERO; - blend_desc.RenderTarget[0].BlendOpAlpha = BLEND_OP_ADD; - blend_desc.RenderTarget[0].RenderTargetWriteMask = COLOR_WRITE_ENABLE_ALL; - g_pBState_Transparent = new BlendState; - device->CreateBlendState(&blend_desc, g_pBState_Transparent); } void wiOcean::cleanupRenderResource() @@ -507,18 +424,6 @@ void wiOcean::cleanupRenderResource() SAFE_DELETE(g_pMeshIB); SAFE_DELETE(g_pMeshVB); - SAFE_DELETE(g_pFresnelMap); - SAFE_DELETE(g_pPerlinMap); - - SAFE_DELETE(g_pPerCallCB); - SAFE_DELETE(g_pPerFrameCB); - SAFE_DELETE(g_pShadingCB); - - SAFE_DELETE(g_pRSState_Solid); - SAFE_DELETE(g_pRSState_Wireframe); - SAFE_DELETE(g_pDSState_Disable); - SAFE_DELETE(g_pBState_Transparent); - g_render_list.clear(); } @@ -842,51 +747,6 @@ void wiOcean::createSurfaceMesh() SAFE_DELETE_ARRAY(index_array); } -void wiOcean::createFresnelMap() -{ - static const int FRESNEL_TEX_SIZE = 256; - static const float g_SkyBlending = 16.0f; - - uint32_t* buffer = new uint32_t[FRESNEL_TEX_SIZE]; - for (int i = 0; i < FRESNEL_TEX_SIZE; i++) - { - float cos_a = i / (FLOAT)FRESNEL_TEX_SIZE; - // Using water's refraction index 1.33 - uint32_t fresnel = (uint32_t)(XMVectorGetX(XMFresnelTerm(XMVectorSet(cos_a, cos_a, cos_a, cos_a), XMVectorSet(1.33f, 1.33f, 1.33f, 1.33f))) * 255); - - uint32_t sky_blend = (uint32_t)(powf(1 / (1 + cos_a), g_SkyBlending) * 255); - - buffer[i] = (sky_blend << 8) | fresnel; - } - - - - Texture1DDesc tex_desc; - tex_desc.Width = FRESNEL_TEX_SIZE; - tex_desc.MipLevels = 1; - tex_desc.ArraySize = 1; - tex_desc.Format = FORMAT_R8G8B8A8_UNORM; - tex_desc.Usage = USAGE_IMMUTABLE; - tex_desc.BindFlags = BIND_SHADER_RESOURCE; - tex_desc.CPUAccessFlags = 0; - tex_desc.MiscFlags = 0; - - SubresourceData init_data; - init_data.pSysMem = buffer; - init_data.SysMemPitch = 0; - init_data.SysMemSlicePitch = 0; - - HRESULT hr = wiRenderer::GetDevice()->CreateTexture1D(&tex_desc, &init_data, &g_pFresnelMap); - assert(SUCCEEDED(hr)); - - delete[] buffer; -} - -void wiOcean::loadTextures() -{ - wiRenderer::GetDevice()->CreateTextureFromFile("perlin_noise.dds", &g_pPerlinMap, true, GRAPHICSTHREAD_IMMEDIATE); -} - bool wiOcean::checkNodeVisibility(const QuadNode& quad_node, const Camera& camera) { // Plane equation setup @@ -1220,6 +1080,30 @@ void wiOcean::Render(const Camera* camera, float time, GRAPHICSTHREAD threadID) device->BindDepthStencilState(g_pDSState_Disable, 0, threadID); // Constants + + Ocean_Rendering_ShadingCB shading_data; + // Grid side length * 2 + shading_data.g_TexelLength_x2 = m_param.patch_length / m_param.dmap_dim * 2;; + // Color + shading_data.g_SkyColor = g_SkyColor; + shading_data.g_WaterbodyColor = g_WaterbodyColor; + // Texcoord + shading_data.g_UVScale = 1.0f / m_param.patch_length; + shading_data.g_UVOffset = 0.5f / m_param.dmap_dim; + // Perlin + shading_data.g_PerlinSize = g_PerlinSize; + shading_data.g_PerlinAmplitude = g_PerlinAmplitude; + shading_data.g_PerlinGradient = g_PerlinGradient; + shading_data.g_PerlinOctave = g_PerlinOctave; + // Multiple reflection workaround + shading_data.g_BendParam = g_BendParam; + // Sun streaks + shading_data.g_SunColor = g_SunColor; + shading_data.g_SunDir = g_SunDir; + shading_data.g_Shineness = g_Shineness; + + device->UpdateBuffer(g_pShadingCB, &shading_data, threadID); + device->BindConstantBufferVS(g_pShadingCB, CB_GETBINDSLOT(Ocean_Rendering_ShadingCB), threadID); device->BindConstantBufferPS(g_pShadingCB, CB_GETBINDSLOT(Ocean_Rendering_ShadingCB), threadID); @@ -1284,17 +1168,6 @@ void wiOcean::Render(const Camera* camera, float time, GRAPHICSTHREAD threadID) device->DrawIndexed(render_param.num_boundary_faces * 3, render_param.boundary_start_index, 0, threadID); } } - - //// Unbind - //vs_srvs[0] = NULL; - //vs_srvs[1] = NULL; - //pd3dContext->VSSetShaderResources(0, 2, &vs_srvs[0]); - - //ps_srvs[0] = NULL; - //ps_srvs[1] = NULL; - //ps_srvs[2] = NULL; - //ps_srvs[3] = NULL; - //pd3dContext->PSSetShaderResources(1, 4, &ps_srvs[0]); } @@ -1328,17 +1201,138 @@ void wiOcean::SetUpStatic() { LoadShaders(); CSFFT_512x512_Data_t::LoadShaders(); + fft512x512_create_plan(&m_fft_plan, 3); + + GraphicsDevice* device = wiRenderer::GetDevice(); + + static const int FRESNEL_TEX_SIZE = 256; + static const float g_SkyBlending = 16.0f; + + uint32_t* buffer = new uint32_t[FRESNEL_TEX_SIZE]; + for (int i = 0; i < FRESNEL_TEX_SIZE; i++) + { + float cos_a = i / (FLOAT)FRESNEL_TEX_SIZE; + // Using water's refraction index 1.33 + uint32_t fresnel = (uint32_t)(XMVectorGetX(XMFresnelTerm(XMVectorSet(cos_a, cos_a, cos_a, cos_a), XMVectorSet(1.33f, 1.33f, 1.33f, 1.33f))) * 255); + + uint32_t sky_blend = (uint32_t)(powf(1 / (1 + cos_a), g_SkyBlending) * 255); + + buffer[i] = (sky_blend << 8) | fresnel; + } + + + + Texture1DDesc tex_desc; + tex_desc.Width = FRESNEL_TEX_SIZE; + tex_desc.MipLevels = 1; + tex_desc.ArraySize = 1; + tex_desc.Format = FORMAT_R8G8B8A8_UNORM; + tex_desc.Usage = USAGE_IMMUTABLE; + tex_desc.BindFlags = BIND_SHADER_RESOURCE; + tex_desc.CPUAccessFlags = 0; + tex_desc.MiscFlags = 0; + + SubresourceData init_data; + init_data.pSysMem = buffer; + init_data.SysMemPitch = 0; + init_data.SysMemSlicePitch = 0; + + HRESULT hr = wiRenderer::GetDevice()->CreateTexture1D(&tex_desc, &init_data, &g_pFresnelMap); + assert(SUCCEEDED(hr)); + + delete[] buffer; + + + wiRenderer::GetDevice()->CreateTextureFromFile("perlin_noise.dds", &g_pPerlinMap, true, GRAPHICSTHREAD_IMMEDIATE); + + + // Constants + GPUBufferDesc cb_desc; + cb_desc.Usage = USAGE_DYNAMIC; + cb_desc.BindFlags = BIND_CONSTANT_BUFFER; + cb_desc.CPUAccessFlags = CPU_ACCESS_WRITE; + cb_desc.MiscFlags = 0; + cb_desc.ByteWidth = sizeof(Ocean_Rendering_PatchCB); + cb_desc.StructureByteStride = 0; + g_pPerCallCB = new GPUBuffer; + device->CreateBuffer(&cb_desc, nullptr, g_pPerCallCB); + + + cb_desc.Usage = USAGE_DYNAMIC; + cb_desc.CPUAccessFlags = CPU_ACCESS_WRITE; + cb_desc.ByteWidth = sizeof(Ocean_Rendering_ShadingCB); + cb_desc.StructureByteStride = 0; + g_pShadingCB = new GPUBuffer; + device->CreateBuffer(&cb_desc, nullptr, g_pShadingCB); + + // State blocks + RasterizerStateDesc ras_desc; + ras_desc.FillMode = FILL_SOLID; + ras_desc.CullMode = CULL_NONE; + ras_desc.FrontCounterClockwise = false; + ras_desc.DepthBias = 0; + ras_desc.SlopeScaledDepthBias = 0.0f; + ras_desc.DepthBiasClamp = 0.0f; + ras_desc.DepthClipEnable = true; + ras_desc.ScissorEnable = false; + ras_desc.MultisampleEnable = true; + ras_desc.AntialiasedLineEnable = false; + + g_pRSState_Solid = new RasterizerState; + device->CreateRasterizerState(&ras_desc, g_pRSState_Solid); + + ras_desc.FillMode = FILL_WIREFRAME; + + g_pRSState_Wireframe = new RasterizerState; + device->CreateRasterizerState(&ras_desc, g_pRSState_Wireframe); + + DepthStencilStateDesc depth_desc; + memset(&depth_desc, 0, sizeof(DepthStencilStateDesc)); + depth_desc.DepthEnable = true; + depth_desc.DepthWriteMask = DEPTH_WRITE_MASK_ALL; + depth_desc.DepthFunc = COMPARISON_GREATER; + depth_desc.StencilEnable = false; + g_pDSState_Disable = new DepthStencilState; + device->CreateDepthStencilState(&depth_desc, g_pDSState_Disable); + + BlendStateDesc blend_desc; + memset(&blend_desc, 0, sizeof(BlendStateDesc)); + blend_desc.AlphaToCoverageEnable = false; + blend_desc.IndependentBlendEnable = false; + blend_desc.RenderTarget[0].BlendEnable = true; + blend_desc.RenderTarget[0].SrcBlend = BLEND_SRC_ALPHA; + blend_desc.RenderTarget[0].DestBlend = BLEND_INV_SRC_ALPHA; + blend_desc.RenderTarget[0].BlendOp = BLEND_OP_ADD; + blend_desc.RenderTarget[0].SrcBlendAlpha = BLEND_ONE; + blend_desc.RenderTarget[0].DestBlendAlpha = BLEND_ZERO; + blend_desc.RenderTarget[0].BlendOpAlpha = BLEND_OP_ADD; + blend_desc.RenderTarget[0].RenderTargetWriteMask = COLOR_WRITE_ENABLE_ALL; + g_pBState_Transparent = new BlendState; + device->CreateBlendState(&blend_desc, g_pBState_Transparent); } void wiOcean::CleanUpStatic() { + fft512x512_destroy_plan(&m_fft_plan); + SAFE_DELETE(m_pUpdateSpectrumCS); SAFE_DELETE(m_pUpdateDisplacementMapCS); SAFE_DELETE(m_pUpdateGradientFoldingCS); - SAFE_DELETE(g_pMeshLayout); - SAFE_DELETE(g_pOceanSurfVS); SAFE_DELETE(g_pOceanSurfPS); SAFE_DELETE(g_pWireframePS); + + SAFE_DELETE(g_pMeshLayout); + + SAFE_DELETE(g_pFresnelMap); + SAFE_DELETE(g_pPerlinMap); + + SAFE_DELETE(g_pPerCallCB); + SAFE_DELETE(g_pShadingCB); + + SAFE_DELETE(g_pRSState_Solid); + SAFE_DELETE(g_pRSState_Wireframe); + SAFE_DELETE(g_pDSState_Disable); + SAFE_DELETE(g_pBState_Transparent); } diff --git a/WickedEngine/wiOcean.h b/WickedEngine/wiOcean.h index 23bcc0b87..56bf8ecdf 100644 --- a/WickedEngine/wiOcean.h +++ b/WickedEngine/wiOcean.h @@ -33,10 +33,21 @@ struct wiOceanParameter wiOceanParameter() { + // Original version: + //dmap_dim = 512; + //patch_length = 2000.0f; + //time_scale = 0.8f; + //wave_amplitude = 0.35f; + //wind_dir = XMFLOAT2(0.8f, 0.6f); + //wind_speed = 600.0f; + //wind_dependency = 0.07f; + //choppy_scale = 1.3f; + + // Scaled version: dmap_dim = 512; - patch_length = 2000.0f; + patch_length = 200.0f; time_scale = 0.8f; - wave_amplitude = 0.35f; + wave_amplitude = 80.0f; wind_dir = XMFLOAT2(0.8f, 0.6f); wind_speed = 600.0f; wind_dependency = 0.07f; @@ -102,7 +113,7 @@ protected: wiGraphicsTypes::GPUBuffer* m_pPerFrameCB; // FFT wrap-up - CSFFT512x512_Plan m_fft_plan; + static CSFFT512x512_Plan m_fft_plan; @@ -185,25 +196,24 @@ protected: static wiGraphicsTypes::VertexLayout* g_pMeshLayout; // Color look up 1D texture - wiGraphicsTypes::Texture1D* g_pFresnelMap = nullptr; + static wiGraphicsTypes::Texture1D* g_pFresnelMap; // Distant perlin wave - wiGraphicsTypes::Texture2D* g_pPerlinMap = nullptr; + static wiGraphicsTypes::Texture2D* g_pPerlinMap; // HLSL shaders static wiGraphicsTypes::VertexShader* g_pOceanSurfVS; static wiGraphicsTypes::PixelShader* g_pOceanSurfPS; static wiGraphicsTypes::PixelShader* g_pWireframePS; - wiGraphicsTypes::GPUBuffer* g_pPerCallCB = nullptr; - wiGraphicsTypes::GPUBuffer* g_pPerFrameCB = nullptr; - wiGraphicsTypes::GPUBuffer* g_pShadingCB = nullptr; + static wiGraphicsTypes::GPUBuffer* g_pPerCallCB; + static wiGraphicsTypes::GPUBuffer* g_pShadingCB; // State blocks - wiGraphicsTypes::RasterizerState* g_pRSState_Solid = nullptr; - wiGraphicsTypes::RasterizerState* g_pRSState_Wireframe = nullptr; - wiGraphicsTypes::DepthStencilState* g_pDSState_Disable = nullptr; - wiGraphicsTypes::BlendState* g_pBState_Transparent = nullptr; + static wiGraphicsTypes::RasterizerState* g_pRSState_Solid; + static wiGraphicsTypes::RasterizerState* g_pRSState_Wireframe; + static wiGraphicsTypes::DepthStencilState* g_pDSState_Disable; + static wiGraphicsTypes::BlendState* g_pBState_Transparent; // init & cleanup diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index 8eee7be75..8818d8da4 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -937,6 +937,8 @@ void wiRenderer::ReloadShaders(const std::string& path) wiFont::LoadShaders(); wiImage::LoadShaders(); wiLensFlare::LoadShaders(); + wiOcean::LoadShaders(); + CSFFT_512x512_Data_t::LoadShaders(); GetDevice()->UNLOCK(); } From 2d254ae5ed1dcce256f3951dc3800207646e8a95 Mon Sep 17 00:00:00 2001 From: Turanszki Janos Date: Sun, 12 Nov 2017 18:01:21 +0000 Subject: [PATCH 10/10] ocean update --- Editor/OceanWindow.cpp | 14 +++++++++++++- Editor/OceanWindow.h | 2 ++ WickedEngine/wiOcean.cpp | 2 +- WickedEngine/wiOcean.h | 3 ++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Editor/OceanWindow.cpp b/Editor/OceanWindow.cpp index ba0e03a9c..c698ee7d8 100644 --- a/Editor/OceanWindow.cpp +++ b/Editor/OceanWindow.cpp @@ -11,7 +11,7 @@ OceanWindow::OceanWindow(wiGUI* gui) :GUI(gui) oceanWindow = new wiWindow(GUI, "Ocean Window"); - oceanWindow->SetSize(XMFLOAT2(600, 300)); + oceanWindow->SetSize(XMFLOAT2(700, 300)); GUI->AddWidget(oceanWindow); float x = 200; @@ -78,6 +78,18 @@ OceanWindow::OceanWindow(wiGUI* gui) :GUI(gui) oceanWindow->AddWidget(timeScaleSlider); + colorPicker = new wiColorPicker(GUI, "Water Color"); + colorPicker->SetPos(XMFLOAT2(380, 30)); + colorPicker->RemoveWidgets(); + colorPicker->SetVisible(true); + colorPicker->SetEnabled(true); + colorPicker->OnColorChanged([&](wiEventArgs args) { + if (wiRenderer::GetOcean() != nullptr) + wiRenderer::GetOcean()->waterColor = XMFLOAT3(args.color.x, args.color.y, args.color.z); + }); + oceanWindow->AddWidget(colorPicker); + + oceanWindow->Translate(XMFLOAT3(800, 50, 0)); oceanWindow->SetVisible(false); } diff --git a/Editor/OceanWindow.h b/Editor/OceanWindow.h index 61d351514..9f9297a06 100644 --- a/Editor/OceanWindow.h +++ b/Editor/OceanWindow.h @@ -7,6 +7,7 @@ class wiWindow; class wiLabel; class wiCheckBox; class wiSlider; +class wiColorPicker; class OceanWindow { @@ -25,5 +26,6 @@ public: wiSlider* choppyScaleSlider; wiSlider* windDependencySlider; wiSlider* timeScaleSlider; + wiColorPicker* colorPicker; }; diff --git a/WickedEngine/wiOcean.cpp b/WickedEngine/wiOcean.cpp index 5a436668d..18d893685 100644 --- a/WickedEngine/wiOcean.cpp +++ b/WickedEngine/wiOcean.cpp @@ -1086,7 +1086,7 @@ void wiOcean::Render(const Camera* camera, float time, GRAPHICSTHREAD threadID) shading_data.g_TexelLength_x2 = m_param.patch_length / m_param.dmap_dim * 2;; // Color shading_data.g_SkyColor = g_SkyColor; - shading_data.g_WaterbodyColor = g_WaterbodyColor; + shading_data.g_WaterbodyColor = waterColor; // Texcoord shading_data.g_UVScale = 1.0f / m_param.patch_length; shading_data.g_UVOffset = 0.5f / m_param.dmap_dim; diff --git a/WickedEngine/wiOcean.h b/WickedEngine/wiOcean.h index 56bf8ecdf..4287fe8fa 100644 --- a/WickedEngine/wiOcean.h +++ b/WickedEngine/wiOcean.h @@ -78,6 +78,8 @@ public: static void SetUpStatic(); static void CleanUpStatic(); + XMFLOAT3 waterColor = XMFLOAT3(0.07f, 0.15f, 0.2f); + protected: wiOceanParameter m_param; @@ -137,7 +139,6 @@ protected: // Shading properties: // Two colors for waterbody and sky color XMFLOAT3 g_SkyColor = XMFLOAT3(0.38f, 0.45f, 0.56f); - XMFLOAT3 g_WaterbodyColor = XMFLOAT3(0.07f, 0.15f, 0.2f); // Blending term for sky cubemap float g_SkyBlending = 16.0f;