Merge remote-tracking branch 'origin/1.1' into 1.2

# Conflicts:
#	Source/Editor/Editor.Build.cs
This commit is contained in:
Wojtek Figat
2021-03-15 09:38:58 +01:00
86 changed files with 4242 additions and 317 deletions

View File

@@ -100,7 +100,12 @@ bool GameSettings::Load()
auto settings = Get();
if (!settings)
{
#if USE_EDITOR
// Allow lack of Game Settings in Editor
return false;
#else
return true;
#endif
}
// Preload all settings assets

View File

@@ -156,9 +156,9 @@ void Log::Logger::Dispose()
WriteFloor();
// Close
LogAfterInit = false;
if (LogAfterInit)
{
LogAfterInit = false;
LogFile->Close();
Delete(LogFile);
LogFile = nullptr;

View File

@@ -99,8 +99,6 @@ void GPUSwapChainDX11::SetFullscreen(bool isFullscreen)
swapChainDesc.BufferDesc = outputDX.DesktopViewMode;
}
releaseBackBuffer();
if (FAILED(_swapChain->ResizeTarget(&swapChainDesc.BufferDesc)))
{
LOG(Warning, "Swapchain resize failed.");
@@ -110,10 +108,6 @@ void GPUSwapChainDX11::SetFullscreen(bool isFullscreen)
{
LOG(Warning, "Cannot change fullscreen mode for '{0}' to {1}.", ToString(), isFullscreen);
}
VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, _width, _height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags));
getBackBuffer();
}
#else
LOG(Info, "Cannot change fullscreen mode on this platform");
@@ -208,7 +202,7 @@ bool GPUSwapChainDX11::Resize(int32 width, int32 height)
ASSERT(_swapChain);
// Disable DXGI changes to the window
VALIDATE_DIRECTX_RESULT(dxgi->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER));
VALIDATE_DIRECTX_RESULT(dxgi->MakeWindowAssociation(_windowHandle, 0));
#else
auto dxgiFactory = (IDXGIFactory2*)_device->GetDXGIFactory();
VALIDATE_DIRECTX_RESULT(dxgiFactory->CreateSwapChainForCoreWindow(_device->GetDevice(), static_cast<IUnknown*>(_windowHandle), &swapChainDesc, nullptr, &_swapChain));

View File

@@ -124,8 +124,6 @@ void GPUSwapChainDX12::SetFullscreen(bool isFullscreen)
swapChainDesc.BufferDesc = outputDX.DesktopViewMode;
}
releaseBackBuffer();
if (FAILED(_swapChain->ResizeTarget(&swapChainDesc.BufferDesc)))
{
LOG(Warning, "Swapchain resize failed.");
@@ -136,10 +134,6 @@ void GPUSwapChainDX12::SetFullscreen(bool isFullscreen)
LOG(Warning, "Cannot change fullscreen mode for '{0}' to {1}.", ToString(), isFullscreen);
}
VALIDATE_DIRECTX_RESULT(_swapChain->ResizeBuffers(swapChainDesc.BufferCount, _width, _height, swapChainDesc.BufferDesc.Format, swapChainDesc.Flags));
getBackBuffer();
_isFullscreen = isFullscreen;
}
#else
@@ -223,7 +217,7 @@ bool GPUSwapChainDX12::Resize(int32 width, int32 height)
_backBuffers.Resize(swapChainDesc.BufferCount);
// Disable DXGI changes to the window
dxgiFactory->MakeWindowAssociation(_windowHandle, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER);
dxgiFactory->MakeWindowAssociation(_windowHandle, 0);
}
else
{

View File

@@ -6,10 +6,14 @@
#include "Engine/Graphics/RenderTask.h"
#include <Engine/Main/Android/android_native_app_glue.h>
#define DefaultDPI 96
AndroidWindow::AndroidWindow(const CreateWindowSettings& settings)
: WindowBase(settings)
{
_clientSize = settings.Size;
_dpi = DefaultDPI;
_dpiScale = (float)_dpi / (float)DefaultDPI;
}
AndroidWindow::~AndroidWindow()

View File

@@ -569,17 +569,17 @@ public:
API_PROPERTY() static BatteryInfo GetBatteryInfo();
/// <summary>
/// Gets the screen DPI setting.
/// Gets the primary monitor's DPI setting.
/// </summary>
API_PROPERTY() static int32 GetDpi();
/// <summary>
/// Gets the screen DPI setting scale factor (1 is default). Includes custom DPI scale.
/// Gets the primary monitor's DPI setting scale factor (1 is default). Includes custom DPI scale.
/// </summary>
API_PROPERTY() static float GetDpiScale();
/// <summary>
/// The custom screen DPI scale factor to apply globally. Can be used to adjust the User Interface scale (resolution).
/// The custom DPI scale factor to apply globally. Can be used to adjust the User Interface scale (resolution).
/// </summary>
API_FIELD() static float CustomDpiScale;

View File

@@ -281,6 +281,8 @@ protected:
String _title;
CursorType _cursor;
Vector2 _clientSize;
int _dpi;
float _dpiScale;
Vector2 _trackingMouseOffset;
bool _isUsingMouseOffset;
@@ -542,6 +544,21 @@ public:
/// <returns>The screen space position.</returns>
API_FUNCTION() virtual Vector2 ClientToScreen(const Vector2& clientPos) const = 0;
/// <summary>
/// Gets the window DPI setting.
/// </summary>
API_PROPERTY() int GetDpi() const
{
return _dpi;
}
/// <summary>
/// Gets the window DPI scale factor (1 is default). Includes custom DPI scale
/// </summary>
API_PROPERTY() float GetDpiScale() const
{
return Platform::CustomDpiScale * _dpiScale;
}
public:
/// <summary>

View File

@@ -31,6 +31,7 @@
#define _NET_WM_STATE_REMOVE 0L // remove/unset property
#define _NET_WM_STATE_ADD 1L // add/set property
#define _NET_WM_STATE_TOGGLE 2L // toggle property
#define DefaultDPI 96
// Window routines function prolog
#define LINUX_WINDOW_PROLOG X11::Display* display = (X11::Display*)LinuxPlatform::GetXDisplay(); X11::Window window = (X11::Window)_window
@@ -150,7 +151,10 @@ LinuxWindow::LinuxWindow(const CreateWindowSettings& settings)
X11::XSetTransientForHint(display, window, (X11::Window)((LinuxWindow*)settings.Parent)->GetNativePtr());
}
// Set events mask
_dpi = Platform::GetDpi();
_dpiScale = (float)_dpi / (float)DefaultDPI;
// Set input mask
long eventMask =
ExposureMask | FocusChangeMask |
KeyPressMask | KeyReleaseMask |

View File

@@ -100,7 +100,6 @@ private:
UWPWindowImpl* _impl;
float _dpi, _dpiScale;
Vector2 _logicalSize;
public:

View File

@@ -7,8 +7,6 @@ namespace FlaxEngine
{
partial class Window
{
internal float _dpiScale;
/// <summary>
/// Window closing delegate.
/// </summary>
@@ -176,7 +174,6 @@ namespace FlaxEngine
private Window()
{
GUI = new WindowRootControl(this);
_dpiScale = Platform.DpiScale;
}
internal void Internal_OnShow()
@@ -192,7 +189,7 @@ namespace FlaxEngine
internal void Internal_OnDraw()
{
Matrix3x3.Scaling(_dpiScale, out var scale);
Matrix3x3.Scaling(DpiScale, out var scale);
Render2D.PushTransform(ref scale);
GUI.Draw();
Render2D.PopTransform();
@@ -200,7 +197,7 @@ namespace FlaxEngine
internal void Internal_OnResize(int width, int height)
{
GUI.Size = new Vector2(width / _dpiScale, height / _dpiScale);
GUI.Size = new Vector2(width / DpiScale, height / DpiScale);
}
internal void Internal_OnCharInput(char c)
@@ -223,7 +220,7 @@ namespace FlaxEngine
internal void Internal_OnMouseDown(ref Vector2 mousePos, MouseButton button)
{
Vector2 pos = mousePos / _dpiScale;
Vector2 pos = mousePos / DpiScale;
bool handled = false;
MouseDown?.Invoke(ref pos, button, ref handled);
@@ -235,7 +232,7 @@ namespace FlaxEngine
internal void Internal_OnMouseUp(ref Vector2 mousePos, MouseButton button)
{
Vector2 pos = mousePos / _dpiScale;
Vector2 pos = mousePos / DpiScale;
bool handled = false;
MouseUp?.Invoke(ref pos, button, ref handled);
@@ -247,7 +244,7 @@ namespace FlaxEngine
internal void Internal_OnMouseDoubleClick(ref Vector2 mousePos, MouseButton button)
{
Vector2 pos = mousePos / _dpiScale;
Vector2 pos = mousePos / DpiScale;
bool handled = false;
MouseDoubleClick?.Invoke(ref pos, button, ref handled);
@@ -259,7 +256,7 @@ namespace FlaxEngine
internal void Internal_OnMouseWheel(ref Vector2 mousePos, float delta)
{
Vector2 pos = mousePos / _dpiScale;
Vector2 pos = mousePos / DpiScale;
bool handled = false;
MouseWheel?.Invoke(ref pos, delta, ref handled);
@@ -271,7 +268,7 @@ namespace FlaxEngine
internal void Internal_OnMouseMove(ref Vector2 mousePos)
{
Vector2 pos = mousePos / _dpiScale;
Vector2 pos = mousePos / DpiScale;
MouseMove?.Invoke(ref pos);
GUI.OnMouseMove(pos);
@@ -285,7 +282,7 @@ namespace FlaxEngine
internal void Internal_OnTouchDown(ref Vector2 pointerPosition, int pointerId)
{
Vector2 pos = pointerPosition / _dpiScale;
Vector2 pos = pointerPosition / DpiScale;
bool handled = false;
TouchDown?.Invoke(ref pos, pointerId, ref handled);
@@ -297,7 +294,7 @@ namespace FlaxEngine
internal void Internal_OnTouchMove(ref Vector2 pointerPosition, int pointerId)
{
Vector2 pos = pointerPosition / _dpiScale;
Vector2 pos = pointerPosition / DpiScale;
bool handled = false;
TouchMove?.Invoke(ref pos, pointerId, ref handled);
@@ -309,7 +306,7 @@ namespace FlaxEngine
internal void Internal_OnTouchUp(ref Vector2 pointerPosition, int pointerId)
{
Vector2 pos = pointerPosition / _dpiScale;
Vector2 pos = pointerPosition / DpiScale;
bool handled = false;
TouchUp?.Invoke(ref pos, pointerId, ref handled);
@@ -335,7 +332,7 @@ namespace FlaxEngine
{
if (HitTest != null)
{
Vector2 pos = mousePos / _dpiScale;
Vector2 pos = mousePos / DpiScale;
result = HitTest(ref pos);
handled = true;
}
@@ -356,7 +353,7 @@ namespace FlaxEngine
dragData = new DragDataText(data[0]);
else
dragData = new DragDataFiles(data);
Vector2 pos = mousePos / _dpiScale;
Vector2 pos = mousePos / DpiScale;
return GUI.OnDragEnter(ref pos, dragData);
}
@@ -367,7 +364,7 @@ namespace FlaxEngine
dragData = new DragDataText(data[0]);
else
dragData = new DragDataFiles(data);
Vector2 pos = mousePos / _dpiScale;
Vector2 pos = mousePos / DpiScale;
return GUI.OnDragMove(ref pos, dragData);
}
@@ -378,7 +375,7 @@ namespace FlaxEngine
dragData = new DragDataText(data[0]);
else
dragData = new DragDataFiles(data);
Vector2 pos = mousePos / _dpiScale;
Vector2 pos = mousePos / DpiScale;
return GUI.OnDragDrop(ref pos, dragData);
}

View File

@@ -182,6 +182,7 @@ WindowsFileSystemWatcher::~WindowsFileSystemWatcher()
FileSystemWatchers::ThreadActive = false;
QueueUserAPC(FileSystemWatchers::StopProc, FileSystemWatchers::Thread->GetHandle(), 0);
FileSystemWatchers::Thread->Join();
Delete(FileSystemWatchers::Thread);
FileSystemWatchers::Thread = nullptr;
}
FileSystemWatchers::Locker.Unlock();

View File

@@ -673,7 +673,7 @@ void WindowsPlatform::SetHighDpiAwarenessEnabled(bool enable)
if (setProcessDpiAwareness)
{
setProcessDpiAwareness(enable ? PROCESS_SYSTEM_DPI_AWARE : PROCESS_DPI_UNAWARE);
setProcessDpiAwareness(enable ? PROCESS_PER_MONITOR_DPI_AWARE : PROCESS_DPI_UNAWARE);
}
SystemDpi = CalculateDpi(shCoreDll);

View File

@@ -13,6 +13,8 @@
#include "../Win32/IncludeWindowsHeaders.h"
#include <propidl.h>
#define DefaultDPI 96
// Use improved borderless window support for Editor
#define WINDOWS_USE_NEW_BORDER_LESS USE_EDITOR && 0
#if WINDOWS_USE_NEW_BORDER_LESS
@@ -123,6 +125,21 @@ WindowsWindow::WindowsWindow(const CreateWindowSettings& settings)
(HINSTANCE)Platform::Instance,
nullptr);
// Query DPI
_dpi = Platform::GetDpi();
const HMODULE user32Dll = LoadLibraryW(L"user32.dll");
if (user32Dll)
{
typedef UINT (STDAPICALLTYPE* GetDpiForWindowProc)(HWND hwnd);
const GetDpiForWindowProc getDpiForWindowProc = (GetDpiForWindowProc)GetProcAddress(user32Dll, "GetDpiForWindow");
if (getDpiForWindowProc)
{
_dpi = getDpiForWindowProc(_handle);
}
FreeLibrary(user32Dll);
}
_dpiScale = (float)_dpi / (float)DefaultDPI;
// Validate result
if (!HasHWND())
{
@@ -1021,6 +1038,22 @@ LRESULT WindowsWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam)
}
break;
}
case WM_DPICHANGED:
{
// Maybe https://stackoverflow.com/a/45110656
_dpi = HIWORD(wParam);
_dpiScale = (float)_dpi / (float)DefaultDPI;
RECT* windowRect = (RECT*)lParam;
SetWindowPos(_handle,
nullptr,
windowRect->left,
windowRect->top,
windowRect->right - windowRect->left,
windowRect->bottom - windowRect->top,
SWP_NOZORDER | SWP_NOACTIVATE);
// TODO: Recalculate fonts
return 0;
}
case WM_ENTERSIZEMOVE:
{
_isResizing = true;

View File

@@ -67,7 +67,7 @@ bool FontManagerService::Init()
ASSERT(Library == nullptr);
// Scale UI fonts to match the monitor DPI
FontManager::FontScale = (float)Platform::GetDpi() / (float)DefaultDPI;
FontManager::FontScale = (float)Platform::GetDpi() / (float)DefaultDPI; // TODO: Adjust this at runtime
// Init Free Type
FreeTypeMemory.user = nullptr;
@@ -109,7 +109,7 @@ void FontManagerService::Dispose()
FontTextureAtlas* FontManager::GetAtlas(int32 index)
{
return Atlases[index];
return index >= 0 && index < Atlases.Count() ? Atlases.Get()[index] : nullptr;
}
bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry)
@@ -206,6 +206,7 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry)
// End for empty glyphs
if (GlyphImageData.IsEmpty())
{
entry.TextureIndex = MAX_uint8;
if (bitmap == &tmpBitmap)
{
FT_Bitmap_Done(Library, bitmap);

View File

@@ -1137,11 +1137,17 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
// Get texture atlas that contains current character
fontAtlasIndex = entry.TextureIndex;
fontAtlas = FontManager::GetAtlas(fontAtlasIndex);
fontAtlas->EnsureTextureCreated();
drawCall.AsChar.Tex = fontAtlas->GetTexture();
// Cache atlas inverted size (inverted to improve performance)
invAtlasSize = 1.0f / fontAtlas->GetSize();
if (fontAtlas)
{
fontAtlas->EnsureTextureCreated();
drawCall.AsChar.Tex = fontAtlas->GetTexture();
invAtlasSize = 1.0f / fontAtlas->GetSize();
}
else
{
drawCall.AsChar.Tex = nullptr;
invAtlasSize = 1.0f;
}
}
// Check if character is a whitespace
@@ -1250,9 +1256,17 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color,
// Get texture atlas that contains current character
fontAtlasIndex = entry.TextureIndex;
fontAtlas = FontManager::GetAtlas(fontAtlasIndex);
fontAtlas->EnsureTextureCreated();
invAtlasSize = 1.0f / fontAtlas->GetSize();
drawCall.AsChar.Tex = fontAtlas->GetTexture();
if (fontAtlas)
{
fontAtlas->EnsureTextureCreated();
invAtlasSize = 1.0f / fontAtlas->GetSize();
drawCall.AsChar.Tex = fontAtlas->GetTexture();
}
else
{
invAtlasSize = 1.0f;
drawCall.AsChar.Tex = nullptr;
}
}
// Get kerning

View File

@@ -123,7 +123,6 @@ void MAssembly::Unload(bool isReloading)
LOG(Info, "Unloading managed assembly \'{0}\' (is reloading)", String(_name));
mono_assembly_close(_monoAssembly);
mono_image_close(_monoImage);
}
else
{
@@ -306,11 +305,9 @@ bool MAssembly::LoadWithImage(const String& assemblyPath)
// Setup assembly
const auto assembly = mono_assembly_load_from_full(assemblyImage, name.Substring(0, name.Length() - 3).Get(), &status, false);
mono_image_close(assemblyImage);
if (status != MONO_IMAGE_OK || assembly == nullptr)
{
// Close image if error occurred
mono_image_close(assemblyImage);
Log::CLRInnerException(TEXT("Mono assembly image is corrupted at ") + assemblyPath);
return true;
}

View File

@@ -508,8 +508,9 @@ bool MCore::LoadEngine()
Thread::ThreadExiting.Bind<OnThreadExiting>();
// Info
const String buildInfo(mono_get_runtime_build_info());
LOG(Info, "Mono version: {0}", buildInfo);
char* buildInfo = mono_get_runtime_build_info();
LOG(Info, "Mono version: {0}", String(buildInfo));
mono_free(buildInfo);
return false;
}

View File

@@ -518,7 +518,7 @@ void Scripting::Release()
module->Destroy(false);
}
_nonNativeModules.Clear();
_nonNativeModules.ClearDelete();
_hasGameModulesLoaded = false;
}
@@ -619,7 +619,7 @@ void Scripting::Reload(bool canTriggerSceneReload)
module->Destroy(true);
}
modules.Clear();
_nonNativeModules.Clear();
_nonNativeModules.ClearDelete();
_hasGameModulesLoaded = false;
// Give GC a try to cleanup old user objects and the other mess

View File

@@ -165,17 +165,19 @@ namespace FlaxEngine.Json
public StringWriter StringWriter;
public JsonTextWriter JsonWriter;
public JsonSerializerInternalWriter SerializerWriter;
public UnmanagedStringReader StringReader;
public UnmanagedMemoryStream MemoryStream;
public StreamReader Reader;
public bool IsDuringSerialization;
public SerializerCache(JsonSerializerSettings settings)
public unsafe SerializerCache(JsonSerializerSettings settings)
{
JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings);
JsonSerializer.Formatting = Formatting.Indented;
StringBuilder = new StringBuilder(256);
StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture);
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
StringReader = new UnmanagedStringReader();
MemoryStream = new UnmanagedMemoryStream((byte*)0, 0);
Reader = new StreamReader(MemoryStream, Encoding.UTF8, false);
JsonWriter = new JsonTextWriter(StringWriter)
{
IndentChar = '\t',
@@ -404,14 +406,15 @@ namespace FlaxEngine.Json
/// <param name="input">The object.</param>
/// <param name="jsonBuffer">The input json data buffer (raw, fixed memory buffer).</param>
/// <param name="jsonLength">The input json data buffer length (characters count).</param>
public static unsafe void Deserialize(object input, void* jsonBuffer, int jsonLength)
public static unsafe void Deserialize(object input, byte* jsonBuffer, int jsonLength)
{
var cache = Cache.Value;
cache.IsDuringSerialization = false;
Current.Value = cache;
cache.StringReader.Initialize(jsonBuffer, jsonLength);
var jsonReader = new JsonTextReader(cache.StringReader);
cache.MemoryStream.Initialize(jsonBuffer, jsonLength);
cache.Reader.DiscardBufferedData();
var jsonReader = new JsonTextReader(cache.Reader);
cache.JsonSerializer.Populate(jsonReader, input);
if (!cache.JsonSerializer.CheckAdditionalContent)

View File

@@ -0,0 +1,157 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace FlaxEngine.Json
{
/// <summary>
/// Implements a <see cref="T:System.IO.Stream" /> that reads from unmanaged buffer (provided as raw pointer and length).
/// </summary>
internal class UnmanagedMemoryStream : Stream
{
private unsafe byte* _ptr;
private int _length;
private int _pos;
private FileAccess _access;
internal bool _isOpen;
private Task<int> _lastReadTask;
internal UnmanagedMemoryStream()
{
}
internal unsafe UnmanagedMemoryStream(byte* pointer, int length, FileAccess access = FileAccess.Read) => Initialize(pointer, length, access);
internal unsafe void Initialize(byte* pointer, int length, FileAccess access = FileAccess.Read)
{
_ptr = pointer;
_length = length;
_pos = 0;
_access = access;
_isOpen = true;
_lastReadTask = null;
}
public override bool CanRead => _isOpen && (uint)(_access & FileAccess.Read) > 0U;
public override bool CanSeek => _isOpen;
public override bool CanWrite => _isOpen && (uint)(_access & FileAccess.Write) > 0U;
protected override unsafe void Dispose(bool disposing)
{
_isOpen = false;
_ptr = null;
base.Dispose(disposing);
}
public override void Flush()
{
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
Flush();
return Task.FromResult(0);
}
public unsafe byte* Pointer => _ptr;
public override long Length => _length;
public long Capacity => _length;
public override long Position
{
get => _pos;
set => _pos = (int)value;
}
public unsafe byte* PositionPointer
{
get => _ptr + _pos;
set => _pos = (int)(value - _ptr);
}
public override unsafe int Read(byte[] buffer, int offset, int count)
{
int toRead = _length - _pos;
if (toRead > count)
toRead = count;
if (toRead <= 0)
return 0;
fixed (byte* bufferPtr = buffer)
Utils.MemoryCopy(new IntPtr(_ptr + _pos), new IntPtr(bufferPtr), toRead);
_pos += toRead;
return toRead;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
int result = Read(buffer, offset, count);
return _lastReadTask == null || _lastReadTask.Result != result ? (_lastReadTask = Task.FromResult(result)) : _lastReadTask;
}
public override unsafe int ReadByte()
{
int index = _pos;
if (index >= _length)
return -1;
_pos = index + 1;
return _ptr[index];
}
public override long Seek(long offset, SeekOrigin loc)
{
switch (loc)
{
case SeekOrigin.Begin:
_pos = (int)offset;
break;
case SeekOrigin.Current:
_pos += (int)offset;
break;
case SeekOrigin.End:
_pos = _length + (int)offset;
break;
default: throw new ArgumentOutOfRangeException();
}
return _pos;
}
public override void SetLength(long value)
{
_length = (int)value;
if (_pos > value)
_pos = _length;
}
public override unsafe void Write(byte[] buffer, int offset, int count)
{
int newPos = _pos + count;
if (newPos > _length)
_length = newPos;
fixed (byte* bufferPtr = buffer)
Utils.MemoryCopy(new IntPtr(_pos + _pos), new IntPtr(bufferPtr), count);
_pos = newPos;
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
Write(buffer, offset, count);
return Task.FromResult(0);
}
public override unsafe void WriteByte(byte value)
{
long newPos = _pos + 1;
if (_pos >= _length)
_length = (int)newPos;
_ptr[_pos] = value;
_pos = (int)newPos;
}
}
}

View File

@@ -518,29 +518,33 @@ bool ImportMaterials(AssimpImporterData& data, String& errorMsg)
if (aMaterial->Get(AI_MATKEY_NAME, aName) == AI_SUCCESS)
materialSlot.Name = String(aName.C_Str()).TrimTrailing();
materialSlot.AssetID = Guid::Empty;
aiColor3D aColor;
if (aMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, aColor) == AI_SUCCESS)
materialSlot.Diffuse.Color = ToColor(aColor);
bool aBoolean;
if (aMaterial->Get(AI_MATKEY_TWOSIDED, aBoolean) == AI_SUCCESS)
materialSlot.TwoSided = aBoolean;
bool aFloat;
if (aMaterial->Get(AI_MATKEY_OPACITY, aFloat) == AI_SUCCESS)
materialSlot.Opacity.Value = aFloat;
if (data.Model.Types & ImportDataTypes::Textures)
if (data.Model.Types & ImportDataTypes::Materials)
{
ImportMaterialTexture(data, aMaterial, aiTextureType_DIFFUSE, materialSlot.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB);
ImportMaterialTexture(data, aMaterial, aiTextureType_EMISSIVE, materialSlot.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB);
ImportMaterialTexture(data, aMaterial, aiTextureType_NORMALS, materialSlot.Normals.TextureIndex, TextureEntry::TypeHint::Normals);
ImportMaterialTexture(data, aMaterial, aiTextureType_OPACITY, materialSlot.Opacity.TextureIndex, TextureEntry::TypeHint::ColorRGBA);
aiColor3D aColor;
if (aMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, aColor) == AI_SUCCESS)
materialSlot.Diffuse.Color = ToColor(aColor);
bool aBoolean;
if (aMaterial->Get(AI_MATKEY_TWOSIDED, aBoolean) == AI_SUCCESS)
materialSlot.TwoSided = aBoolean;
bool aFloat;
if (aMaterial->Get(AI_MATKEY_OPACITY, aFloat) == AI_SUCCESS)
materialSlot.Opacity.Value = aFloat;
if (materialSlot.Diffuse.TextureIndex != -1)
if (data.Model.Types & ImportDataTypes::Textures)
{
// Detect using alpha mask in diffuse texture
materialSlot.Diffuse.HasAlphaMask = TextureTool::HasAlpha(data.Model.Textures[materialSlot.Diffuse.TextureIndex].FilePath);
if (materialSlot.Diffuse.HasAlphaMask)
data.Model.Textures[materialSlot.Diffuse.TextureIndex].Type = TextureEntry::TypeHint::ColorRGBA;
ImportMaterialTexture(data, aMaterial, aiTextureType_DIFFUSE, materialSlot.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB);
ImportMaterialTexture(data, aMaterial, aiTextureType_EMISSIVE, materialSlot.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB);
ImportMaterialTexture(data, aMaterial, aiTextureType_NORMALS, materialSlot.Normals.TextureIndex, TextureEntry::TypeHint::Normals);
ImportMaterialTexture(data, aMaterial, aiTextureType_OPACITY, materialSlot.Opacity.TextureIndex, TextureEntry::TypeHint::ColorRGBA);
if (materialSlot.Diffuse.TextureIndex != -1)
{
// Detect using alpha mask in diffuse texture
materialSlot.Diffuse.HasAlphaMask = TextureTool::HasAlpha(data.Model.Textures[materialSlot.Diffuse.TextureIndex].FilePath);
if (materialSlot.Diffuse.HasAlphaMask)
data.Model.Textures[materialSlot.Diffuse.TextureIndex].Type = TextureEntry::TypeHint::ColorRGBA;
}
}
}
}
@@ -691,13 +695,10 @@ bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, cons
ProcessNodes(assimpData, scene->mRootNode, -1);
// Import materials
if (data.Types & ImportDataTypes::Materials)
if (ImportMaterials(assimpData, errorMsg))
{
if (ImportMaterials(assimpData, errorMsg))
{
LOG(Warning, "Failed to import materials.");
return true;
}
LOG(Warning, "Failed to import materials.");
return true;
}
// Import geometry

View File

@@ -48,6 +48,11 @@ public class TextureTool : EngineModule
{
options.PrivateDependencies.Add("stb");
options.SourceFiles.Add(Path.Combine(FolderPath, "TextureTool.stb.cpp"));
if (options.Target.IsEditor)
{
// Use helper lib for decompression
options.PrivateDependencies.Add("detex");
}
}
options.PublicDefinitions.Add("COMPILE_WITH_TEXTURE_TOOL");

View File

@@ -250,6 +250,8 @@ bool TextureTool::ImportTexture(const StringView& path, TextureData& textureData
bool hasAlpha = false;
#if COMPILE_WITH_DIRECTXTEX
const auto failed = ImportTextureDirectXTex(type, path, textureData, options, errorMsg, hasAlpha);
#elif COMPILE_WITH_STB
const auto failed = ImportTextureStb(type, path, textureData, options, errorMsg, hasAlpha);
#else
const auto failed = true;
LOG(Warning, "Importing textures is not supported on this platform.");
@@ -327,6 +329,8 @@ bool TextureTool::Convert(TextureData& dst, const TextureData& src, const PixelF
#if COMPILE_WITH_DIRECTXTEX
return ConvertDirectXTex(dst, src, dstFormat);
#elif COMPILE_WITH_STB
return ConvertStb(dst, src, dstFormat);
#else
LOG(Warning, "Converting textures is not supported on this platform.");
return true;
@@ -441,11 +445,12 @@ TextureTool::PixelFormatSampler PixelFormatSamplers[] =
sizeof(Color32),
[](const void* ptr)
{
return Color(*(Color32*)ptr);
return Color::SrgbToLinear(Color(*(Color32*)ptr));
},
[](const void* ptr, const Color& color)
{
*(Color32*)ptr = Color32(color);
Color srgb = Color::LinearToSrgb(color);
*(Color32*)ptr = Color32(srgb);
},
},
{
@@ -553,11 +558,12 @@ TextureTool::PixelFormatSampler PixelFormatSamplers[] =
[](const void* ptr)
{
const Color32 bgra = *(Color32*)ptr;
return Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A));
return Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A)));
},
[](const void* ptr, const Color& color)
{
*(Color32*)ptr = Color32(byte(color.B * MAX_uint8), byte(color.G * MAX_uint8), byte(color.R * MAX_uint8), byte(color.A * MAX_uint8));
Color srgb = Color::LinearToSrgb(color);
*(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), byte(srgb.A * MAX_uint8));
},
},
{
@@ -579,11 +585,12 @@ TextureTool::PixelFormatSampler PixelFormatSamplers[] =
[](const void* ptr)
{
const Color32 bgra = *(Color32*)ptr;
return Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8));
return Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8)));
},
[](const void* ptr, const Color& color)
{
*(Color32*)ptr = Color32(byte(color.B * MAX_uint8), byte(color.G * MAX_uint8), byte(color.R * MAX_uint8), MAX_uint8);
Color srgb = Color::LinearToSrgb(color);
*(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), MAX_uint8);
},
},
{

View File

@@ -218,7 +218,7 @@ public:
/// <param name="y">The Y texture coordinates (normalized to range 0-height).</param>
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <param name="color">The color to store.</param>
/// <param name="color">The color to store (linear).</param>
static void Store(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch, const Color& color);
/// <summary>
@@ -232,7 +232,7 @@ public:
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="size">The size of the input texture (in pixels).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <returns>The sampled color.</returns>
/// <returns>The sampled color (linear).</returns>
static Color SamplePoint(const PixelFormatSampler* sampler, const Vector2& uv, const void* data, const Int2& size, int32 rowPitch);
/// <summary>
@@ -246,7 +246,7 @@ public:
/// <param name="y">The Y texture coordinates (normalized to range 0-height).</param>
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <returns>The sampled color.</returns>
/// <returns>The sampled color (linear).</returns>
static Color SamplePoint(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch);
/// <summary>
@@ -260,7 +260,7 @@ public:
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="size">The size of the input texture (in pixels).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <returns>The sampled color.</returns>
/// <returns>The sampled color (linear).</returns>
static Color SampleLinear(const PixelFormatSampler* sampler, const Vector2& uv, const void* data, const Int2& size, int32 rowPitch);
private:
@@ -291,6 +291,9 @@ private:
#if COMPILE_WITH_STB
static bool ExportTextureStb(ImageType type, const StringView& path, const TextureData& textureData);
static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, bool& hasAlpha);
static bool ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha);
static bool ConvertStb(TextureData& dst, const TextureData& src, const PixelFormat dstFormat);
static bool ResizeStb(PixelFormat format, TextureMipData& dstMip, const TextureMipData& srcMip, int32 dstMipWidth, int32 dstMipHeight);
static bool ResizeStb(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight);
#endif
};

View File

@@ -6,7 +6,9 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/Textures/TextureUtils.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Platform/File.h"
@@ -37,6 +39,17 @@
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <ThirdParty/stb/stb_image_resize.h>
#define STBD_ABS(i) Math::Abs(i)
#define STBD_FABS(x) Math::Abs(x)
#define STB_DXT_IMPLEMENTATION
#include <ThirdParty/stb/stb_dxt.h>
#if USE_EDITOR
#include <ThirdParty/detex/detex.h>
#endif
static void stbWrite(void* context, void* data, int size)
{
auto file = (FileWriteStream*)context;
@@ -50,46 +63,142 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
LOG(Warning, "Exporting texture arrays and cubemaps is not supported by stb library.");
}
TextureData const* texture = &textureData;
#if USE_EDITOR
// Handle compressed textures
TextureData decompressed;
if (PixelFormatExtensions::IsCompressed(textureData.Format))
{
decompressed.Format = PixelFormatExtensions::IsSRGB(textureData.Format) ? PixelFormat::R8G8B8A8_UNorm_sRGB : PixelFormat::R8G8B8A8_UNorm;
decompressed.Width = textureData.Width;
decompressed.Height = textureData.Height;
decompressed.Depth = textureData.Depth;
decompressed.Items.Resize(1);
decompressed.Items[0].Mips.Resize(1);
auto decompressedData = decompressed.GetData(0, 0);
decompressedData->RowPitch = textureData.Width * sizeof(Color32);
decompressedData->Lines = textureData.Height;
decompressedData->DepthPitch = decompressedData->RowPitch * decompressedData->Lines;
decompressedData->Data.Allocate(decompressedData->DepthPitch);
Color32 colors[16];
int32 blocksWidth = textureData.Width / 4;
int32 blocksHeight = textureData.Height / 4;
const auto blocksData = texture->GetData(0, 0);
byte* decompressedBytes = decompressedData->Data.Get();
switch (textureData.Format)
{
case PixelFormat::BC1_UNorm:
case PixelFormat::BC1_UNorm_sRGB:
{
for (int32 yBlock = 0; yBlock < blocksHeight; yBlock++)
{
for (int32 xBlock = 0; xBlock < blocksWidth; xBlock++)
{
const byte* block = blocksData->Data.Get() + yBlock * 4 * blocksData->RowPitch + xBlock * 8;
detexDecompressBlockBC1(block, 0, 0, (byte*)&colors);
for (int32 y = 0; y < 4; y++)
{
for (int32 x = 0; x < 4; x++)
{
*((Color32*)decompressedBytes + (yBlock * 4 + y) * textureData.Width + (xBlock * 4 + x)) = colors[y * 4 + x];
}
}
}
}
break;
}
case PixelFormat::BC2_UNorm:
case PixelFormat::BC2_UNorm_sRGB:
{
for (int32 yBlock = 0; yBlock < blocksHeight; yBlock++)
{
for (int32 xBlock = 0; xBlock < blocksWidth; xBlock++)
{
const byte* block = blocksData->Data.Get() + yBlock * 4 * blocksData->RowPitch + xBlock * 16;
detexDecompressBlockBC2(block, 0, 0, (byte*)&colors);
for (int32 y = 0; y < 4; y++)
{
for (int32 x = 0; x < 4; x++)
{
*((Color32*)decompressedBytes + (yBlock * 4 + y) * textureData.Width + (xBlock * 4 + x)) = colors[y * 4 + x];
}
}
}
}
break;
}
case PixelFormat::BC3_UNorm:
case PixelFormat::BC3_UNorm_sRGB:
{
for (int32 yBlock = 0; yBlock < blocksHeight; yBlock++)
{
for (int32 xBlock = 0; xBlock < blocksWidth; xBlock++)
{
const byte* block = blocksData->Data.Get() + yBlock * 4 * blocksData->RowPitch + xBlock * 16;
detexDecompressBlockBC3(block, 0, 0, (byte*)&colors);
for (int32 y = 0; y < 4; y++)
{
for (int32 x = 0; x < 4; x++)
{
*((Color32*)decompressedBytes + (yBlock * 4 + y) * textureData.Width + (xBlock * 4 + x)) = colors[y * 4 + x];
}
}
}
}
break;
}
default:
LOG(Warning, "Texture data format {0} is not supported by stb library.", (int32)textureData.Format);
return true;
}
texture = &decompressed;
}
#endif
// Convert into RGBA8
const auto sampler = GetSampler(textureData.Format);
const auto sampler = GetSampler(texture->Format);
if (sampler == nullptr)
{
LOG(Warning, "Texture data format {0} is not supported by stb library.", (int32)textureData.Format);
return true;
}
const auto srcData = textureData.GetData(0, 0);
const auto srcData = texture->GetData(0, 0);
const int comp = 4;
Array<byte> data;
bool sRGB = PixelFormatExtensions::IsSRGB(textureData.Format);
bool sRGB = PixelFormatExtensions::IsSRGB(texture->Format);
if (type == ImageType::HDR)
{
data.Resize(sizeof(float) * comp * textureData.Width * textureData.Height);
data.Resize(sizeof(float) * comp * texture->Width * texture->Height);
auto ptr = (Vector4*)data.Get();
for (int32 y = 0; y < textureData.Height; y++)
for (int32 y = 0; y < texture->Height; y++)
{
for (int32 x = 0; x < textureData.Width; x++)
for (int32 x = 0; x < texture->Width; x++)
{
Color color = SamplePoint(sampler, x, y, srcData->Data.Get(), srcData->RowPitch);
if (sRGB)
color = Color::SrgbToLinear(color);
*(ptr + x + y * textureData.Width) = color.ToVector4();
*(ptr + x + y * texture->Width) = color.ToVector4();
}
}
}
else
{
data.Resize(sizeof(Color32) * comp * textureData.Width * textureData.Height);
data.Resize(sizeof(Color32) * comp * texture->Width * texture->Height);
auto ptr = (Color32*)data.Get();
for (int32 y = 0; y < textureData.Height; y++)
for (int32 y = 0; y < texture->Height; y++)
{
for (int32 x = 0; x < textureData.Width; x++)
for (int32 x = 0; x < texture->Width; x++)
{
Color color = SamplePoint(sampler, x, y, srcData->Data.Get(), srcData->RowPitch);
if (sRGB)
color = Color::SrgbToLinear(color);
*(ptr + x + y * textureData.Width) = Color32(color);
*(ptr + x + y * texture->Width) = Color32(color);
}
}
}
@@ -109,21 +218,21 @@ bool TextureTool::ExportTextureStb(ImageType type, const StringView& path, const
switch (type)
{
case ImageType::BMP:
result = stbi_write_bmp_core(&s, textureData.Width, textureData.Height, comp, data.Get());
result = stbi_write_bmp_core(&s, texture->Width, texture->Height, comp, data.Get());
break;
case ImageType::JPEG:
result = stbi_write_jpg_core(&s, textureData.Width, textureData.Height, comp, data.Get(), 90);
result = stbi_write_jpg_core(&s, texture->Width, texture->Height, comp, data.Get(), 90);
break;
case ImageType::TGA:
result = stbi_write_tga_core(&s, textureData.Width, textureData.Height, comp, data.Get());
result = stbi_write_tga_core(&s, texture->Width, texture->Height, comp, data.Get());
break;
case ImageType::HDR:
result = stbi_write_hdr_core(&s, textureData.Width, textureData.Height, comp, (float*)data.Get());
result = stbi_write_hdr_core(&s, texture->Width, texture->Height, comp, (float*)data.Get());
break;
case ImageType::PNG:
{
int32 ptrSize = 0;
const auto ptr = stbi_write_png_to_mem(data.Get(), 0, textureData.Width, textureData.Height, comp, &ptrSize);
const auto ptr = stbi_write_png_to_mem(data.Get(), 0, texture->Width, texture->Height, comp, &ptrSize);
if (ptr)
{
file->WriteBytes(ptr, ptrSize);
@@ -185,9 +294,10 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
stbi_uc* stbData = stbi_load_from_memory(fileData.Get(), fileData.Count(), &width, &height, &components, 4);
if (!stbData)
{
LOG(Warning, "Failed to load image.");
LOG(Warning, "Failed to load image. {0}", String(stbi_failure_reason()));
return false;
}
fileData.Resize(0);
// Setup texture data
textureData.Width = width;
@@ -260,6 +370,370 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
return false;
}
bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, TextureData& textureData, const Options& options, String& errorMsg, bool& hasAlpha)
{
// Load image data
if (type == ImageType::Internal)
{
if (options.FlipY)
{
errorMsg = TEXT("Flipping images imported from Internal source is not supported by stb.");
return true;
}
MISSING_CODE("Importing internal textures with STB.");
return true;
}
else
{
stbi_set_flip_vertically_on_load_thread(options.FlipY);
bool failed = ImportTextureStb(type, path, textureData, hasAlpha);
stbi_set_flip_vertically_on_load_thread(false);
if (failed)
{
return true;
}
}
// Use two data containers for texture importing for more optimzied performance
TextureData textureDataTmp;
TextureData* textureDataSrc = &textureData;
TextureData* textureDataDst = &textureDataTmp;
// Check if resize source image
const int32 sourceWidth = textureData.Width;
const int32 sourceHeight = textureData.Height;
int32 width = Math::Clamp(options.Resize ? options.SizeX : static_cast<int32>(sourceWidth * options.Scale), 1, options.MaxSize);
int32 height = Math::Clamp(options.Resize ? options.SizeY : static_cast<int32>(sourceHeight * options.Scale), 1, options.MaxSize);
if (sourceWidth != width || sourceHeight != height)
{
// During resizing we need to keep texture aspect ratio
const bool keepAspectRatio = false; // TODO: expose as import option
if (keepAspectRatio)
{
const float aspectRatio = static_cast<float>(sourceWidth) / sourceHeight;
if (width >= height)
height = Math::CeilToInt(width / aspectRatio);
else
width = Math::CeilToInt(height / aspectRatio);
}
// Resize source texture
LOG(Info, "Resizing texture from {0}x{1} to {2}x{3}.", sourceWidth, sourceHeight, width, height);
if (ResizeStb(*textureDataDst, *textureDataSrc, width, height))
{
errorMsg = String::Format(TEXT("Cannot resize texture."));
return true;
}
::Swap(textureDataSrc, textureDataDst);
}
// Cache data
float alphaThreshold = 0.3f;
bool isPowerOfTwo = Math::IsPowerOfTwo(width) && Math::IsPowerOfTwo(height);
PixelFormat targetFormat = TextureUtils::ToPixelFormat(options.Type, width, height, options.Compress);
if (options.sRGB)
targetFormat = PixelFormatExtensions::TosRGB(targetFormat);
// Check mip levels
int32 sourceMipLevels = textureDataSrc->GetMipLevels();
bool hasSourceMipLevels = isPowerOfTwo && sourceMipLevels > 1;
bool useMipLevels = isPowerOfTwo && (options.GenerateMipMaps || hasSourceMipLevels) && (width > 1 || height > 1);
int32 arraySize = (int32)textureDataSrc->GetArraySize();
int32 mipLevels = MipLevelsCount(width, height, useMipLevels);
if (useMipLevels && !options.GenerateMipMaps && mipLevels != sourceMipLevels)
{
errorMsg = String::Format(TEXT("Imported texture has not full mip chain, loaded mips count: {0}, expected: {1}"), sourceMipLevels, mipLevels);
return true;
}
// Decompress if texture is compressed (next steps need decompressed input data, for eg. mip maps generation or format changing)
if (PixelFormatExtensions::IsCompressed(textureDataSrc->Format))
{
// TODO: implement texture decompression
errorMsg = String::Format(TEXT("Imported texture used compressed format {0}. Not supported for importing on this platform.."), (int32)textureDataSrc->Format);
return true;
}
// Generate mip maps chain
if (useMipLevels && options.GenerateMipMaps)
{
for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++)
{
auto& slice = textureDataSrc->Items[arrayIndex];
slice.Mips.Resize(mipLevels);
for (int32 mipIndex = 1; mipIndex < mipLevels; mipIndex++)
{
const auto& srcMip = slice.Mips[mipIndex - 1];
auto& dstMip = slice.Mips[mipIndex];
auto dstMipWidth = Math::Max(textureDataSrc->Width >> mipIndex, 1);
auto dstMipHeight = Math::Max(textureDataSrc->Height >> mipIndex, 1);
if (ResizeStb(textureDataSrc->Format, dstMip, srcMip, dstMipWidth, dstMipHeight))
{
errorMsg = TEXT("Failed to generate mip texture.");
return true;
}
}
}
}
// Preserve mipmap alpha coverage (if requested)
if (PixelFormatExtensions::HasAlpha(textureDataSrc->Format) && options.PreserveAlphaCoverage && useMipLevels)
{
// TODO: implement alpha coverage preserving
errorMsg = TEXT("Importing textures with alpha coverage preserving is not supported on this platform.");
return true;
}
// Compress mip maps or convert image
if (targetFormat != textureDataSrc->Format)
{
if (ConvertStb(*textureDataDst, *textureDataSrc, targetFormat))
{
errorMsg = String::Format(TEXT("Cannot convert/compress texture."));
return true;
}
::Swap(textureDataSrc, textureDataDst);
}
// Copy data to the output if not in the result container
if (textureDataSrc != &textureData)
{
textureData = textureDataTmp;
}
return false;
}
bool TextureTool::ConvertStb(TextureData& dst, const TextureData& src, const PixelFormat dstFormat)
{
// Setup
auto arraySize = src.GetArraySize();
dst.Width = src.Width;
dst.Height = src.Height;
dst.Depth = src.Depth;
dst.Format = dstFormat;
dst.Items.Resize(arraySize, false);
auto formatSize = PixelFormatExtensions::SizeInBytes(src.Format);
auto components = PixelFormatExtensions::ComputeComponentsCount(src.Format);
auto sampler = TextureTool::GetSampler(src.Format);
if (!sampler)
{
LOG(Warning, "Cannot convert image. Unsupported format {0}", static_cast<int32>(src.Format));
return true;
}
if (PixelFormatExtensions::IsCompressed(dstFormat))
{
int32 bytesPerBlock;
switch (dstFormat)
{
case PixelFormat::BC1_UNorm:
case PixelFormat::BC1_UNorm_sRGB:
case PixelFormat::BC4_UNorm:
bytesPerBlock = 8;
break;
default:
bytesPerBlock = 16;
break;
}
bool isDstSRGB = PixelFormatExtensions::IsSRGB(dstFormat);
// Compress all array slices
for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++)
{
const auto& srcSlice = src.Items[arrayIndex];
auto& dstSlice = dst.Items[arrayIndex];
auto mipLevels = srcSlice.Mips.Count();
dstSlice.Mips.Resize(mipLevels, false);
// Compress all mip levels
for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
{
const auto& srcMip = srcSlice.Mips[mipIndex];
auto& dstMip = dstSlice.Mips[mipIndex];
auto mipWidth = Math::Max(src.Width >> mipIndex, 1);
auto mipHeight = Math::Max(src.Height >> mipIndex, 1);
auto blocksWidth = Math::Max(Math::DivideAndRoundUp(mipWidth, 4), 1);
auto blocksHeight = Math::Max(Math::DivideAndRoundUp(mipHeight, 4), 1);
// Allocate memory
dstMip.RowPitch = blocksWidth * bytesPerBlock;
dstMip.DepthPitch = dstMip.RowPitch * blocksHeight;
dstMip.Lines = blocksHeight;
dstMip.Data.Allocate(dstMip.DepthPitch);
// Compress texture
for (int32 yBlock = 0; yBlock < blocksHeight; yBlock++)
{
for (int32 xBlock = 0; xBlock < blocksWidth; xBlock++)
{
// Sample source texture 4x4 block
Color32 srcBlock[16];
for (int32 y = 0; y < 4; y++)
{
for (int32 x = 0; x < 4; x++)
{
Color color = TextureTool::SamplePoint(sampler, xBlock * 4 + x, yBlock * 4 + y, srcMip.Data.Get(), srcMip.RowPitch);
if (isDstSRGB)
color = Color::LinearToSrgb(color);
srcBlock[y * 4 + x] = Color32(color);
}
}
// Compress block
switch (dstFormat)
{
case PixelFormat::BC1_UNorm:
case PixelFormat::BC1_UNorm_sRGB:
stb_compress_dxt_block((byte*)dstMip.Data.Get() + (yBlock * blocksWidth + xBlock) * bytesPerBlock, (byte*)&srcBlock, 0, STB_DXT_HIGHQUAL);
break;
case PixelFormat::BC3_UNorm:
case PixelFormat::BC3_UNorm_sRGB:
stb_compress_dxt_block((byte*)dstMip.Data.Get() + (yBlock * blocksWidth + xBlock) * bytesPerBlock, (byte*)&srcBlock, 1, STB_DXT_HIGHQUAL);
break;
case PixelFormat::BC4_UNorm:
for (int32 i = 1; i < 16; i++)
((byte*)&srcBlock)[i] = srcBlock[i].R;
stb_compress_bc4_block((byte*)dstMip.Data.Get() + (yBlock * blocksWidth + xBlock) * bytesPerBlock, (byte*)&srcBlock);
break;
case PixelFormat::BC5_UNorm:
for (int32 i = 0; i < 16; i++)
((uint16*)&srcBlock)[i] = srcBlock[i].R << 8 | srcBlock[i].G;
stb_compress_bc5_block((byte*)dstMip.Data.Get() + (yBlock * blocksWidth + xBlock) * bytesPerBlock, (byte*)&srcBlock);
break;
default:
LOG(Warning, "Cannot compress image. Unsupported format {0}", static_cast<int32>(dstFormat));
return true;
}
}
}
}
}
}
else
{
int32 bytesPerPixel = PixelFormatExtensions::SizeInBytes(dstFormat);
auto dstSampler = TextureTool::GetSampler(dstFormat);
if (!dstSampler)
{
LOG(Warning, "Cannot convert image. Unsupported format {0}", static_cast<int32>(dstFormat));
return true;
}
// Convert all array slices
for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++)
{
const auto& srcSlice = src.Items[arrayIndex];
auto& dstSlice = dst.Items[arrayIndex];
auto mipLevels = srcSlice.Mips.Count();
dstSlice.Mips.Resize(mipLevels, false);
// Convert all mip levels
for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
{
const auto& srcMip = srcSlice.Mips[mipIndex];
auto& dstMip = dstSlice.Mips[mipIndex];
auto mipWidth = Math::Max(src.Width >> mipIndex, 1);
auto mipHeight = Math::Max(src.Height >> mipIndex, 1);
// Allocate memory
dstMip.RowPitch = mipWidth * bytesPerPixel;
dstMip.DepthPitch = dstMip.RowPitch * mipHeight;
dstMip.Lines = mipHeight;
dstMip.Data.Allocate(dstMip.DepthPitch);
// Convert texture
for (int32 y = 0; y < mipHeight; y++)
{
for (int32 x = 0; x < mipWidth; x++)
{
// Sample source texture
Color color = TextureTool::SamplePoint(sampler, x, y, srcMip.Data.Get(), srcMip.RowPitch);
// Store destination texture
TextureTool::Store(dstSampler, x, y, dstMip.Data.Get(), dstMip.RowPitch, color);
}
}
}
}
}
return false;
}
bool TextureTool::ResizeStb(PixelFormat format, TextureMipData& dstMip, const TextureMipData& srcMip, int32 dstMipWidth, int32 dstMipHeight)
{
// Setup
auto formatSize = PixelFormatExtensions::SizeInBytes(format);
auto components = PixelFormatExtensions::ComputeComponentsCount(format);
auto srcMipWidth = srcMip.RowPitch / formatSize;
auto srcMipHeight = srcMip.DepthPitch / srcMip.RowPitch;
// Allocate memory
dstMip.RowPitch = dstMipWidth * formatSize;
dstMip.DepthPitch = dstMip.RowPitch * dstMipHeight;
dstMip.Lines = dstMipHeight;
dstMip.Data.Allocate(dstMip.DepthPitch);
// Resize texture
switch (format)
{
case PixelFormat::R8_Typeless:
case PixelFormat::R8_SInt:
case PixelFormat::R8_SNorm:
case PixelFormat::R8G8_Typeless:
case PixelFormat::R8G8_SInt:
case PixelFormat::R8G8_SNorm:
case PixelFormat::R8G8B8A8_Typeless:
case PixelFormat::R8G8B8A8_UNorm:
case PixelFormat::R8G8B8A8_UInt:
case PixelFormat::R8G8B8A8_SNorm:
case PixelFormat::R8G8B8A8_SInt:
case PixelFormat::B8G8R8A8_UNorm:
case PixelFormat::B8G8R8X8_Typeless:
case PixelFormat::B8G8R8X8_UNorm:
{
if (!stbir_resize_uint8((const uint8*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (uint8*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components))
{
LOG(Warning, "Cannot resize image.");
return true;
}
break;
}
case PixelFormat::R8G8B8A8_UNorm_sRGB:
case PixelFormat::B8G8R8A8_UNorm_sRGB:
case PixelFormat::B8G8R8X8_UNorm_sRGB:
{
auto alphaChannel = format == PixelFormat::B8G8R8X8_UNorm_sRGB ? STBIR_ALPHA_CHANNEL_NONE : 3;
if (!stbir_resize_uint8_srgb((const uint8*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (uint8*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components, alphaChannel, 0))
{
LOG(Warning, "Cannot resize image.");
return true;
}
break;
}
case PixelFormat::R32_Typeless:
case PixelFormat::R32_Float:
case PixelFormat::R32G32_Float:
case PixelFormat::R32G32B32_Float:
case PixelFormat::R32G32B32A32_Float:
{
if (!stbir_resize_float((const float*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (float*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components))
{
LOG(Warning, "Cannot resize image.");
return true;
}
break;
}
default:
LOG(Warning, "Cannot resize image. Unsupported format {0}", static_cast<int32>(format));
return true;
}
return false;
}
bool TextureTool::ResizeStb(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight)
{
// Setup
@@ -268,7 +742,7 @@ bool TextureTool::ResizeStb(TextureData& dst, const TextureData& src, int32 dstW
dst.Height = dstHeight;
dst.Depth = src.Depth;
dst.Format = src.Format;
dst.Items.Resize(arraySize);
dst.Items.Resize(arraySize, false);
auto formatSize = PixelFormatExtensions::SizeInBytes(src.Format);
auto components = PixelFormatExtensions::ComputeComponentsCount(src.Format);
@@ -278,7 +752,7 @@ bool TextureTool::ResizeStb(TextureData& dst, const TextureData& src, int32 dstW
const auto& srcSlice = src.Items[arrayIndex];
auto& dstSlice = dst.Items[arrayIndex];
auto mipLevels = srcSlice.Mips.Count();
dstSlice.Mips.Resize(mipLevels);
dstSlice.Mips.Resize(mipLevels, false);
// Resize all mip levels
for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
@@ -287,69 +761,10 @@ bool TextureTool::ResizeStb(TextureData& dst, const TextureData& src, int32 dstW
auto& dstMip = dstSlice.Mips[mipIndex];
auto srcMipWidth = srcMip.RowPitch / formatSize;
auto srcMipHeight = srcMip.DepthPitch / srcMip.RowPitch;
auto dstMipWidth = Math::Max(dstWidth << mipIndex, 1);
auto dstMipHeight = Math::Max(dstHeight << mipIndex, 1);
// Allocate memory
dstMip.RowPitch = dstMipWidth * formatSize;
dstMip.DepthPitch = dstMip.RowPitch * dstMipHeight;
dstMip.Lines = dstMipHeight;
dstMip.Data.Allocate(dstMip.DepthPitch);
// Resize texture
switch (src.Format)
{
case PixelFormat::R8_Typeless:
case PixelFormat::R8_SInt:
case PixelFormat::R8_SNorm:
case PixelFormat::R8G8_Typeless:
case PixelFormat::R8G8_SInt:
case PixelFormat::R8G8_SNorm:
case PixelFormat::R8G8B8A8_Typeless:
case PixelFormat::R8G8B8A8_UNorm:
case PixelFormat::R8G8B8A8_UInt:
case PixelFormat::R8G8B8A8_SNorm:
case PixelFormat::R8G8B8A8_SInt:
case PixelFormat::B8G8R8A8_UNorm:
case PixelFormat::B8G8R8X8_Typeless:
case PixelFormat::B8G8R8X8_UNorm:
{
if (!stbir_resize_uint8((const uint8*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (uint8*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components))
{
LOG(Warning, "Cannot resize image.");
return true;
}
break;
}
case PixelFormat::R8G8B8A8_UNorm_sRGB:
case PixelFormat::B8G8R8A8_UNorm_sRGB:
case PixelFormat::B8G8R8X8_UNorm_sRGB:
{
auto alphaChannel = src.Format == PixelFormat::B8G8R8X8_UNorm_sRGB ? STBIR_ALPHA_CHANNEL_NONE : 3;
if (!stbir_resize_uint8_srgb((const uint8*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (uint8*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components, alphaChannel, 0))
{
LOG(Warning, "Cannot resize image.");
return true;
}
break;
}
case PixelFormat::R32_Typeless:
case PixelFormat::R32_Float:
case PixelFormat::R32G32_Float:
case PixelFormat::R32G32B32_Float:
case PixelFormat::R32G32B32A32_Float:
{
if (!stbir_resize_float((const float*)srcMip.Data.Get(), srcMipWidth, srcMipHeight, srcMip.RowPitch, (float*)dstMip.Data.Get(), dstMipWidth, dstMipHeight, dstMip.RowPitch, components))
{
LOG(Warning, "Cannot resize image.");
return true;
}
break;
}
default:
LOG(Warning, "Cannot resize image. Unsupported format {0}", static_cast<int32>(src.Format));
auto dstMipWidth = Math::Max(dstWidth >> mipIndex, 1);
auto dstMipHeight = Math::Max(dstHeight >> mipIndex, 1);
if (ResizeStb(src.Format, dstMip, srcMip, dstMipWidth, dstMipHeight))
return true;
}
}
}

View File

@@ -192,7 +192,7 @@ namespace FlaxEngine.GUI
var rect = new Rectangle(new Vector2(Margin.Left, Margin.Top), Size - Margin.Size);
if (ClipText)
Render2D.PushClip(ref rect);
Render2D.PushClip(new Rectangle(Vector2.Zero, Size));
var color = IsMouseOver ? TextColorHighlighted : TextColor;

View File

@@ -124,7 +124,7 @@ namespace FlaxEngine.GUI
var font = textBlock.Style.Font.GetFont();
if (font)
{
height = font.Height;
height = font.Height / Platform.DpiScale;
return textBlock.Bounds.UpperLeft;
}
}
@@ -136,7 +136,7 @@ namespace FlaxEngine.GUI
var font = textBlock.Style.Font.GetFont();
if (font)
{
height = font.Height;
height = font.Height / Platform.DpiScale;
return textBlock.Bounds.UpperRight;
}
}
@@ -151,7 +151,7 @@ namespace FlaxEngine.GUI
var font = textBlock.Style.Font.GetFont();
if (!font)
break;
height = font.Height;
height = font.Height / Platform.DpiScale;
return textBlock.Bounds.Location + font.GetCharPosition(_text, ref textBlock.Range, index - textBlock.Range.StartIndex);
}
}
@@ -166,7 +166,7 @@ namespace FlaxEngine.GUI
var font = textBlock.Style.Font.GetFont();
if (!font)
break;
height = font.Height;
height = font.Height / Platform.DpiScale;
return textBlock.Bounds.UpperRight;
}
}
@@ -280,10 +280,11 @@ namespace FlaxEngine.GUI
{
Vector2 leftEdge = selection.StartIndex <= textBlock.Range.StartIndex ? textBlock.Bounds.UpperLeft : font.GetCharPosition(_text, selection.StartIndex);
Vector2 rightEdge = selection.EndIndex >= textBlock.Range.EndIndex ? textBlock.Bounds.UpperRight : font.GetCharPosition(_text, selection.EndIndex);
float height = font.Height / Platform.DpiScale;
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
alpha *= alpha;
Color selectionColor = Color.White * alpha;
Rectangle selectionRect = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, font.Height);
Rectangle selectionRect = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, height);
textBlock.Style.BackgroundSelectedBrush.Draw(selectionRect, selectionColor);
}
}
@@ -329,7 +330,8 @@ namespace FlaxEngine.GUI
if (textBlock.Style.UnderlineBrush != null)
{
var underLineHeight = 2.0f;
var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + font.Height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight);
var height = font.Height / Platform.DpiScale;
var underlineRect = new Rectangle(textBlock.Bounds.Location.X, textBlock.Bounds.Location.Y + height - underLineHeight * 0.5f, textBlock.Bounds.Width, underLineHeight);
textBlock.Style.UnderlineBrush.Draw(underlineRect, textBlock.Style.Color);
}
}

View File

@@ -1,7 +1,5 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using FlaxEngine.Assertions;
namespace FlaxEngine.GUI
{
/// <summary>
@@ -109,7 +107,7 @@ namespace FlaxEngine.GUI
return Vector2.Zero;
}
height = font.Height;
height = font.Height / Platform.DpiScale;
return font.GetCharPosition(_text, index, ref _layout);
}
@@ -161,7 +159,7 @@ namespace FlaxEngine.GUI
{
Vector2 leftEdge = font.GetCharPosition(_text, SelectionLeft, ref _layout);
Vector2 rightEdge = font.GetCharPosition(_text, SelectionRight, ref _layout);
float fontHeight = font.Height;
float fontHeight = font.Height / Platform.DpiScale;
// Draw selection background
float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);

View File

@@ -30,7 +30,7 @@ namespace FlaxEngine.GUI
/// Gets or sets the normalized position in the parent control that the upper left corner is anchored to (range 0-1).
/// </summary>
[Serialize]
[ExpandGroups, Limit(0.0f, 1.0f, 0.01f), EditorDisplay("Transform"), EditorOrder(990), Tooltip("The normalized position in the parent control that the upper left corner is anchored to (range 0-1).")]
[HideInEditor, ExpandGroups, Limit(0.0f, 1.0f, 0.01f), EditorDisplay("Transform"), EditorOrder(990), Tooltip("The normalized position in the parent control that the upper left corner is anchored to (range 0-1).")]
public Vector2 AnchorMin
{
get => _anchorMin;
@@ -50,7 +50,7 @@ namespace FlaxEngine.GUI
/// Gets or sets the normalized position in the parent control that the bottom right corner is anchored to (range 0-1).
/// </summary>
[Serialize]
[ExpandGroups, Limit(0.0f, 1.0f, 0.01f), EditorDisplay("Transform"), EditorOrder(991), Tooltip("The normalized position in the parent control that the bottom right corner is anchored to (range 0-1).")]
[HideInEditor, ExpandGroups, Limit(0.0f, 1.0f, 0.01f), EditorDisplay("Transform"), EditorOrder(991), Tooltip("The normalized position in the parent control that the bottom right corner is anchored to (range 0-1).")]
public Vector2 AnchorMax
{
get => _anchorMax;
@@ -70,7 +70,7 @@ namespace FlaxEngine.GUI
/// Gets or sets the offsets of the corners of the control relative to its anchors.
/// </summary>
[Serialize]
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(992), Tooltip("The offsets of the corners of the control relative to its anchors.")]
[HideInEditor, ExpandGroups, EditorDisplay("Transform"), EditorOrder(992), Tooltip("The offsets of the corners of the control relative to its anchors.")]
public Margin Offsets
{
get => _offsets;
@@ -84,11 +84,53 @@ namespace FlaxEngine.GUI
}
}
#if FLAX_EDITOR
/// <summary>
/// Helper for Editor UI (see UIControlControlEditor).
/// </summary>
[NoSerialize, HideInEditor]
internal float Proxy_Offset_Left
{
get => Offsets.Left;
set => Offsets = new Margin(value, Offsets.Right, Offsets.Top, Offsets.Bottom);
}
/// <summary>
/// Helper for Editor UI (see UIControlControlEditor).
/// </summary>
[NoSerialize, HideInEditor]
internal float Proxy_Offset_Right
{
get => Offsets.Right;
set => Offsets = new Margin(Offsets.Left, value, Offsets.Top, Offsets.Bottom);
}
/// <summary>
/// Helper for Editor UI (see UIControlControlEditor).
/// </summary>
[NoSerialize, HideInEditor]
internal float Proxy_Offset_Top
{
get => Offsets.Top;
set => Offsets = new Margin(Offsets.Left, Offsets.Right, value, Offsets.Bottom);
}
/// <summary>
/// Helper for Editor UI (see UIControlControlEditor).
/// </summary>
[NoSerialize, HideInEditor]
internal float Proxy_Offset_Bottom
{
get => Offsets.Bottom;
set => Offsets = new Margin(Offsets.Left, Offsets.Right, Offsets.Top, value);
}
#endif
/// <summary>
/// Gets or sets coordinates of the upper-left corner of the control relative to the upper-left corner of its container.
/// </summary>
[NoSerialize]
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(1000), Tooltip("The location of the upper-left corner of the control relative to he upper-left corner of its container.")]
[HideInEditor, ExpandGroups, EditorDisplay("Transform"), EditorOrder(1000), Tooltip("The location of the upper-left corner of the control relative to he upper-left corner of its container.")]
public Vector2 Location
{
get => _bounds.Location;
@@ -119,7 +161,7 @@ namespace FlaxEngine.GUI
/// Gets or sets control's size.
/// </summary>
[NoSerialize]
[EditorDisplay("Transform"), EditorOrder(1010), Tooltip("The size of the control bounds.")]
[HideInEditor, EditorDisplay("Transform"), EditorOrder(1010), Tooltip("The size of the control bounds.")]
public Vector2 Size
{
get => _bounds.Size;
@@ -231,7 +273,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the scale.
/// </summary>
[EditorDisplay("Transform"), Limit(float.MinValue, float.MaxValue, 0.1f), EditorOrder(1020), Tooltip("The control scale parameter.")]
[ExpandGroups, EditorDisplay("Transform"), Limit(float.MinValue, float.MaxValue, 0.1f), EditorOrder(1020), Tooltip("The control scale parameter.")]
public Vector2 Scale
{
get => _scale;
@@ -247,7 +289,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the normalized pivot location (used to transform control around it). Point (0,0) is upper left corner, (0.5,0.5) is center, (1,1) is bottom right corner.
/// </summary>
[EditorDisplay("Transform"), Limit(0.0f, 1.0f, 0.1f), EditorOrder(1030), Tooltip("The control rotation pivot location in normalized control size. Point (0,0) is upper left corner, (0.5,0.5) is center, (1,1) is bottom right corner.")]
[ExpandGroups, EditorDisplay("Transform"), Limit(0.0f, 1.0f, 0.1f), EditorOrder(1030), Tooltip("The control rotation pivot location in normalized control size. Point (0,0) is upper left corner, (0.5,0.5) is center, (1,1) is bottom right corner.")]
public Vector2 Pivot
{
get => _pivot;
@@ -263,7 +305,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the shear transform angles (x, y). Defined in degrees.
/// </summary>
[EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees.")]
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees.")]
public Vector2 Shear
{
get => _shear;
@@ -279,7 +321,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets or sets the rotation angle (in degrees).
/// </summary>
[EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees).")]
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees).")]
public float Rotation
{
get => _rotation;

View File

@@ -176,7 +176,7 @@ namespace FlaxEngine.GUI
/// Gets or sets the anchor preset used by the control anchors (based on <see cref="AnchorMin"/> and <see cref="AnchorMax"/>).
/// </summary>
/// <remarks>To change anchor preset with current control bounds preservation use <see cref="SetAnchorPreset"/>.</remarks>
[NoSerialize, EditorDisplay("Transform"), EditorOrder(980), Tooltip("The anchor preset used by the control anchors.")]
[NoSerialize, EditorDisplay("Transform"), HideInEditor, EditorOrder(980), Tooltip("The anchor preset used by the control anchors.")]
public AnchorPresets AnchorPreset
{
get
@@ -310,7 +310,7 @@ namespace FlaxEngine.GUI
/// <summary>
/// Gets the GUI window root control which contains that control (or null if not linked to any).
/// </summary>
public virtual WindowRootControl RootWindow => _parent?.RootWindow;
public virtual WindowRootControl RootWindow => _root?.RootWindow;
/// <summary>
/// Gets screen position of the control (upper left corner).

View File

@@ -224,7 +224,7 @@ namespace FlaxEngine.GUI
/// </summary>
public void SyncBackbufferSize()
{
float scale = ResolutionScale * Platform.DpiScale;
float scale = ResolutionScale * (RootWindow?.DpiScale ?? Platform.DpiScale);
int width = Mathf.CeilToInt(Width * scale);
int height = Mathf.CeilToInt(Height * scale);
if (_customResolution.HasValue)

View File

@@ -68,7 +68,7 @@ namespace FlaxEngine.GUI
var parentWin = target.Root;
if (parentWin == null)
return;
float dpiScale = Platform.DpiScale;
float dpiScale = target.RootWindow.DpiScale;
Vector2 dpiSize = Size * dpiScale;
Vector2 locationWS = target.PointToWindow(location);
Vector2 locationSS = parentWin.PointToScreen(locationWS);
@@ -183,7 +183,7 @@ namespace FlaxEngine.GUI
{
if (_window)
{
_window.ClientSize = Size * Platform.DpiScale;
_window.ClientSize = Size * _window.DpiScale;
}
}

View File

@@ -55,6 +55,11 @@ namespace FlaxEngine.GUI
/// </summary>
public bool IsMaximized => _window.IsMaximized;
/// <summary>
/// Gets the window DPI scale factor (1 is default). Includes custom DPI scale
/// </summary>
public float DpiScale => _window.DpiScale;
internal WindowRootControl(Window window)
{
_window = window;
@@ -151,7 +156,7 @@ namespace FlaxEngine.GUI
}
/// <inheritdoc />
public override Vector2 TrackingMouseOffset => _window.TrackingMouseOffset / _window._dpiScale;
public override Vector2 TrackingMouseOffset => _window.TrackingMouseOffset / _window.DpiScale;
/// <inheritdoc />
public override WindowRootControl RootWindow => this;
@@ -159,8 +164,8 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override Vector2 MousePosition
{
get => _window.MousePosition / _window._dpiScale;
set => _window.MousePosition = value * _window._dpiScale;
get => _window.MousePosition / _window.DpiScale;
set => _window.MousePosition = value * _window.DpiScale;
}
/// <inheritdoc />
@@ -234,13 +239,13 @@ namespace FlaxEngine.GUI
/// <inheritdoc />
public override Vector2 PointFromScreen(Vector2 location)
{
return _window.ScreenToClient(location) / _window._dpiScale;
return _window.ScreenToClient(location) / _window.DpiScale;
}
/// <inheritdoc />
public override Vector2 PointToScreen(Vector2 location)
{
return _window.ClientToScreen(location * _window._dpiScale);
return _window.ClientToScreen(location * _window.DpiScale);
}
/// <inheritdoc />

View File

@@ -177,8 +177,15 @@ void TextRender::UpdateLayout()
// Get texture atlas that contains current character
drawChunk.FontAtlasIndex = entry.TextureIndex;
fontAtlas = FontManager::GetAtlas(drawChunk.FontAtlasIndex);
fontAtlas->EnsureTextureCreated();
invAtlasSize = 1.0f / fontAtlas->GetSize();
if (fontAtlas)
{
fontAtlas->EnsureTextureCreated();
invAtlasSize = 1.0f / fontAtlas->GetSize();
}
else
{
invAtlasSize = 1.0f;
}
// Setup material
drawChunk.Material = Content::CreateVirtualAsset<MaterialInstance>();
@@ -281,6 +288,11 @@ void TextRender::UpdateLayout()
#endif
// Update text bounds (from build vertex positions)
if (_ib.Data.IsEmpty())
{
// Empty
box = BoundingBox(_transform.Translation, _transform.Translation);
}
_localBox = box;
BoundingBox::Transform(_localBox, _world, _box);
BoundingSphere::FromBox(_box, _sphere);

View File

@@ -232,20 +232,30 @@ namespace FlaxEngine
return result;
}
private static IEnumerable<string> GraphemeClusters(this string s)
{
var enumerator = System.Globalization.StringInfo.GetTextElementEnumerator(s);
while (enumerator.MoveNext())
{
yield return (string)enumerator.Current;
}
}
/// <summary>
/// Reverses the specified input string.
/// </summary>
/// <remarks>Correctly handles all UTF-16 strings</remarks>
/// <param name="s">The string to reverse.</param>
/// <returns>The reversed string.</returns>
public static string Reverse(this string s)
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
string[] graphemes = s.GraphemeClusters().ToArray();
Array.Reverse(graphemes);
return string.Concat(graphemes);
}
private static readonly Regex IncNameRegex1 = new Regex("^(\\d+)");
private static readonly Regex IncNameRegex2 = new Regex("^\\)(\\d+)\\(");
private static readonly Regex IncNameRegex1 = new Regex("(\\d+)$");
private static readonly Regex IncNameRegex2 = new Regex("\\((\\d+)\\)$");
/// <summary>
/// Tries to parse number in the name brackets at the end of the value and then increment it to create a new name.
@@ -264,14 +274,13 @@ namespace FlaxEngine
int index;
int MaxChecks = 10000;
string result;
string reversed = name.Reverse();
// Find '<name><num>' case
var match = IncNameRegex1.Match(reversed);
var match = IncNameRegex1.Match(name);
if (match.Success && match.Groups.Count == 2)
{
// Get result
string num = match.Groups[0].Value.Reverse();
string num = match.Groups[0].Value;
// Parse value
if (int.TryParse(num, out index))
@@ -294,12 +303,12 @@ namespace FlaxEngine
}
// Find '<name> (<num>)' case
match = IncNameRegex2.Match(reversed);
match = IncNameRegex2.Match(name);
if (match.Success && match.Groups.Count == 2)
{
// Get result
string num = match.Groups[0].Value;
num = num.Substring(1, num.Length - 2).Reverse();
num = num.Substring(1, num.Length - 2);
// Parse value
if (int.TryParse(num, out index))

View File

@@ -1118,7 +1118,8 @@ ShaderGenerator::Value ShaderGenerator::writeLocal(ValueType type, const String&
ShaderGenerator::Value ShaderGenerator::writeOperation2(Node* caller, const Value& valueA, const Value& valueB, Char op1)
{
const String value = String::Format(TEXT("{0} {1} {2}"), valueA.Value, op1, Value::Cast(valueB, valueA.Type).Value);
const Char op1Str[2] = { op1, 0};
const String value = String::Format(TEXT("{0} {1} {2}"), valueA.Value, op1Str, Value::Cast(valueB, valueA.Type).Value);
return writeLocal(valueA.Type, value, caller);
}