Merge remote-tracking branch 'origin/master' into 1.10

# Conflicts:
#	Source/Engine/Platform/StringUtils.h
This commit is contained in:
Wojtek Figat
2024-10-11 18:07:41 +02:00
37 changed files with 642 additions and 107 deletions

View File

@@ -80,12 +80,12 @@ public:
public:
bool operator==(const Color32& other) const
{
return R == other.R && G == other.G && B == other.B && A == other.A;
return Raw == other.Raw;
}
bool operator!=(const Color32& other) const
{
return R != other.R || G != other.G || B != other.B || A != other.A;
return Raw != other.Raw;
}
Color32 operator+(const Color32& b) const
@@ -138,7 +138,7 @@ public:
// Returns true if color is fully transparent (all components are equal zero).
bool IsTransparent() const
{
return R + G + B + A == 0;
return Raw == 0;
}
// Returns true if color has opacity channel in use (different from 255).
@@ -222,7 +222,7 @@ namespace Math
{
FORCE_INLINE static bool NearEqual(const Color32& a, const Color32& b)
{
return a == b;
return a.Raw == b.Raw;
}
}

View File

@@ -173,8 +173,8 @@ public:
/// <summary>
/// Frequency of shadow updates at the maximum distance from the view at which shadows are still rendered. This value is multiplied by ShadowsUpdateRate and allows scaling the update rate in-between the shadow range. For example, if light is near view, it will get normal shadow updates but will reduce this rate when far from view. See ShadowsUpdateRate to learn more.
/// </summary>
API_FIELD(Attributes="EditorOrder(105), EditorDisplay(\"Shadow\", \"Update Rate At Distance\"), Limit(0.0f, 1.0f)")
float ShadowsUpdateRateAtDistance = 0.5f;
API_FIELD(Attributes="EditorOrder(101), EditorDisplay(\"Shadow\", \"Update Rate At Distance\"), Limit(0.0f, 1.0f)")
float ShadowsUpdateRateAtDistance = 1.0f;
/// <summary>
/// Defines the resolution of the shadow map texture used to draw objects projection from light-point-of-view. Higher values increase shadow quality at cost of performance.

View File

@@ -52,7 +52,7 @@ public:
/// <summary>
/// Gets the joint mode flags. Controls joint behaviour.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(DistanceJointFlag.MinDistance | DistanceJointFlag.MaxDistance)")
API_PROPERTY(Attributes="EditorOrder(100), EditorDisplay(\"Joint\"), DefaultValue(DistanceJointFlag.MinDistance | DistanceJointFlag.MaxDistance)")
FORCE_INLINE DistanceJointFlag GetFlags() const
{
return _flags;

View File

@@ -80,7 +80,7 @@ public:
/// <summary>
/// Gets the joint mode flags. Controls joint behaviour.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(100), DefaultValue(HingeJointFlag.Limit | HingeJointFlag.Drive)")
API_PROPERTY(Attributes="EditorOrder(100), EditorDisplay(\"Joint\"), DefaultValue(HingeJointFlag.Limit | HingeJointFlag.Drive)")
FORCE_INLINE HingeJointFlag GetFlags() const
{
return _flags;

View File

@@ -186,8 +186,8 @@ API_ENUM() enum class ArchitectureType
#ifndef PLATFORM_ARCH_ARM64
#define PLATFORM_ARCH_ARM64 0
#endif
#ifndef PLATFORM_WCHAR_IS_CHAR16
#define PLATFORM_WCHAR_IS_CHAR16 0
#ifndef PLATFORM_TEXT_IS_CHAR16
#define PLATFORM_TEXT_IS_CHAR16 0
#endif
#ifndef PLATFORM_DEBUG_BREAK
#define PLATFORM_DEBUG_BREAK

View File

@@ -196,12 +196,12 @@ public:
static int32 HexDigit(Char c);
// Parses text to unsigned integer value. Returns true if failed to convert the value.
template<typename CharType>
static bool ParseHex(const CharType* str, int32 length, uint32* result)
template<typename T>
static bool ParseHex(const T* str, int32 length, uint32* result)
{
uint32 sum = 0;
const CharType* p = str;
const CharType* end = str + length;
const T* p = str;
const T* end = str + length;
if (*p == '0' && *(p + 1) == 'x')
p += 2;
while (*p && p < end)
@@ -221,10 +221,10 @@ public:
}
// Parses text to unsigned integer value. Returns true if failed to convert the value.
template<typename CharType>
static bool ParseHex(const CharType* str, uint32* result)
template<typename T>
static bool ParseHex(const T* str, uint32* result)
{
return ParseHex(str, Length(str), result);
return StringUtils::ParseHex(str, StringUtils::Length(str), result);
}
// Parses text to the unsigned integer value. Returns true if failed to convert the value.
@@ -247,7 +247,7 @@ public:
static bool Parse(const T* str, uint32 length, uint32* result)
{
uint64 tmp;
const bool b = Parse(str, length, &tmp);
const bool b = StringUtils::Parse(str, length, &tmp);
*result = (uint32)tmp;
return b;
}
@@ -255,7 +255,7 @@ public:
static bool Parse(const T* str, uint32 length, uint16* result)
{
uint64 tmp;
const bool b = Parse(str, length, &tmp);
const bool b = StringUtils::Parse(str, length, &tmp);
*result = (uint16)tmp;
return b;
}
@@ -263,7 +263,7 @@ public:
static bool Parse(const T* str, uint32 length, uint8* result)
{
uint64 tmp;
const bool b = Parse(str, length, &tmp);
const bool b = StringUtils::Parse(str, length, &tmp);
*result = (uint8)tmp;
return b;
}
@@ -296,7 +296,7 @@ public:
static bool Parse(const T* str, uint32 length, int32* result)
{
int64 tmp;
const bool b = Parse(str, length, &tmp);
const bool b = StringUtils::Parse(str, length, &tmp);
*result = (int32)tmp;
return b;
}
@@ -304,7 +304,7 @@ public:
static bool Parse(const T* str, uint32 length, int16* result)
{
int64 tmp;
const bool b = Parse(str, length, &tmp);
const bool b = StringUtils::Parse(str, length, &tmp);
*result = (int16)tmp;
return b;
}
@@ -312,7 +312,7 @@ public:
static bool Parse(const T* str, uint32 length, int8* result)
{
int64 tmp;
const bool b = Parse(str, length, &tmp);
const bool b = StringUtils::Parse(str, length, &tmp);
*result = (int8)tmp;
return b;
}
@@ -321,7 +321,7 @@ public:
template<typename T, typename U>
static bool Parse(const T* str, U* result)
{
return Parse(str, Length(str), result);
return StringUtils::Parse(str, StringUtils::Length(str), result);
}
// Parses text to the scalar value. Returns true if failed to convert the value.

View File

@@ -330,6 +330,7 @@ public:
for (auto it = Lights.Begin(); it.IsNotEnd(); ++it)
{
auto& atlasLight = it->Value;
atlasLight.StaticState = ShadowAtlasLight::Unused;
atlasLight.Cache.StaticValid = false;
for (int32 i = 0; i < atlasLight.TilesCount; i++)
atlasLight.Tiles[i].ClearDynamic();
@@ -370,7 +371,8 @@ public:
for (auto& e : Lights)
{
auto& atlasLight = e.Value;
if (atlasLight.StaticState == ShadowAtlasLight::CopyStaticShadow && atlasLight.Bounds.Intersects(bounds))
if ((atlasLight.StaticState == ShadowAtlasLight::CopyStaticShadow || atlasLight.StaticState == ShadowAtlasLight::NoStaticGeometry)
&& atlasLight.Bounds.Intersects(bounds))
{
// Invalidate static shadow
atlasLight.Cache.StaticValid = false;
@@ -719,8 +721,14 @@ bool ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
}
switch (atlasLight.StaticState)
{
case ShadowAtlasLight::CopyStaticShadow:
case ShadowAtlasLight::NoStaticGeometry:
// Light was modified so attempt to find the static shadow again
if (!atlasLight.Cache.StaticValid && atlasLight.HasStaticShadowContext)
{
atlasLight.StaticState = ShadowAtlasLight::WaitForGeometryCheck;
break;
}
case ShadowAtlasLight::CopyStaticShadow:
case ShadowAtlasLight::FailedToInsertTiles:
// Skip collecting static draws
atlasLight.HasStaticShadowContext = false;
@@ -728,7 +736,7 @@ bool ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
}
if (atlasLight.HasStaticShadowContext)
{
// If rendering finds any static draws then it's set to true
// If rendering finds any static draws then it will be set to true
for (auto& tile : atlasLight.Tiles)
tile.HasStaticGeometry = false;
}
@@ -1367,7 +1375,7 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch)
const ShadowsCustomBuffer* shadowsPtr = renderContext.Buffers->FindCustomBuffer<ShadowsCustomBuffer>(TEXT("Shadows"), false);
if (shadowsPtr == nullptr || shadowsPtr->Lights.IsEmpty() || shadowsPtr->LastFrameUsed != Engine::FrameCount)
return;
PROFILE_GPU_CPU("ShadowMaps");
PROFILE_GPU_CPU("Shadow Maps");
const ShadowsCustomBuffer& shadows = *shadowsPtr;
GPUContext* context = GPUDevice::Instance->GetMainContext();
context->ResetSR();
@@ -1384,9 +1392,29 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch)
for (auto& e : shadows.Lights)
{
ShadowAtlasLight& atlasLight = e.Value;
if (atlasLight.StaticState != ShadowAtlasLight::UpdateStaticShadow || !atlasLight.HasStaticShadowContext || atlasLight.ContextCount == 0)
if (!atlasLight.HasStaticShadowContext || atlasLight.ContextCount == 0)
continue;
int32 contextIndex = 0;
if (atlasLight.StaticState == ShadowAtlasLight::WaitForGeometryCheck)
{
// Check for any static geometry to use in static shadow map
for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++)
{
ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex];
contextIndex++; // Skip dynamic context
auto& shadowContextStatic = renderContextBatch.Contexts[atlasLight.ContextIndex + contextIndex++];
if (!shadowContextStatic.List->DrawCallsLists[(int32)DrawCallsListType::Depth].IsEmpty() || !shadowContextStatic.List->ShadowDepthDrawCallsList.IsEmpty())
{
tile.HasStaticGeometry = true;
}
}
}
if (atlasLight.StaticState != ShadowAtlasLight::UpdateStaticShadow)
continue;
contextIndex = 0;
for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++)
{
ShadowAtlasLightTile& tile = atlasLight.Tiles[tileIndex];

View File

@@ -661,7 +661,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
if (sourceWidth != width || sourceHeight != height)
{
// During resizing we need to keep texture aspect ratio
const bool keepAspectRatio = false; // TODO: expose as import option
const bool keepAspectRatio = options.KeepAspectRatio;
if (keepAspectRatio)
{
const float aspectRatio = static_cast<float>(sourceWidth) / sourceHeight;
@@ -727,8 +727,10 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
}
bool keepAsIs = false;
if (!options.FlipY &&
!options.InvertGreenChannel &&
if (!options.FlipY &&
!options.FlipX &&
!options.InvertGreenChannel &&
!options.ReconstructZChannel &&
options.Compress &&
type == ImageType::DDS &&
mipLevels == sourceMipLevels &&
@@ -787,7 +789,7 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
SET_CURRENT_IMG(tmpImg);
}
// Check flip/rotate source image
// Check flip/rotate Y source image
if (!keepAsIs && options.FlipY)
{
auto& tmpImg = GET_TMP_IMG();
@@ -801,6 +803,20 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
SET_CURRENT_IMG(tmpImg);
}
// Check flip/rotate X source image
if (!keepAsIs && options.FlipX)
{
auto& tmpImg = GET_TMP_IMG();
DirectX::TEX_FR_FLAGS flags = DirectX::TEX_FR_FLIP_HORIZONTAL;
result = FlipRotate(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(), flags, tmpImg);
if (FAILED(result))
{
errorMsg = String::Format(TEXT("Cannot rotate/flip texture, error: {0:x}"), static_cast<uint32>(result));
return true;
}
SET_CURRENT_IMG(tmpImg);
}
// Check if invert green channel
if (!keepAsIs && options.InvertGreenChannel)
{
@@ -829,6 +845,43 @@ bool TextureTool::ImportTextureDirectXTex(ImageType type, const StringView& path
SET_CURRENT_IMG(timage);
}
// Reconstruct Z Channel
if (!keepAsIs & options.ReconstructZChannel)
{
auto& timage = GET_TMP_IMG();
bool isunorm = (DirectX::FormatDataType(sourceDxgiFormat) == DirectX::FORMAT_TYPE_UNORM) != 0;
result = TransformImage(currentImage->GetImages(), currentImage->GetImageCount(), currentImage->GetMetadata(),
[&](DirectX::XMVECTOR* outPixels, const DirectX::XMVECTOR* inPixels, size_t w, size_t y)
{
static const DirectX::XMVECTORU32 s_selectz = { { { DirectX::XM_SELECT_0, DirectX::XM_SELECT_0, DirectX::XM_SELECT_1, DirectX::XM_SELECT_0 } } };
UNREFERENCED_PARAMETER(y);
for (size_t j = 0; j < w; ++j)
{
const DirectX::XMVECTOR value = inPixels[j];
DirectX::XMVECTOR z;
if (isunorm)
{
DirectX::XMVECTOR x2 = DirectX::XMVectorMultiplyAdd(value, DirectX::g_XMTwo, DirectX::g_XMNegativeOne);
x2 = DirectX::XMVectorSqrt(DirectX::XMVectorSubtract(DirectX::g_XMOne, DirectX::XMVector2Dot(x2, x2)));
z = DirectX::XMVectorMultiplyAdd(x2, DirectX::g_XMOneHalf, DirectX::g_XMOneHalf);
}
else
{
z = DirectX::XMVectorSqrt(DirectX::XMVectorSubtract(DirectX::g_XMOne, DirectX::XMVector2Dot(value, value)));
}
outPixels[j] = DirectX::XMVectorSelect(value, z, s_selectz);
}
}, timage);
if (FAILED(result))
{
errorMsg = String::Format(TEXT("Cannot reconstruct z channel in texture, error: {0:x}"), static_cast<uint32>(result));
return true;
}
SET_CURRENT_IMG(timage);
}
// Generate mip maps chain
if (!keepAsIs && useMipLevels && options.GenerateMipMaps)
{

View File

@@ -73,12 +73,21 @@ void TextureTool::Options::Serialize(SerializeStream& stream, const void* otherO
stream.JKEY("FlipY");
stream.Bool(FlipY);
stream.JKEY("FlipX");
stream.Bool(FlipX);
stream.JKEY("InvertGreenChannel");
stream.Bool(InvertGreenChannel);
stream.JKEY("ReconstructZChannel");
stream.Bool(ReconstructZChannel);
stream.JKEY("Resize");
stream.Bool(Resize);
stream.JKEY("KeepAspectRatio");
stream.Bool(KeepAspectRatio);
stream.JKEY("PreserveAlphaCoverage");
stream.Bool(PreserveAlphaCoverage);
@@ -133,8 +142,11 @@ void TextureTool::Options::Deserialize(DeserializeStream& stream, ISerializeModi
sRGB = JsonTools::GetBool(stream, "sRGB", sRGB);
GenerateMipMaps = JsonTools::GetBool(stream, "GenerateMipMaps", GenerateMipMaps);
FlipY = JsonTools::GetBool(stream, "FlipY", FlipY);
FlipX = JsonTools::GetBool(stream, "FlipX", FlipX);
InvertGreenChannel = JsonTools::GetBool(stream, "InvertGreenChannel", InvertGreenChannel);
ReconstructZChannel = JsonTools::GetBool(stream, "ReconstructZChannel", ReconstructZChannel);
Resize = JsonTools::GetBool(stream, "Resize", Resize);
KeepAspectRatio = JsonTools::GetBool(stream, "KeepAspectRatio", KeepAspectRatio);
PreserveAlphaCoverage = JsonTools::GetBool(stream, "PreserveAlphaCoverage", PreserveAlphaCoverage);
PreserveAlphaCoverageReference = JsonTools::GetFloat(stream, "PreserveAlphaCoverageReference", PreserveAlphaCoverageReference);
TextureGroup = JsonTools::GetInt(stream, "TextureGroup", TextureGroup);

View File

@@ -53,14 +53,22 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(60)")
bool GenerateMipMaps = true;
// True if flip Y coordinate of the texture.
// True if flip Y coordinate of the texture (Flips over X axis).
API_FIELD(Attributes="EditorOrder(70)")
bool FlipY = false;
// True if flip X coordinate of the texture (Flips over Y axis).
API_FIELD(Attributes="EditorOrder(71)")
bool FlipX = false;
// True if to invert the green channel on a normal map. Good for OpenGL to DirectX conversion.
API_FIELD(Attributes = "EditorOrder(71)")
API_FIELD(Attributes = "EditorOrder(72)")
bool InvertGreenChannel = false;
// Rebuild Z (blue) channel assuming X/Y are normals.
API_FIELD(Attributes = "EditorOrder(73)")
bool ReconstructZChannel = false;
// Texture size scale. Allows increasing or decreasing the imported texture resolution. Default is 1.
API_FIELD(Attributes="EditorOrder(80), Limit(0.0001f, 1000.0f, 0.01f)")
float Scale = 1.0f;
@@ -73,6 +81,10 @@ API_CLASS(Namespace="FlaxEngine.Tools", Static) class FLAXENGINE_API TextureTool
API_FIELD(Attributes="EditorOrder(100)")
bool Resize = false;
// Keeps the aspect ratio when resizing.
API_FIELD(Attributes="EditorOrder(101), VisibleIf(nameof(Resize))")
bool KeepAspectRatio = false;
// The width of the imported texture. If Resize property is set to true then texture will be resized during the import to this value during the import, otherwise it will be ignored.
API_FIELD(Attributes="HideInEditor")
int32 SizeX = 1024;

View File

@@ -458,7 +458,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
{
if (!options.InternalLoad.IsBinded() || options.InternalLoad(textureData))
return true;
if (options.FlipY)
if (options.FlipY || options.FlipX)
{
// TODO: impl this
errorMsg = TEXT("Flipping images imported from Internal source is not supported by stb.");
@@ -489,7 +489,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
if (sourceWidth != width || sourceHeight != height)
{
// During resizing we need to keep texture aspect ratio
const bool keepAspectRatio = false; // TODO: expose as import option
const bool keepAspectRatio = options.KeepAspectRatio; // TODO: expose as import option
if (keepAspectRatio)
{
const float aspectRatio = static_cast<float>(sourceWidth) / sourceHeight;
@@ -536,6 +536,22 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
return true;
}
if (options.FlipX)
{
// TODO: impl this
LOG(Warning, "Option 'Flip X' is not supported");
}
if (options.InvertGreenChannel)
{
// TODO: impl this
LOG(Warning, "Option 'Invert Green Channel' is not supported");
}
if (options.ReconstructZChannel)
{
// TODO: impl this
LOG(Warning, "Option 'Reconstruct Z Channel' is not supported");
}
// Generate mip maps chain
if (useMipLevels && options.GenerateMipMaps)
{

View File

@@ -60,6 +60,51 @@ namespace FlaxEngine.GUI
return false;
}
/// <summary>
/// Gets the text block or the nearest text block of the character at the given index.
/// </summary>
/// <param name="index">The character index.</param>
/// <param name="result">The result text block descriptor.</param>
/// <param name="snapToNext">If true, the when the index is between two text blocks, it will return the next block.</param>
/// <returns>True if a text block is found, otherwise false.</returns>
public bool GetNearestTextBlock(int index, out TextBlock result, bool snapToNext = false)
{
var textBlocksSpan = CollectionsMarshal.AsSpan(_textBlocks);
int blockCount = _textBlocks.Count;
for (int i = 0; i < blockCount; i++)
{
ref TextBlock currentBlock = ref textBlocksSpan[i];
if (currentBlock.Range.Contains(index))
{
result = currentBlock;
return true;
}
if (i < blockCount - 1)
{
ref TextBlock nextBlock = ref textBlocksSpan[i + 1];
if (index >= currentBlock.Range.EndIndex && index < nextBlock.Range.StartIndex)
{
result = snapToNext ? nextBlock : currentBlock;
return true;
}
}
}
// Handle case when index is outside all text ranges
if (index >= 0 && blockCount > 0)
{
result = (index <= textBlocksSpan[0].Range.StartIndex) ? textBlocksSpan[0] : textBlocksSpan[blockCount - 1];
return true;
}
// If no text block is found
result = new TextBlock();
return false;
}
/// <summary>
/// Updates the text blocks.
/// </summary>
@@ -221,6 +266,35 @@ namespace FlaxEngine.GUI
return base.OnMouseDoubleClick(location, button);
}
/// <inheritdoc />
protected override void SetSelection(int start, int end, bool withScroll = true)
{
int snappedStart = start;
int snappedEnd = end;
if (start != -1 && end != -1)
{
bool movingBack = (_selectionStart != -1 && _selectionEnd != -1) && (end < _selectionEnd || start < _selectionStart);
GetNearestTextBlock(start, out TextBlock startTextBlock, !movingBack);
GetNearestTextBlock(end, out TextBlock endTextBlock, !movingBack);
snappedStart = startTextBlock.Range.Contains(start) ? start : (movingBack ? startTextBlock.Range.EndIndex - 1 : startTextBlock.Range.StartIndex);
snappedEnd = endTextBlock.Range.Contains(end) ? end : (movingBack ? endTextBlock.Range.EndIndex - 1 : endTextBlock.Range.StartIndex);
snappedStart = movingBack ? Math.Min(start, snappedStart) : Math.Max(start, snappedStart);
snappedEnd = movingBack ? Math.Min(end, snappedEnd) : Math.Max(end, snappedEnd);
// Don't snap if selection is right in the end of the text
if (start == _text.Length)
snappedStart = _text.Length;
if (end == _text.Length)
snappedEnd = _text.Length;
}
base.SetSelection(snappedStart, snappedEnd, withScroll);
}
/// <inheritdoc />
public override void DrawSelf()
{
@@ -289,8 +363,8 @@ namespace FlaxEngine.GUI
// Selection
if (hasSelection && textBlock.Style.BackgroundSelectedBrush != null && textBlock.Range.Intersect(ref selection))
{
var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex);
var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex);
var leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : GetCharPosition(selection.StartIndex, out _);
var rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : GetCharPosition(selection.EndIndex, out _);
float height = font.Height;
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
alpha *= alpha;

View File

@@ -397,6 +397,7 @@ namespace MF
// Loop
playerMF.Time.Ticks %= player.Duration.Ticks;
playerMF.Seek = 1;
player.PlayAudio();
}
else
{