Merge remote-tracking branch 'origin/1.1' into 1.2
# Conflicts: # Source/Editor/Editor.Build.cs
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -156,9 +156,9 @@ void Log::Logger::Dispose()
|
||||
WriteFloor();
|
||||
|
||||
// Close
|
||||
LogAfterInit = false;
|
||||
if (LogAfterInit)
|
||||
{
|
||||
LogAfterInit = false;
|
||||
LogFile->Close();
|
||||
Delete(LogFile);
|
||||
LogFile = nullptr;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -100,7 +100,6 @@ private:
|
||||
|
||||
UWPWindowImpl* _impl;
|
||||
|
||||
float _dpi, _dpiScale;
|
||||
Vector2 _logicalSize;
|
||||
|
||||
public:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
157
Source/Engine/Serialization/UnmanagedMemoryStream.cs
Normal file
157
Source/Engine/Serialization/UnmanagedMemoryStream.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user