671 lines
19 KiB
C++
671 lines
19 KiB
C++
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
|
|
|
#include "RenderTools.h"
|
|
#include "PixelFormatExtensions.h"
|
|
#include "Engine/Core/Types/BaseTypes.h"
|
|
#include "PixelFormat.h"
|
|
#include "RenderView.h"
|
|
#include "GPUDevice.h"
|
|
#include "RenderTask.h"
|
|
#include "Engine/Content/Assets/Model.h"
|
|
#include "Engine/Content/Assets/SkinnedModel.h"
|
|
#include "Engine/Core/Log.h"
|
|
#include "Engine/Core/Math/Packed.h"
|
|
#include "Engine/Engine/Time.h"
|
|
|
|
const Char* ToString(RendererType value)
|
|
{
|
|
const Char* result;
|
|
switch (value)
|
|
{
|
|
case RendererType::Unknown:
|
|
result = TEXT("Unknown");
|
|
break;
|
|
case RendererType::DirectX10:
|
|
result = TEXT("DirectX 10");
|
|
break;
|
|
case RendererType::DirectX10_1:
|
|
result = TEXT("DirectX 10.1");
|
|
break;
|
|
case RendererType::DirectX11:
|
|
result = TEXT("DirectX 11");
|
|
break;
|
|
case RendererType::DirectX12:
|
|
result = TEXT("DirectX 12");
|
|
break;
|
|
case RendererType::OpenGL4_1:
|
|
result = TEXT("OpenGL 4.1");
|
|
break;
|
|
case RendererType::OpenGL4_4:
|
|
result = TEXT("OpenGL 4.4");
|
|
break;
|
|
case RendererType::OpenGLES3:
|
|
result = TEXT("OpenGL ES 3");
|
|
break;
|
|
case RendererType::OpenGLES3_1:
|
|
result = TEXT("OpenGL ES 3.1");
|
|
break;
|
|
case RendererType::Null:
|
|
result = TEXT("Null");
|
|
break;
|
|
case RendererType::Vulkan:
|
|
result = TEXT("Vulkan");
|
|
break;
|
|
case RendererType::PS4:
|
|
result = TEXT("PS4");
|
|
break;
|
|
case RendererType::PS5:
|
|
result = TEXT("PS5");
|
|
break;
|
|
default:
|
|
result = TEXT("?");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const Char* ToString(ShaderProfile value)
|
|
{
|
|
const Char* result;
|
|
switch (value)
|
|
{
|
|
case ShaderProfile::Unknown:
|
|
result = TEXT("Unknown");
|
|
break;
|
|
case ShaderProfile::DirectX_SM4:
|
|
result = TEXT("DirectX SM4");
|
|
break;
|
|
case ShaderProfile::DirectX_SM5:
|
|
result = TEXT("DirectX SM5");
|
|
break;
|
|
case ShaderProfile::DirectX_SM6:
|
|
result = TEXT("DirectX SM6");
|
|
break;
|
|
case ShaderProfile::GLSL_410:
|
|
result = TEXT("GLSL 410");
|
|
break;
|
|
case ShaderProfile::GLSL_440:
|
|
result = TEXT("GLSL 440");
|
|
break;
|
|
case ShaderProfile::Vulkan_SM5:
|
|
result = TEXT("Vulkan SM5");
|
|
break;
|
|
case ShaderProfile::PS4:
|
|
result = TEXT("PS4");
|
|
break;
|
|
case ShaderProfile::PS5:
|
|
result = TEXT("PS5");
|
|
break;
|
|
default:
|
|
result = TEXT("?");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const Char* ToString(FeatureLevel value)
|
|
{
|
|
const Char* result;
|
|
switch (value)
|
|
{
|
|
case FeatureLevel::ES2:
|
|
result = TEXT("ES2");
|
|
break;
|
|
case FeatureLevel::ES3:
|
|
result = TEXT("ES3");
|
|
break;
|
|
case FeatureLevel::ES3_1:
|
|
result = TEXT("ES3_1");
|
|
break;
|
|
case FeatureLevel::SM4:
|
|
result = TEXT("SM4");
|
|
break;
|
|
case FeatureLevel::SM5:
|
|
result = TEXT("SM5");
|
|
break;
|
|
case FeatureLevel::SM6:
|
|
result = TEXT("SM6");
|
|
break;
|
|
default:
|
|
result = TEXT("?");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const Char* ToString(MSAALevel value)
|
|
{
|
|
const Char* result;
|
|
switch (value)
|
|
{
|
|
case MSAALevel::None:
|
|
result = TEXT("None");
|
|
break;
|
|
case MSAALevel::X2:
|
|
result = TEXT("X2");
|
|
break;
|
|
case MSAALevel::X4:
|
|
result = TEXT("X4");
|
|
break;
|
|
case MSAALevel::X8:
|
|
result = TEXT("X8");
|
|
break;
|
|
default:
|
|
result = TEXT("?");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool BlendingMode::operator==(const BlendingMode& other) const
|
|
{
|
|
#define EQUAL(x) x == other.x &&
|
|
return EQUAL(BlendEnable)
|
|
EQUAL(SrcBlend)
|
|
EQUAL(DestBlend)
|
|
EQUAL(BlendOp)
|
|
EQUAL(SrcBlendAlpha)
|
|
EQUAL(DestBlendAlpha)
|
|
EQUAL(BlendOpAlpha)
|
|
EQUAL(RenderTargetWriteMask)
|
|
AlphaToCoverageEnable == other.AlphaToCoverageEnable;
|
|
#undef EQUAL
|
|
}
|
|
|
|
uint32 GetHash(const BlendingMode& key)
|
|
{
|
|
uint32 hash = key.AlphaToCoverageEnable ? 1 : 0;
|
|
CombineHash(hash, key.BlendEnable ? 1 : 0);
|
|
CombineHash(hash, (uint32)key.SrcBlend);
|
|
CombineHash(hash, (uint32)key.DestBlend);
|
|
CombineHash(hash, (uint32)key.BlendOp);
|
|
CombineHash(hash, (uint32)key.SrcBlendAlpha);
|
|
CombineHash(hash, (uint32)key.DestBlendAlpha);
|
|
CombineHash(hash, (uint32)key.BlendOpAlpha);
|
|
CombineHash(hash, (uint32)key.RenderTargetWriteMask);
|
|
return hash;
|
|
}
|
|
|
|
// @formatter:off
|
|
|
|
BlendingMode BlendingMode::Opaque =
|
|
{
|
|
false, // AlphaToCoverageEnable
|
|
false, // BlendEnable
|
|
Blend::One, // SrcBlend
|
|
Blend::Zero, // DestBlend
|
|
Operation::Add, // BlendOp
|
|
Blend::One, // SrcBlendAlpha
|
|
Blend::Zero, // DestBlendAlpha
|
|
Operation::Add, // BlendOpAlpha
|
|
ColorWrite::All, // RenderTargetWriteMask
|
|
};
|
|
|
|
BlendingMode BlendingMode::Additive =
|
|
{
|
|
false, // AlphaToCoverageEnable
|
|
true, // BlendEnable
|
|
Blend::SrcAlpha, // SrcBlend
|
|
Blend::One, // DestBlend
|
|
Operation::Add, // BlendOp
|
|
Blend::SrcAlpha, // SrcBlendAlpha
|
|
Blend::One, // DestBlendAlpha
|
|
Operation::Add, // BlendOpAlpha
|
|
ColorWrite::All, // RenderTargetWriteMask
|
|
};
|
|
|
|
BlendingMode BlendingMode::AlphaBlend =
|
|
{
|
|
false, // AlphaToCoverageEnable
|
|
true, // BlendEnable
|
|
Blend::SrcAlpha, // SrcBlend
|
|
Blend::InvSrcAlpha, // DestBlend
|
|
Operation::Add, // BlendOp
|
|
Blend::One, // SrcBlendAlpha
|
|
Blend::InvSrcAlpha, // DestBlendAlpha
|
|
Operation::Add, // BlendOpAlpha
|
|
ColorWrite::All, // RenderTargetWriteMask
|
|
};
|
|
|
|
BlendingMode BlendingMode::Add =
|
|
{
|
|
false, // AlphaToCoverageEnable
|
|
true, // BlendEnable
|
|
Blend::One, // SrcBlend
|
|
Blend::One, // DestBlend
|
|
Operation::Add, // BlendOp
|
|
Blend::One, // SrcBlendAlpha
|
|
Blend::One, // DestBlendAlpha
|
|
Operation::Add, // BlendOpAlpha
|
|
ColorWrite::All, // RenderTargetWriteMask
|
|
};
|
|
|
|
BlendingMode BlendingMode::Multiply =
|
|
{
|
|
false, // AlphaToCoverageEnable
|
|
true, // BlendEnable
|
|
Blend::Zero, // SrcBlend
|
|
Blend::SrcColor, // DestBlend
|
|
Operation::Add, // BlendOp
|
|
Blend::Zero, // SrcBlendAlpha
|
|
Blend::SrcAlpha, // DestBlendAlpha
|
|
Operation::Add, // BlendOpAlpha
|
|
ColorWrite::All, // RenderTargetWriteMask
|
|
};
|
|
|
|
// @formatter:on
|
|
|
|
FeatureLevel RenderTools::GetFeatureLevel(ShaderProfile profile)
|
|
{
|
|
switch (profile)
|
|
{
|
|
case ShaderProfile::DirectX_SM6:
|
|
case ShaderProfile::PS5:
|
|
return FeatureLevel::SM6;
|
|
case ShaderProfile::DirectX_SM5:
|
|
case ShaderProfile::Vulkan_SM5:
|
|
case ShaderProfile::PS4:
|
|
return FeatureLevel::SM5;
|
|
case ShaderProfile::DirectX_SM4:
|
|
return FeatureLevel::SM4;
|
|
case ShaderProfile::GLSL_440:
|
|
case ShaderProfile::GLSL_410:
|
|
case ShaderProfile::Unknown:
|
|
return FeatureLevel::ES2;
|
|
default:
|
|
return FeatureLevel::ES2;
|
|
}
|
|
}
|
|
|
|
bool RenderTools::CanSupportTessellation(ShaderProfile profile)
|
|
{
|
|
switch (profile)
|
|
{
|
|
case ShaderProfile::Vulkan_SM5:
|
|
case ShaderProfile::DirectX_SM6:
|
|
case ShaderProfile::DirectX_SM5:
|
|
case ShaderProfile::PS4:
|
|
case ShaderProfile::PS5:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void RenderTools::ComputePitch(PixelFormat format, int32 width, int32 height, uint32& rowPitch, uint32& slicePitch)
|
|
{
|
|
switch (format)
|
|
{
|
|
case PixelFormat::BC1_Typeless:
|
|
case PixelFormat::BC1_UNorm:
|
|
case PixelFormat::BC1_UNorm_sRGB:
|
|
case PixelFormat::BC4_Typeless:
|
|
case PixelFormat::BC4_UNorm:
|
|
case PixelFormat::BC4_SNorm:
|
|
ASSERT(PixelFormatExtensions::IsCompressed(format));
|
|
{
|
|
uint32 nbw = Math::Max<uint32>(1, (width + 3) / 4);
|
|
uint32 nbh = Math::Max<uint32>(1, (height + 3) / 4);
|
|
rowPitch = nbw * 8;
|
|
slicePitch = rowPitch * nbh;
|
|
}
|
|
break;
|
|
case PixelFormat::BC2_Typeless:
|
|
case PixelFormat::BC2_UNorm:
|
|
case PixelFormat::BC2_UNorm_sRGB:
|
|
case PixelFormat::BC3_Typeless:
|
|
case PixelFormat::BC3_UNorm:
|
|
case PixelFormat::BC3_UNorm_sRGB:
|
|
case PixelFormat::BC5_Typeless:
|
|
case PixelFormat::BC5_UNorm:
|
|
case PixelFormat::BC5_SNorm:
|
|
case PixelFormat::BC6H_Typeless:
|
|
case PixelFormat::BC6H_Uf16:
|
|
case PixelFormat::BC6H_Sf16:
|
|
case PixelFormat::BC7_Typeless:
|
|
case PixelFormat::BC7_UNorm:
|
|
case PixelFormat::BC7_UNorm_sRGB:
|
|
ASSERT(PixelFormatExtensions::IsCompressed(format));
|
|
{
|
|
uint32 nbw = Math::Max<uint32>(1, (width + 3) / 4);
|
|
uint32 nbh = Math::Max<uint32>(1, (height + 3) / 4);
|
|
rowPitch = nbw * 16;
|
|
slicePitch = rowPitch * nbh;
|
|
}
|
|
break;
|
|
case PixelFormat::ASTC_4x4_UNorm:
|
|
case PixelFormat::ASTC_4x4_UNorm_sRGB:
|
|
case PixelFormat::ASTC_6x6_UNorm:
|
|
case PixelFormat::ASTC_6x6_UNorm_sRGB:
|
|
case PixelFormat::ASTC_8x8_UNorm:
|
|
case PixelFormat::ASTC_8x8_UNorm_sRGB:
|
|
case PixelFormat::ASTC_10x10_UNorm:
|
|
case PixelFormat::ASTC_10x10_UNorm_sRGB:
|
|
{
|
|
const int32 blockSize = PixelFormatExtensions::ComputeBlockSize(format);
|
|
uint32 nbw = Math::Max<uint32>(1, Math::DivideAndRoundUp(width, blockSize));
|
|
uint32 nbh = Math::Max<uint32>(1, Math::DivideAndRoundUp(height, blockSize));
|
|
rowPitch = nbw * 16; // All ASTC blocks use 128 bits
|
|
slicePitch = rowPitch * nbh;
|
|
}
|
|
break;
|
|
case PixelFormat::R8G8_B8G8_UNorm:
|
|
case PixelFormat::G8R8_G8B8_UNorm:
|
|
ASSERT(PixelFormatExtensions::IsPacked(format));
|
|
rowPitch = ((width + 1) >> 1) * 4;
|
|
slicePitch = rowPitch * height;
|
|
break;
|
|
default:
|
|
ASSERT(PixelFormatExtensions::IsValid(format));
|
|
ASSERT(!PixelFormatExtensions::IsCompressed(format) && !PixelFormatExtensions::IsPacked(format) && !PixelFormatExtensions::IsPlanar(format));
|
|
{
|
|
uint32 bpp = PixelFormatExtensions::SizeInBits(format);
|
|
|
|
// Default byte alignment
|
|
rowPitch = (width * bpp + 7) / 8;
|
|
slicePitch = rowPitch * height;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RenderTools::UpdateModelLODTransition(byte& lodTransition)
|
|
{
|
|
// TODO: expose this parameter to be configured per game (global config)
|
|
const float ModelLODTransitionTime = 0.3f;
|
|
|
|
// Update LOD transition (note: LODTransition is mapped from [0-255] to [0-ModelLODTransitionTime] using fixed point value to use 8bit only)
|
|
const float normalizedProgress = static_cast<float>(lodTransition) * (1.0f / 255.0f);
|
|
const float deltaProgress = Time::Draw.UnscaledDeltaTime.GetTotalSeconds() / ModelLODTransitionTime;
|
|
const auto newProgress = static_cast<int32>((normalizedProgress + deltaProgress) * 255.0f);
|
|
lodTransition = static_cast<byte>(Math::Min<int32>(newProgress, 255));
|
|
}
|
|
|
|
uint64 RenderTools::CalculateTextureMemoryUsage(PixelFormat format, int32 width, int32 height, int32 mipLevels)
|
|
{
|
|
uint64 result = 0;
|
|
|
|
if (mipLevels == 0)
|
|
mipLevels = 69;
|
|
|
|
uint32 rowPitch, slicePitch;
|
|
while (mipLevels > 0 && (width >= 1 || height >= 1))
|
|
{
|
|
ComputePitch(format, width, height, rowPitch, slicePitch);
|
|
result += slicePitch;
|
|
|
|
if (width > 1)
|
|
width >>= 1;
|
|
if (height > 1)
|
|
height >>= 1;
|
|
|
|
mipLevels--;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
uint64 RenderTools::CalculateTextureMemoryUsage(PixelFormat format, int32 width, int32 height, int32 depth, int32 mipLevels)
|
|
{
|
|
return CalculateTextureMemoryUsage(format, width, height, mipLevels) * depth;
|
|
}
|
|
|
|
float RenderTools::ComputeBoundsScreenRadiusSquared(const Float3& origin, float radius, const Float3& viewOrigin, const Matrix& projectionMatrix)
|
|
{
|
|
const float screenMultiple = 0.5f * Math::Max(projectionMatrix.Values[0][0], projectionMatrix.Values[1][1]);
|
|
const float distSqr = Float3::DistanceSquared(origin, viewOrigin) * projectionMatrix.Values[2][3];
|
|
return Math::Square(screenMultiple * radius) / Math::Max(1.0f, distSqr);
|
|
}
|
|
|
|
int32 RenderTools::ComputeModelLOD(const Model* model, const Float3& origin, float radius, const RenderContext& renderContext)
|
|
{
|
|
const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View);
|
|
const float screenRadiusSquared = ComputeBoundsScreenRadiusSquared(origin, radius, *lodView) * renderContext.View.ModelLODDistanceFactorSqrt;
|
|
|
|
// Check if model is being culled
|
|
if (Math::Square(model->MinScreenSize * 0.5f) > screenRadiusSquared)
|
|
return -1;
|
|
|
|
// Skip if no need to calculate LOD
|
|
if (model->LODs.Count() <= 1)
|
|
return 0;
|
|
|
|
// Iterate backwards and return the first matching LOD
|
|
for (int32 lodIndex = model->LODs.Count() - 1; lodIndex >= 0; lodIndex--)
|
|
{
|
|
if (Math::Square(model->LODs[lodIndex].ScreenSize * 0.5f) >= screenRadiusSquared)
|
|
{
|
|
return lodIndex;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32 RenderTools::ComputeSkinnedModelLOD(const SkinnedModel* model, const Float3& origin, float radius, const RenderContext& renderContext)
|
|
{
|
|
const auto lodView = (renderContext.LodProxyView ? renderContext.LodProxyView : &renderContext.View);
|
|
const float screenRadiusSquared = ComputeBoundsScreenRadiusSquared(origin, radius, *lodView) * renderContext.View.ModelLODDistanceFactorSqrt;
|
|
|
|
// Check if model is being culled
|
|
if (Math::Square(model->MinScreenSize * 0.5f) > screenRadiusSquared)
|
|
return -1;
|
|
|
|
// Skip if no need to calculate LOD
|
|
if (model->LODs.Count() <= 1)
|
|
return 0;
|
|
|
|
// Iterate backwards and return the first matching LOD
|
|
for (int32 lodIndex = model->LODs.Count() - 1; lodIndex >= 0; lodIndex--)
|
|
{
|
|
if (Math::Square(model->LODs[lodIndex].ScreenSize * 0.5f) >= screenRadiusSquared)
|
|
{
|
|
return lodIndex;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void RenderTools::ComputeCascadeUpdateFrequency(int32 cascadeIndex, int32 cascadeCount, int32& updateFrequency, int32& updatePhrase, int32 updateMaxCountPerFrame)
|
|
{
|
|
switch (updateMaxCountPerFrame)
|
|
{
|
|
case 1: // 1 cascade update per frame
|
|
switch (cascadeIndex)
|
|
{
|
|
case 0: // Cascade 0
|
|
updateFrequency = 2;
|
|
updatePhrase = 0;
|
|
break;
|
|
case 1: // Cascade 1
|
|
updateFrequency = 4;
|
|
updatePhrase = 1;
|
|
break;
|
|
case 2: // Cascade 2
|
|
updateFrequency = 8;
|
|
updatePhrase = 3;
|
|
break;
|
|
default:
|
|
if (cascadeCount > 4)
|
|
{
|
|
if (cascadeIndex == 3)
|
|
{
|
|
// Cascade 3
|
|
updateFrequency = 16;
|
|
updatePhrase = 7;
|
|
}
|
|
else
|
|
{
|
|
// Other
|
|
updateFrequency = 16;
|
|
updatePhrase = 15;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Cascade 3
|
|
updateFrequency = 8;
|
|
updatePhrase = 7;
|
|
}
|
|
}
|
|
break;
|
|
case 2: // 2 cascade2 update per frame
|
|
switch (cascadeIndex)
|
|
{
|
|
case 0: // Cascade 0
|
|
updateFrequency = 1;
|
|
updatePhrase = 0;
|
|
break;
|
|
case 1: // Cascade 1
|
|
updateFrequency = 2;
|
|
updatePhrase = 0;
|
|
break;
|
|
case 2: // Cascade 2
|
|
updateFrequency = 4;
|
|
updatePhrase = 1;
|
|
break;
|
|
default:
|
|
if (cascadeCount > 4)
|
|
{
|
|
if (cascadeIndex == 3)
|
|
{
|
|
// Cascade 3
|
|
updateFrequency = 8;
|
|
updatePhrase = 3;
|
|
}
|
|
else
|
|
{
|
|
// Other
|
|
updateFrequency = 8;
|
|
updatePhrase = 7;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Cascade 3
|
|
updateFrequency = 4;
|
|
updatePhrase = 3;
|
|
}
|
|
}
|
|
break;
|
|
default: // Every frame
|
|
updateFrequency = 1;
|
|
updatePhrase = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal)
|
|
{
|
|
// Calculate tangent
|
|
const Float3 c1 = Float3::Cross(normal, Float3::UnitZ);
|
|
const Float3 c2 = Float3::Cross(normal, Float3::UnitY);
|
|
const Float3 tangent = c1.LengthSquared() > c2.LengthSquared() ? c1 : c2;
|
|
|
|
// Calculate bitangent sign
|
|
const Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent));
|
|
const byte sign = static_cast<byte>(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
|
|
|
// Set tangent frame
|
|
resultNormal = Float1010102(normal * 0.5f + 0.5f, 0);
|
|
resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
|
}
|
|
|
|
void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent)
|
|
{
|
|
// Calculate bitangent sign
|
|
const Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent));
|
|
const byte sign = static_cast<byte>(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
|
|
|
|
// Set tangent frame
|
|
resultNormal = Float1010102(normal * 0.5f + 0.5f, 0);
|
|
resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign);
|
|
}
|
|
|
|
int32 MipLevelsCount(int32 width, bool useMipLevels)
|
|
{
|
|
if (!useMipLevels)
|
|
return 1;
|
|
|
|
int32 result = 1;
|
|
while (width > 1)
|
|
{
|
|
width >>= 1;
|
|
result++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int32 MipLevelsCount(int32 width, int32 height, bool useMipLevels)
|
|
{
|
|
// Check if use mip maps
|
|
if (!useMipLevels)
|
|
{
|
|
// No mipmaps chain, only single mip map
|
|
return 1;
|
|
}
|
|
|
|
// Count mip maps
|
|
int32 result = 1;
|
|
while (width > 1 || height > 1)
|
|
{
|
|
if (width > 1)
|
|
width >>= 1;
|
|
if (height > 1)
|
|
height >>= 1;
|
|
result++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int32 MipLevelsCount(int32 width, int32 height, int32 depth, bool useMipLevels)
|
|
{
|
|
if (!useMipLevels)
|
|
return 1;
|
|
|
|
int32 result = 1;
|
|
while (width > 1 || height > 1 || depth > 1)
|
|
{
|
|
if (width > 1)
|
|
width >>= 1;
|
|
if (height > 1)
|
|
height >>= 1;
|
|
if (depth > 1)
|
|
depth >>= 1;
|
|
result++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
float ViewToCenterLessRadius(const RenderView& view, const Float3& center, float radius)
|
|
{
|
|
// Calculate distance from view to sphere center
|
|
float viewToCenter = Float3::Distance(view.Position, center);
|
|
|
|
// Check if need to fix the radius
|
|
//if (radius + viewToCenter > view.Far)
|
|
{
|
|
// Clamp radius
|
|
//radius = view.Far - viewToCenter;
|
|
}
|
|
|
|
// Calculate result
|
|
return viewToCenter - radius;
|
|
}
|
|
|
|
void MeshBase::SetMaterialSlotIndex(int32 value)
|
|
{
|
|
if (value < 0 || value >= _model->MaterialSlots.Count())
|
|
{
|
|
LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count());
|
|
return;
|
|
}
|
|
|
|
_materialSlotIndex = value;
|
|
}
|
|
|
|
void MeshBase::SetBounds(const BoundingBox& box)
|
|
{
|
|
_box = box;
|
|
BoundingSphere::FromBox(box, _sphere);
|
|
}
|