message: Copy some initial documentation form package.json to USD.md

diff:
# USD for Unity
-Somce documentation
+The Unity USD package contains a set of libraries designed to support the use of Pixar's Universal Scene Description (USD) in Unity. The goal of this package is to make it maximally easy to integrate USD using native Unity & C# data types with a familiar serialization paradigm and little prior knowledge of USD. USD enables round-trip asset workflows between digital content creation tools like Maya, 3ds Max, Houdini, Katana, Nuke, Modo, Sketchup, Substance Designer, and Substance Painter.
+
+Unlike other asset file formats, Universal Scene Description is designed to support an entire asset pipeline and to enable teams to work in parallel using asset references and overrides.
+
+This package includes ready-to-use binaries and examples to allow anyone to easily jump-in.

diff:
"dependencies": {
"com.unity.timeline" : "1.0.0"
},
- "description": "The Unity USD package contains a set of libraries designed to support the use of Pixar's Universal Scene Description (USD) in Unity. The goal of this package is to make it maximally easy to integrate USD using native Unity & C# data types with a familiar serialization paradigm and little prior knowledge of USD. USD enables round-trip asset workflows between digital content creation tools like Maya, 3ds Max, Houdini, Katana, Nuke, Modo, Sketchup, Substance Designer, and Substance Painter. \n\nUnlike other asset file formats, Universal Scene Description is designed to support an entire asset pipeline and to enable teams to work in parallel using asset references and overrides. \n\nThis package includes ready-to-use binaries and examples to allow anyone to easily jump-in (see Examples folder).",
+ "description": "The Unity USD package contains a set of libraries designed to support the use of Pixar's Universal Scene Description (USD) in Unity. The goal of this package is to make it maximally easy to integrate USD using native Unity & C# data types with a familiar serialization paradigm and little prior knowledge of USD. USD enables round-trip asset workflows between digital content creation tools like Maya, 3ds Max, Houdini, Katana, Nuke, Modo, Sketchup, Substance Designer, and Substance Painter. \n\nUnlike other asset file formats, Universal Scene Description is designed to support an entire asset pipeline and to enable teams to work in parallel using asset references and overrides. \n\nThis package includes ready-to-use binaries and examples to allow anyone to easily jump-in.",
"displayName": "USD",
"keywords": [
"usd",


message: Add new lines at the end of 2 files.

change_type: MODIFY

diff:
@@ -10,3 +10,4 @@ namespace Unity.Formats.USD.Tests
}
}
}
+

diff:
@@ -77,3 +77,4 @@ namespace Unity.Formats.USD.Tests
}
}
}
+

license: Apache License 2.0

repo: unity-technologies/usd-unity-sdk

original_message: Add new lines at the end of 2 files.


message: Fixed meta files so Dlls are enabled on OS X Standalone builds.

diff:
fileFormatVersion: 2
guid: 19eea9797d36d0843b13c9d3f253d336
-timeCreated: 1494027065
-licenseType: Pro
PluginImporter:
- serializedVersion: 1
+ externalObjects: {}
+ serializedVersion: 2
iconMap: {}
executionOrder: {}
+ defineConstraints: []
isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
platformData:
- Android:
+ - first:
+ '': Any
+ second:
enabled: 0
settings:
- CPU: AnyCPU
- Any:
+ Exclude Editor: 0
+ Exclude Linux64: 1
+ Exclude OSXUniversal: 0
+ Exclude Win: 1
+ Exclude Win64: 0
+ - first:
+ '': Linux
+ second:
enabled: 0
- settings: {}
- Editor:
- enabled: 1
settings:
- CPU: AnyCPU
- DefaultValueInitialized: true
- OS: AnyOS
- Linux:
+ CPU: None
+ - first:
+ '': LinuxUniversal
+ second:
enabled: 0
settings:
CPU: None
- Linux64:
+ - first:
+ '': OSXIntel
+ second:
enabled: 0
settings:
CPU: None
- LinuxUniversal:
+ - first:
+ '': OSXIntel64
+ second:
enabled: 0
settings:
CPU: None
- OSXIntel:
+ - first:
+ Android: Android
+ second:
enabled: 0
settings:
- CPU: None
- OSXIntel64:
+ CPU: AnyCPU
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ DefaultValueInitialized: true
+ OS: AnyOS
+ - first:
+ Facebook: Win
+ second:
enabled: 0
settings:
CPU: None
- OSXUniversal:
+ - first:
+ Facebook: Win64
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ - first:
+ Standalone: Linux64
+ second:
enabled: 0
settings:
CPU: None
- Win:
+ - first:
+ Standalone: OSXUniversal
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ - first:
+ Standalone: Win
+ second:
enabled: 0
settings:
CPU: None
- Win64:
+ - first:
+ Standalone: Win64
+ second:
enabled: 1
settings:
CPU: AnyCPU
- WindowsStoreApps:
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
enabled: 0
settings:
CPU: AnyCPU

diff:
fileFormatVersion: 2
guid: 97a1c74d192859449af63b88c971db03
-timeCreated: 1494027066
-licenseType: Pro
PluginImporter:
- serializedVersion: 1
+ externalObjects: {}
+ serializedVersion: 2
iconMap: {}
executionOrder: {}
+ defineConstraints: []
isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
platformData:
- Android:
+ - first:
+ '': Any
+ second:
enabled: 0
settings:
- CPU: AnyCPU
- Any:
+ Exclude Editor: 0
+ Exclude Linux64: 1
+ Exclude OSXUniversal: 0
+ Exclude Win: 1
+ Exclude Win64: 0
+ - first:
+ '': Linux
+ second:
enabled: 0
- settings: {}
- Editor:
- enabled: 1
settings:
- CPU: AnyCPU
- DefaultValueInitialized: true
- OS: AnyOS
- Linux:
+ CPU: None
+ - first:
+ '': LinuxUniversal
+ second:
enabled: 0
settings:
CPU: None
- Linux64:
+ - first:
+ '': OSXIntel
+ second:
enabled: 0
settings:
CPU: None
- LinuxUniversal:
+ - first:
+ '': OSXIntel64
+ second:
enabled: 0
settings:
CPU: None
- OSXIntel:
+ - first:
+ Android: Android
+ second:
enabled: 0
settings:
- CPU: None
- OSXIntel64:
+ CPU: AnyCPU
+ - first:
+ Any:
+ second:
+ enabled: 0
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ DefaultValueInitialized: true
+ OS: AnyOS
+ - first:
+ Facebook: Win
+ second:
enabled: 0
settings:
CPU: None
- OSXUniversal:
+ - first:
+ Facebook: Win64
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ - first:
+ Standalone: Linux64
+ second:
enabled: 0
settings:
CPU: None
- Win:
+ - first:
+ Standalone: OSXUniversal
+ second:
+ enabled: 1
+ settings:
+ CPU: AnyCPU
+ - first:
+ Standalone: Win
+ second:
enabled: 0
settings:
CPU: None
- Win64:
+ - first:
+ Standalone: Win64
+ second:
enabled: 1
settings:
CPU: AnyCPU
- WindowsStoreApps:
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
enabled: 0
settings:
CPU: AnyCPU

diff:
@@ -11,6 +11,19 @@ PluginImporter:
isOverridable: 0
isExplicitlyReferenced: 0
platformData:
+ - first:
+ '': Any
+ second:
+ enabled: 0
+ settings:
+ Exclude Editor: 0
+ Exclude Linux: 0
+ Exclude Linux64: 0
+ Exclude LinuxUniversal: 0
+ Exclude OSXUniversal: 0
+ Exclude Win: 0
+ Exclude Win64: 0
+ Exclude iOS: 0
- first:
Any:
second:
@@ -19,10 +32,11 @@ PluginImporter:
- first:
Editor: Editor
second:
- enabled: 0
+ enabled: 1
settings:
CPU: x86_64
DefaultValueInitialized: true
+ OS: AnyOS
- first:
Facebook: Win
second:
@@ -38,7 +52,7 @@ PluginImporter:
- first:
Standalone: Linux
second:
- enabled: 0
+ enabled: 1
settings:
CPU: None
- first:
@@ -52,7 +66,7 @@ PluginImporter:
second:
enabled: 1
settings:
- CPU: x86_64
+ CPU: AnyCPU
- first:
Standalone: OSXIntel
second:
@@ -68,13 +82,13 @@ PluginImporter:
- first:
Standalone: OSXUniversal
second:
- enabled: 0
+ enabled: 1
settings:
- CPU: x86_64
+ CPU: AnyCPU
- first:
Standalone: Win
second:
- enabled: 0
+ enabled: 1
settings:
CPU: None
- first:
@@ -83,6 +97,14 @@ PluginImporter:
enabled: 1
settings:
CPU: AnyCPU
+ - first:
+ iPhone: iOS
+ second:
+ enabled: 1
+ settings:
+ AddToEmbeddedBinaries: false
+ CompileFlags:
+ FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:


message: Fix timeline clips not implementing the correct interface in order to specify clipCaps.

diff:
@@ -19,7 +19,7 @@ using UnityEngine.Timeline;
namespace Unity.Formats.USD {
[System.Serializable]
- public class UsdPlayableAsset : PlayableAsset {
+ public class UsdPlayableAsset : PlayableAsset, ITimelineClipAsset {
private UsdAsset m_sourceUsdAsset;
[Tooltip("USD Player to Control")]


message: Fix error in trunk about usdAsset not inheriting from AssetImporter

diff:
@@ -19,7 +19,7 @@ using UnityEditor.Experimental.AssetImporters;
namespace Unity.Formats.USD {
[CustomEditor(typeof(UsdAsset))]
- public class UsdAssetEditor : ScriptedImporterEditor {
+ public class UsdAssetEditor : Editor {
private readonly string[] kTabNames = new string[] { "Simple", "Advanced" };
private int m_tab;
@@ -38,8 +38,8 @@ namespace Unity.Formats.USD {
Custom = 4
}
- public override void OnEnable() {
- base.OnEnable();
+ public void OnEnable() {
+
if (!m_usdLogo) {
var script = MonoScript.FromScriptableObject(this);
var path = AssetDatabase.GetAssetPath(script);


message: Make Mesh and Scene Import options Serializable so they can be stored in a scene

diff:
@@ -43,6 +43,7 @@ namespace Unity.Formats.USD {
/// <summary>
/// Indicates how values are imported from the given scene into a UnityEngine.Mesh object.
/// </summary>
+ [System.Serializable]
public class MeshImportOptions {
/// <summary>

diff:
@@ -67,6 +67,7 @@ namespace Unity.Formats.USD {
/// <summary>
/// Indicates how the scene should be imported from USD to Unity.
/// </summary>
+ [System.Serializable]
public class SceneImportOptions {
/// <summary>
@@ -134,6 +135,7 @@ namespace Unity.Formats.USD {
/// <summary>
/// A set of registered mappings from USD shader ID to Unity material.
/// </summary>
+ [System.NonSerialized]
public MaterialMap materialMap = new MaterialMap();
/// <summary>


message: Lazy instantation of MaterialMap default materials to avoid errors from calling constructor during deserialization

diff:
@@ -52,15 +52,34 @@ namespace Unity.Formats.USD {
/// </summary>
public bool useOriginalShaderIfAvailable;
+ Material m_displayColorMaterial, m_specularWorkflowMaterial, m_metallicWorkflowMaterial;
+
/// <summary>
/// A material to use when no material could be found.
/// </summary>
- public Material DisplayColorMaterial { get; set; }
-
- public Material SpecularWorkflowMaterial { get; set; }
- public Material MetallicWorkflowMaterial { get; set; }
+ public Material DisplayColorMaterial {
+ get {
+ if (m_displayColorMaterial == null) InstantiateMaterials();
+ return m_displayColorMaterial;
+ }
+ set {m_displayColorMaterial = value;}
+ }
+ public Material SpecularWorkflowMaterial {
+ get {
+ if (m_specularWorkflowMaterial == null) InstantiateMaterials();
+ return m_specularWorkflowMaterial;
+ }
+ set {m_specularWorkflowMaterial = value;}
+ }
+ public Material MetallicWorkflowMaterial {
+ get {
+ if (m_metallicWorkflowMaterial == null) InstantiateMaterials();
+ return m_metallicWorkflowMaterial;
+ }
+ set {m_metallicWorkflowMaterial = value;}
+ }
- public MaterialMap() {
+ void InstantiateMaterials() {
var pipeline = UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset;
if (!pipeline) {
// Fallback to the built-in render pipeline, assume Standard PBS shader.


message: allow Simple UI to take text input to file and root. added file browser

diff:
@@ -175,12 +175,22 @@ namespace Unity.Formats.USD {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("USD File");
- EditorGUILayout.LabelField(usdAsset.usdFullPath, EditorStyles.textField);
+ usdAsset.usdFullPath = EditorGUILayout.TextField(usdAsset.usdFullPath, EditorStyles.textField);
+ if(GUILayout.Button("...")) {
+ string lastDir;
+ if (string.IsNullOrEmpty(usdAsset.usdFullPath))
+ lastDir = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
+ else
+ lastDir = Path.GetDirectoryName(usdAsset.usdFullPath);
+ string importFilepath = EditorUtility.OpenFilePanelWithFilters("Usd Asset", lastDir, new string[] { "Usd","us*"});
+ if ( string.IsNullOrEmpty(importFilepath)) return;
+ usdAsset.usdFullPath = importFilepath;
+ }
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("USD Root Path");
- EditorGUILayout.LabelField(usdAsset.m_usdRootPath, EditorStyles.textField);
+ usdAsset.m_usdRootPath = EditorGUILayout.TextField(usdAsset.m_usdRootPath, EditorStyles.textField);
EditorGUILayout.EndHorizontal();
GUILayout.Label("Import Settings", EditorStyles.boldLabel);


message: UsdAsset Buttons react to loaded state of UsdAsset instance.

diff:
@@ -126,6 +126,11 @@ namespace Unity.Formats.USD {
style.fontSize = 12;
style.wordWrap = true;
EditorGUILayout.LabelField("Edit prefab for destructive operations", style);
+ } else {
+ if (usdAsset.transform.childCount == 0) {
+ if (GUILayout.Button(new GUIContent(m_reimportButton, "Import from USD"), buttonStyle)) {
+ ReloadFromUsd(usdAsset, forceRebuild: true);
+ }
} else {
if (GUILayout.Button(new GUIContent(m_reimportButton, "Reimport from USD (destructive)"), buttonStyle)) {
if (EditorUtility.DisplayDialog("Reimport from Source", "Destroy and rebuild all USD objects?\n\n"
@@ -134,6 +139,9 @@ namespace Unity.Formats.USD {
ReloadFromUsd(usdAsset, forceRebuild: true);
}
}
+ }
+
+ EditorGUI.BeginDisabledGroup(usdAsset.transform.childCount == 0);
if (GUILayout.Button(new GUIContent(m_trashButton, "Remove USD Contents (destructive)"), buttonStyle)) {
if (EditorUtility.DisplayDialog("Clear Contents", "Destroy all USD objects?\n\n"
+ "Any GameObject with a UsdPrimSource will be destroyed. "
@@ -149,6 +157,7 @@ namespace Unity.Formats.USD {
DetachFromUsd(usdAsset);
}
}
+ EditorGUI.EndDisabledGroup();
}
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();


message: IP on implementing ImportProcessor. PrimMap and Geometry work, IP on UsdPath

change_type: ADD

diff:
+fileFormatVersion: 2
+guid: 486e7a15a5b8a244fa4d1d7528ff756e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:

diff:
+fileFormatVersion: 2
+guid: eb13fa709dbfbd548b745a46a99e442e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:

diff:
+fileFormatVersion: 2
+guid: 072090583bba1ac47aba500294914c30
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:

diff:
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using System.Text.RegularExpressions;
+using pxr;
+
+//possibly move this to Examples
+namespace Unity.Formats.USD
+{
+ public abstract class RegexImportProcessor : MonoBehaviour
+ {
+
+ public enum ECompareAgainst { UsdName, UsdPath }
+ public ECompareAgainst compareAgainst;
+
+ public enum EMatchType { Wildcard, Regex }
+ public EMatchType matchType;
+
+ public bool isNot = false;
+
+ public string expression = "*";
+ private Regex m_regex;
+
+ protected bool IsMatch(SdfPath sdfPath)
+ {
+ string test = compareAgainst == ECompareAgainst.UsdName ? sdfPath.GetName() : sdfPath.ToString();
+ return isNot ? ! m_regex.IsMatch(test) : m_regex.IsMatch(test);
+ }
+
+ protected void InitRegex()
+ {
+ switch (matchType)
+ {
+ case EMatchType.Wildcard:
+ m_regex = new Regex(WildcardToRegex(expression));
+ break;
+ case EMatchType.Regex:
+ m_regex = new Regex(expression);
+ break;
+ }
+ }
+
+ public static string WildcardToRegex(string pattern)
+ {
+ return "^" + Regex.Escape(pattern).
+ Replace("\\*", ".*").
+ Replace("\\?", ".") + "$";
+ }
+ }
+}

diff:
+fileFormatVersion: 2
+guid: 2b8f80f88bd381245a8682e76f675299
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:

diff:
+fileFormatVersion: 2
+guid: bef49abbaed129443bd8dc351c0cb266
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:

diff:
@@ -342,6 +342,11 @@ namespace Unity.Formats.USD {
if (AfterBuildPrimMap != null) {
AfterBuildPrimMap(primMap, scene, usdPrimRoot, root, importOptions);
}
+
+ foreach( var processor in root.GetComponents<IImportProcessPrimMap>())
+ {
+ processor.ProcessPrimMap(primMap);
+ }
Profiler.EndSample();
if (ShouldYield(targetTime, timer)) { yield return null; ResetTimer(timer); }
@@ -936,6 +941,11 @@ namespace Unity.Formats.USD {
if (AfterImport != null) {
AfterImport(primMap, scene, usdPrimRoot, root, importOptions);
}
+ Debug.LogError("IImportProcessGeometry");
+ foreach( var processor in root.GetComponents<IImportProcessGeometry>())
+ {
+ processor.ProcessGeometry(primMap);
+ }
Profiler.EndSample();
}


message: Cleanup and rename of ImportProcessor

diff:
@@ -7,11 +7,11 @@ using pxr;
//possibly move this to Examples
// also not sure how well this will work with reloads or anim..
namespace Unity.Formats.USD {
- public class CombineMeshes : RegexImportProcessor, IImportProcessGeometry {
+ public class CombineMeshes : RegexImportProcessor, IImportPostProcessGeometry {
const int VERTEX_LIMIT = 65534;
public bool enforceU16VertexLimit;
- public void ProcessGeometry(PrimMap primMap)
+ public void PostProcessGeometry(PrimMap primMap)
{
InitRegex();
@@ -25,7 +25,7 @@ namespace Unity.Formats.USD {
void Reset()
{
- expression = "Geom";
+ matchExpression = "Geom";
isNot = false;
matchType = EMatchType.Wildcard;
compareAgainst = ECompareAgainst.UsdName;

diff:
@@ -6,17 +6,21 @@ using Unity.Formats.USD;
using pxr;
namespace Unity.Formats.USD {
- public interface IImportProcessUsd
+ // happens while hierarchy is loading
+ public interface IImportProcessSdfPath
{
- void ProcessUsd(PrimMap primMap);
+ bool ProcessSdfPath(SdfPath sdfPath);
}
- public interface IImportProcessPrimMap
+
+ // happens after GameObject Hierarchy created
+ public interface IImportPostProcessHierarchy
{
- void ProcessPrimMap(PrimMap primMap);
+ void PostProcessHierarchy(PrimMap primMap);
}
- public interface IImportProcessGeometry
+ // happens after GameObject Hierarchy created and Geometry created
+ public interface IImportPostProcessGeometry
{
- void ProcessGeometry(PrimMap primMap);
+ void PostProcessGeometry(PrimMap primMap);
}
}
\ No newline at end of file

diff:
@@ -18,7 +18,7 @@ namespace Unity.Formats.USD
public bool isNot = false;
- public string expression = "*";
+ public string matchExpression = "*";
private Regex m_regex;
protected bool IsMatch(SdfPath sdfPath)
@@ -32,10 +32,10 @@ namespace Unity.Formats.USD
switch (matchType)
{
case EMatchType.Wildcard:
- m_regex = new Regex(WildcardToRegex(expression));
+ m_regex = new Regex(WildcardToRegex(matchExpression));
break;
case EMatchType.Regex:
- m_regex = new Regex(expression);
+ m_regex = new Regex(matchExpression);
break;
}
}

diff:
@@ -7,10 +7,10 @@ using pxr;
//possibly move this to Examples
namespace Unity.Formats.USD
{
- public class SetHideFlags : RegexImportProcessor, IImportProcessPrimMap
+ public class SetHideFlags : RegexImportProcessor, IImportPostProcessHierarchy
{
public HideFlags hideFlagsSettings = HideFlags.DontSave;
- public void ProcessPrimMap(PrimMap primMap)
+ public void PostProcessHierarchy(PrimMap primMap)
{
InitRegex();
@@ -24,7 +24,7 @@ namespace Unity.Formats.USD
void Reset()
{
- expression = "/";
+ matchExpression = "/";
isNot = true;
matchType = EMatchType.Wildcard;
compareAgainst = ECompareAgainst.UsdName;

diff:
@@ -61,23 +61,6 @@ namespace Unity.Formats.USD {
/// </summary>
public static IImporter ActiveMeshImporter;
- public delegate void ImportNotice(PrimMap primMap,
- Scene usdScene,
- pxr.SdfPath usdPrimRoot,
- GameObject unityRoot,
- SceneImportOptions importOptions);
-
- /// <summary>
- /// Executes after the PrimMap has been constructed and before import.
- /// </summary>
- public static event ImportNotice AfterBuildPrimMap;
-
- /// <summary>
- /// Executes after the entire import process (BuildScene) completes. Note that this event may
- /// fire after several frames, when the coroutine overload of BuildScene is used.
- /// </summary>
- public static event ImportNotice AfterImport;
-
static SceneImporter() {
SceneImporter.ActiveMeshImporter = new MeshImportStrategy(
MeshImporter.BuildMesh,
@@ -338,14 +321,10 @@ namespace Unity.Formats.USD {
if (ShouldYield(targetTime, timer)) { yield return null; ResetTimer(timer); }
- Profiler.BeginSample("USD: AfterBuildPrimMap");
- if (AfterBuildPrimMap != null) {
- AfterBuildPrimMap(primMap, scene, usdPrimRoot, root, importOptions);
- }
-
- foreach( var processor in root.GetComponents<IImportProcessPrimMap>())
+ Profiler.BeginSample("USD: PostProcessHierarchy");
+ foreach( var processor in root.GetComponents<IImportPostProcessHierarchy>())
{
- processor.ProcessPrimMap(primMap);
+ processor.PostProcessHierarchy(primMap);
}
Profiler.EndSample();
@@ -934,17 +913,10 @@ namespace Unity.Formats.USD {
}
Profiler.EndSample();
- //
- // AfterImport callback.
- //
- Profiler.BeginSample("AfterImport callback");
- if (AfterImport != null) {
- AfterImport(primMap, scene, usdPrimRoot, root, importOptions);
- }
- Debug.LogError("IImportProcessGeometry");
- foreach( var processor in root.GetComponents<IImportProcessGeometry>())
+ Profiler.BeginSample("USD: PostProcessGeometry");
+ foreach( var processor in root.GetComponents<IImportPostProcessGeometry>())
{
- processor.ProcessGeometry(primMap);
+ processor.PostProcessGeometry(primMap);
}
Profiler.EndSample();
}

original_message: Cleanup and rename of ImportProcessor


message: Added place holder for usd scene processing

diff:
@@ -7,9 +7,9 @@ using pxr;
namespace Unity.Formats.USD {
// happens while hierarchy is loading
- public interface IImportProcessSdfPath
+ public interface IImportProcessUsd
{
- bool ProcessSdfPath(SdfPath sdfPath);
+ void ProcessUsd(Scene scene);
}
// happens after GameObject Hierarchy created


message: Added SceneImportOptions arg for processors.

diff:
@@ -11,7 +11,7 @@ namespace Unity.Formats.USD {
const int VERTEX_LIMIT = 65534;
public bool enforceU16VertexLimit;
- public void PostProcessGeometry(PrimMap primMap)
+ public void PostProcessGeometry(PrimMap primMap, SceneImportOptions sceneImportOptions)
{
InitRegex();
@@ -77,7 +77,7 @@ namespace Unity.Formats.USD {
}
//build new meshes
- bool mergeSubMeshes = true; //options.materialAssign == Options.EMaterialAssign.DefaultOnly;
+ bool mergeSubMeshes = true;
foreach (List<CombineInstance> subcis in listcis)
{

diff:
@@ -10,7 +10,7 @@ namespace Unity.Formats.USD
public class SetHideFlags : RegexImportProcessor, IImportPostProcessHierarchy
{
public HideFlags hideFlagsSettings = HideFlags.DontSave;
- public void PostProcessHierarchy(PrimMap primMap)
+ public void PostProcessHierarchy(PrimMap primMap, SceneImportOptions sceneImportOptions)
{
InitRegex();

diff:
@@ -324,7 +324,7 @@ namespace Unity.Formats.USD {
Profiler.BeginSample("USD: PostProcessHierarchy");
foreach( var processor in root.GetComponents<IImportPostProcessHierarchy>())
{
- processor.PostProcessHierarchy(primMap);
+ processor.PostProcessHierarchy(primMap, importOptions);
}
Profiler.EndSample();
@@ -916,7 +916,7 @@ namespace Unity.Formats.USD {
Profiler.BeginSample("USD: PostProcessGeometry");
foreach( var processor in root.GetComponents<IImportPostProcessGeometry>())
{
- processor.PostProcessGeometry(primMap);
+ processor.PostProcessGeometry(primMap, importOptions);
}
Profiler.EndSample();
}


message: Renamed PostProcessGeometry to PostProcessComponents. Added doctags and tooltips for ImportProcessors

diff:
+// Copyright 2018 Pixar Animation Studios
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text.RegularExpressions;
using pxr;
-//possibly move this to Examples
-namespace Unity.Formats.USD
+namespace Unity.Formats.USD.Examples
{
+ /// <summary>
+ /// Abstract class meant as base class for certain IImportPostProcessHierarchy
+ /// implmentations. Has methods for filtering Usd Paths based on glob-style wildcards or regex.
+ /// </summary>
public abstract class RegexImportProcessor : MonoBehaviour
{
public enum ECompareAgainst { UsdName, UsdPath }
+ [Tooltip("UsdName matches only the name of the GameObject/UsdPrim, while UsdPath matches against the entire path of the object starting at /")]
public ECompareAgainst compareAgainst;
public enum EMatchType { Wildcard, Regex }
+ [Tooltip("Wildcard matches using simple filesystem-style globbing, Regex uses strict regular expression")]
public EMatchType matchType;
+ [Tooltip("If true, matches are ignored, failed matches are proccessed.")]
public bool isNot = false;
+ [Tooltip("The wildcard or regex expression to test against")]
public string matchExpression = "*";
private Regex m_regex;

diff:
@@ -913,10 +913,10 @@ namespace Unity.Formats.USD {
}
Profiler.EndSample();
- Profiler.BeginSample("USD: PostProcessGeometry");
- foreach( var processor in root.GetComponents<IImportPostProcessGeometry>())
+ Profiler.BeginSample("USD: Post Process Components");
+ foreach( var processor in root.GetComponents<IImportPostProcessComponents>())
{
- processor.PostProcessGeometry(primMap, importOptions);
+ processor.PostProcessComponents(primMap, importOptions);
}
Profiler.EndSample();
}


message: Moved ImportProcessor implementations to Samples~/. Moved interfaces to Scene/

change_type: DELETE

diff:
-fileFormatVersion: 2
-guid: 486e7a15a5b8a244fa4d1d7528ff756e
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:

change_type: RENAME


message: Wrapped PostProcess callbacks in try-catch

diff:
@@ -324,7 +324,11 @@ namespace Unity.Formats.USD {
Profiler.BeginSample("USD: Post Process Hierarchy");
foreach( var processor in root.GetComponents<IImportPostProcessHierarchy>())
{
+ try {
processor.PostProcessHierarchy(primMap, importOptions);
+ } catch (System.Exception ex) {
+ Debug.LogException(ex);
+ }
}
Profiler.EndSample();
@@ -916,7 +920,11 @@ namespace Unity.Formats.USD {
Profiler.BeginSample("USD: Post Process Components");
foreach( var processor in root.GetComponents<IImportPostProcessComponents>())
{
+ try {
processor.PostProcessComponents(primMap, importOptions);
+ } catch (System.Exception ex) {
+ Debug.LogException(ex);
+ }
}
Profiler.EndSample();
}


message: Fixed publishing on yamato. Publish is triggered by having a tag with "vX.Y.Z". package.json needs to match this version.

diff:
@@ -50,26 +50,20 @@ run_preview_verified_staging:
flavor: m1.large
name: Runner
commands:
- # Install ci tools dependencies
- - git clone --single-branch --branch feat/PAI-434-template-test git@gitlab.cds.internal.unity3d.com:upm-packages/project-templates/upm-template-utils.git
- - npm install upm-template-utils
- - node upm-template-utils/index.js template publish
+ - npm install upm-ci-utils -g --registry https://api.bintray.com/npm/unity/unity-npm
+ - upm-ci package pack
+ - upm-ci package publish --registry staging
triggers:
tags:
only:
- - /^(v|V)[0-9].[0-9].[0-9]/
+ - /^(v|V)[0-9]+.[0-9]+.[0-9]+/
artifacts:
- logs.zip:
- paths:
- - "upm-ci~/logs/**/*"
- - "upm-ci~/test-results/**/*"
- Package.zip:
+ artifacts.zip:
paths:
- - "upm-ci~/package/*.tgz"
+ - "upm-ci~/packages/*.tgz"
dependencies:
{% for editor in editors %}
{% for platform in platforms %}
- .yamato/upm-ci.yml#{{ platform.name }}_{{ editor.version }}
{% endfor %}
{% endfor %}
-


message: Do not repack during run_verified_publish

diff:
@@ -51,7 +51,6 @@ run_preview_verified_staging:
name: Runner
commands:
- npm install upm-ci-utils -g --registry https://api.bintray.com/npm/unity/unity-npm
- - upm-ci package pack
- upm-ci package publish --registry staging
triggers:
tags:


message: Different publish command.

diff:
@@ -51,7 +51,11 @@ run_preview_verified_staging:
name: Runner
commands:
- npm install upm-ci-utils -g --registry https://api.bintray.com/npm/unity/unity-npm
- - upm-ci package publish
+ - |
+ cp upm-ci~ package/com.unity.formats.usd
+ cd package/com.unity.formats.usd
+ upm-ci package publish
+
triggers:
tags:
only:

original_message: Different publish command.


message: Copy artefacts properly.

diff:
@@ -51,11 +51,7 @@ run_preview_verified_staging:
name: Runner
commands:
- npm install upm-ci-utils -g --registry https://api.bintray.com/npm/unity/unity-npm
- - |
- cp upm-ci~ package/com.unity.formats.usd
- cd package/com.unity.formats.usd
- upm-ci package publish
-
+ - upm-ci package publish --package-path package/com.unity.formats.usd
triggers:
tags:
only:

original_message: Copy artefacts properly.


message: Updated license for ImportProcessor an it's examples

diff:
-// Copyright 2019 Pixar Animation Studios
-//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at


message: Fix samples for preview.3

diff:
# Changes in usd-unitysdk for Unity
-## [1.0.0-preview.2] - 2019-03-15
+
+## [1.0.0-preview.3] - 2019-03-15
### Changes
- CI integration
+- Samples Fixes
## [1.0.0-preview.1] - 2019-02-20
### Changes

diff:
@@ -241,7 +241,7 @@ namespace Unity.Formats.USD.Examples {
var detailNormalMapPath = "/Model/Materials/SimpleMat/DetailNormalMap";
var detailMaskPath = "/Model/Materials/SimpleMat/DetailMask";
- var textureFilePath = @"Assets/Samples/USD/1.0.0-preview.1/ImportMaterials/Textures/";
+ var textureFilePath = @"Assets/Samples/USD/1.0.0-preview.3/ImportMaterials/Textures/";
var cube = new CubeSample();
cube.size = 7;

diff:
@@ -373,7 +373,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: c6b9422ac107eb345ba17ec50a29caf4, type: 3}
m_Name:
m_EditorClassIdentifier:
- m_usdFile: Samples/USD/1.0.0-preview.1/ImportMesh/mesh.usd
+ m_usdFile: Samples/USD/1.0.0-preview.3/ImportMesh/mesh.usd
m_material: {fileID: 2100000, guid: 5a4399f9b4f4d774dbc7921697839d7e, type: 2}
m_usdTime: 0
m_changeHandedness: 0

diff:
@@ -21113,7 +21113,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 4aff4833db8e2b14aa0f0b6071c41e5b, type: 3}
m_Name:
m_EditorClassIdentifier:
- m_usdFile: Assets/Samples/USD/1.0.0-preview.1/UsdTimelinePlayable/mesh.usd
+ m_usdFile: Assets/Samples/USD/1.0.0-preview.3/UsdTimelinePlayable/mesh.usd
m_projectAssetPath: Assets/
m_usdRootPath: /
m_usdTimeOffset: 0

original_message: Fix samples for preview.3


message: Switch to stable upm-ci

diff:
@@ -19,7 +19,7 @@ pack:
image: buildfarm/mac:stable
flavor: m1.mac
commands:
- - npm install upm-ci-utils -g --registry https://api.bintray.com/npm/unity/unity-npm
+ - npm install upm-ci-utils@stable -g --registry https://api.bintray.com/npm/unity/unity-npm
- upm-ci package pack --package-path package/com.unity.formats.usd
artifacts:
packages:
@@ -35,7 +35,7 @@ test_{{ platform.name }}_{{ editor.version }}:
image: {{ platform.image }}
flavor: {{ platform.flavor}}
commands:
- - npm install upm-ci-utils -g --registry https://api.bintray.com/npm/unity/unity-npm
+ - npm install upm-ci-utils@stable -g --registry https://api.bintray.com/npm/unity/unity-npm
- upm-ci package test --unity-version {{ editor.version }} --package-path package/com.unity.formats.usd
artifacts:
logs:
@@ -83,7 +83,7 @@ run_preview_verified_staging:
flavor: m1.large
name: Runner
commands:
- - npm install upm-ci-utils -g --registry https://api.bintray.com/npm/unity/unity-npm
+ - npm install upm-ci-utils@stable -g --registry https://api.bintray.com/npm/unity/unity-npm
- upm-ci package publish --package-path package/com.unity.formats.usd
triggers:
tags:

original_message: Switch to stable upm-ci


message: SetHideFlags now ORs HideFlags rather than setting. Changed defaults work better with default Usd Asset import settings.

diff:
@@ -24,21 +24,38 @@ namespace Unity.Formats.USD.Examples {
public HideFlags hideFlagsSettings = HideFlags.DontSave;
+ public enum Operation {Overwrite, LogicalAND, LogicalOR, LogicalXOR}
+ [Tooltip("The logical operator to use when setting the flags. In most cases Or will produce expectd results")]
+ public Operation operation = Operation.LogicalOR;
+
public void PostProcessHierarchy(PrimMap primMap, SceneImportOptions sceneImportOptions) {
InitRegex();
foreach (KeyValuePair<SdfPath, GameObject> kvp in primMap) {
if (!IsMatch(kvp.Key)) continue;
GameObject go = kvp.Value;
+ switch (operation) {
+ case Operation.LogicalAND:
+ go.hideFlags &= hideFlagsSettings;
+ return;
+ case Operation.LogicalOR:
+ go.hideFlags |= hideFlagsSettings;
+ return;
+ case Operation.LogicalXOR:
+ go.hideFlags ^= hideFlagsSettings;
+ return;
+ default:
go.hideFlags = hideFlagsSettings;
+ return;
+ }
}
}
void Reset() {
- matchExpression = "/";
+ matchExpression = "/World";
isNot = true;
matchType = EMatchType.Wildcard;
- compareAgainst = ECompareAgainst.UsdName;
+ compareAgainst = ECompareAgainst.UsdPath;
}
}
}


message: Changed SetHideFlags to use Regex rather than wildcard and correctly continue iterating over primmap.

diff:
@@ -37,24 +37,24 @@ namespace Unity.Formats.USD.Examples {
switch (operation) {
case Operation.LogicalAND:
go.hideFlags &= hideFlagsSettings;
- return;
+ continue;
case Operation.LogicalOR:
go.hideFlags |= hideFlagsSettings;
- return;
+ continue;
case Operation.LogicalXOR:
go.hideFlags ^= hideFlagsSettings;
- return;
+ continue;
default:
go.hideFlags = hideFlagsSettings;
- return;
+ continue;
}
}
}
void Reset() {
- matchExpression = "/World";
- isNot = true;
- matchType = EMatchType.Wildcard;
+ matchExpression = @"^\/.+";
+ isNot = false;
+ matchType = EMatchType.Regex;
compareAgainst = ECompareAgainst.UsdPath;
}
}


message: Fixed tooltip typo

diff:
@@ -25,7 +25,7 @@ namespace Unity.Formats.USD.Examples {
public HideFlags hideFlagsSettings = HideFlags.DontSave;
public enum Operation {Overwrite, LogicalAND, LogicalOR, LogicalXOR}
- [Tooltip("The logical operator to use when setting the flags. In most cases Or will produce expectd results")]
+ [Tooltip("The logical operator to use when setting the flags. In most cases Or will produce expected results")]
public Operation operation = Operation.LogicalOR;
public void PostProcessHierarchy(PrimMap primMap, SceneImportOptions sceneImportOptions) {

original_message: Fixed tooltip typo


message: JIRA: Writing to an invalid prim crashes Unity

diff:
@@ -782,7 +782,8 @@ namespace USD.NET
// TODO(jcowles): there is a potential issue here if the cache gets out of sync with the
// underlying USD scene. The correct fix is to listen for change processing events and
// clear the cache accordingly.
- if (!m_primMap.TryGetValue(path, out prim))
+ prim = GetUsdPrim(path);
+ if (!prim.IsValid())
{
if (WriteMode == WriteModes.Define)
{
@@ -850,14 +851,20 @@ namespace USD.NET
private pxr.UsdPrim GetUsdPrim(SdfPath path)
{
UsdPrim prim;
- lock (m_stageLock) {
- if (!m_primMap.TryGetValue(path, out prim) || !prim.IsValid())
+ lock (m_stageLock)
+ {
+ var has = m_primMap.TryGetValue(path, out prim);
+ if (!has || !prim.IsValid())
{
prim = Stage.GetPrimAtPath(path);
if (prim.IsValid())
{
m_primMap[path] = prim;
}
+ else if (has)
+ {
+ m_primMap.Remove(path);
+ }
}
}
return prim;

diff:
@@ -310,5 +310,17 @@ namespace USD.NET.Tests
Assert.IsTrue(prim.IsValid() == false);
stage.Dispose();
}
+
+ [Test]
+ public void WritingInvalidPrims_ShouldNotCrash()
+ {
+ var stage = pxr.UsdStage.CreateInMemory(UsdStage.InitialLoadSet.LoadNone);
+ var scene = Scene.Open(stage);
+ scene.Write("/Foo", new CubeSample());
+ scene.Stage.RemovePrim(scene.GetSdfPath("/Foo"));
+ scene.Write("/Foo", new CubeSample());
+
+ Assert.IsTrue(scene.GetPrimAtPath("/Foo").IsValid());
+ }
}
}

original_message: JIRA: USDU-168 Writing to an invalid prim crashes Unity (#256)


message: Implemented `From<Color` for `Background`

diff:
@@ -7,3 +7,9 @@ pub enum Background {
Color(Color),
// TODO: Add gradient and image variants
}
+
+impl From<Color> for Background {
+ fn from(color: Color) -> Self {
+ Background::Color(color)
+ }
+}
\ No newline at end of file

diff:
@@ -37,6 +37,13 @@ impl Color {
}
}
+ /// Creates a [`Color`] from its RGB components.
+ ///
+ /// [`Color`]: struct.Color.html
+ pub fn from_rgb(r: f32, g: f32, b: f32) -> Color {
+ Color { r, g, b, a: 1.0 }
+ }
+
/// Converts the [`Color`] into its linear values.
///
/// [`Color`]: struct.Color.html

diff:
use iced::{
- button, image, Align, Application, Background, Button, Color, Column,
+ button, image, Align, Application, Button, Color, Column,
Command, Container, Element, Image, Length, Row, Settings, Text,
};
@@ -225,7 +225,7 @@ impl From<reqwest::Error> for Error {
fn button<'a>(state: &'a mut button::State, text: &str) -> Button<'a, Message> {
Button::new(state, Text::new(text).color(Color::WHITE))
- .background(Background::Color([0.11, 0.42, 0.87].into()))
+ .background(Color::from_rgb(0.11, 0.42, 0.87).into())
.border_radius(10)
.padding(10)
}

diff:
use iced::{
- button, scrollable, text_input, Align, Application, Background, Button,
+ button, scrollable, text_input, Align, Application, Button,
Checkbox, Color, Column, Command, Container, Element, Font,
HorizontalAlignment, Length, Row, Scrollable, Settings, Text, TextInput,
};
@@ -332,7 +332,7 @@ impl Task {
.on_press(TaskMessage::Delete)
.padding(10)
.border_radius(5)
- .background(Background::Color([0.8, 0.2, 0.2].into())),
+ .background(Color::from_rgb(0.8, 0.2, 0.2).into()),
)
.into()
}
@@ -361,7 +361,7 @@ impl Controls {
let label = Text::new(label).size(16).width(Length::Shrink);
let button = if filter == current_filter {
Button::new(state, label.color(Color::WHITE))
- .background(Background::Color([0.2, 0.2, 0.7].into()))
+ .background(Color::from_rgb(0.2, 0.2, 0.7).into())
} else {
Button::new(state, label)
};

diff:
use iced::{
- button, scrollable, slider, text_input, Background, Button, Checkbox,
+ button, scrollable, slider, text_input, Button, Checkbox,
Color, Column, Container, Element, HorizontalAlignment, Image, Length,
Radio, Row, Sandbox, Scrollable, Settings, Slider, Text, TextInput,
};
@@ -684,24 +684,17 @@ fn primary_button<'a, Message>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
- button(state, label).background(Background::Color(Color {
- r: 0.11,
- g: 0.42,
- b: 0.87,
- a: 1.0,
- }))
+ button(state, label)
+ .background(Color::from_rgb(0.11, 0.42, 0.87).into())
}
fn secondary_button<'a, Message>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
- button(state, label).background(Background::Color(Color {
- r: 0.4,
- g: 0.4,
- b: 0.4,
- a: 1.0,
- }))
+ button(state, label)
+ .background(Color::from_rgb(0.4, 0.4, 0.4).into())
+
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

diff:
@@ -29,7 +29,7 @@ impl slider::Renderer for Renderer {
width: bounds.width,
height: 2.0,
},
- background: Background::Color([0.6, 0.6, 0.6].into()),
+ background: Color::from_rgb(0.6, 0.6, 0.6).into(),
border_radius: 0,
},
Primitive::Quad {
@@ -57,7 +57,7 @@ impl slider::Renderer for Renderer {
width: HANDLE_WIDTH + 2.0,
height: HANDLE_HEIGHT + 2.0,
},
- background: Background::Color([0.6, 0.6, 0.6].into()),
+ background: Color::from_rgb(0.6, 0.6, 0.6).into(),
border_radius: 5,
},
Primitive::Quad {

license: MIT License

repo: iced-rs/iced


message: Implemented SVG support in iced_wgpu.

diff:
@@ -28,11 +28,13 @@ mod image;
mod primitive;
mod quad;
mod renderer;
+mod svg;
mod text;
mod transformation;
pub(crate) use crate::image::Image;
pub(crate) use quad::Quad;
+pub(crate) use svg::Svg;
pub(crate) use transformation::Transformation;
pub use primitive::Primitive;

diff:
@@ -3,6 +3,8 @@ use iced_native::{
VerticalAlignment,
};
+use crate::svg;
+
/// A rendering primitive.
#[derive(Debug, Clone)]
pub enum Primitive {
@@ -46,6 +48,13 @@ pub enum Primitive {
/// The bounds of the image
bounds: Rectangle,
},
+ /// A svg icon primitive
+ Svg {
+ /// The handle of the icon
+ handle: svg::Handle,
+ /// The bounds of the icon
+ bounds: Rectangle,
+ },
/// A clip primitive
Clip {
/// The bounds of the clip

diff:
-use crate::{quad, text, Image, Primitive, Quad, Transformation};
+use crate::{quad, text, Image, Primitive, Quad, Svg, Transformation};
use iced_native::{
renderer::{Debugger, Windowed},
Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget,
@@ -23,6 +23,7 @@ pub struct Renderer {
queue: Queue,
quad_pipeline: quad::Pipeline,
image_pipeline: crate::image::Pipeline,
+ svg_pipeline: crate::svg::Pipeline,
text_pipeline: text::Pipeline,
}
@@ -31,6 +32,7 @@ struct Layer<'a> {
offset: Vector<u32>,
quads: Vec<Quad>,
images: Vec<Image>,
+ svgs: Vec<Svg>,
text: Vec<wgpu_glyph::Section<'a>>,
}
@@ -41,6 +43,7 @@ impl<'a> Layer<'a> {
offset,
quads: Vec::new(),
images: Vec::new(),
+ svgs: Vec::new(),
text: Vec::new(),
}
}
@@ -64,12 +67,14 @@ impl Renderer {
let text_pipeline = text::Pipeline::new(&mut device);
let quad_pipeline = quad::Pipeline::new(&mut device);
let image_pipeline = crate::image::Pipeline::new(&mut device);
+ let svg_pipeline = crate::svg::Pipeline::new(&mut device);
Self {
device,
queue,
quad_pipeline,
image_pipeline,
+ svg_pipeline,
text_pipeline,
}
}
@@ -128,6 +133,7 @@ impl Renderer {
self.queue.submit(&[encoder.finish()]);
self.image_pipeline.trim_cache();
+ self.svg_pipeline.trim_cache();
*mouse_cursor
}
@@ -237,6 +243,13 @@ impl Renderer {
scale: [bounds.width, bounds.height],
});
}
+ Primitive::Svg { handle, bounds } => {
+ layer.svgs.push(Svg {
+ handle: handle.clone(),
+ position: [bounds.x, bounds.y],
+ scale: [bounds.width, bounds.height],
+ })
+ },
Primitive::Clip {
bounds,
offset,
@@ -345,6 +358,25 @@ impl Renderer {
);
}
+ if layer.svgs.len() > 0 {
+ let translated = transformation
+ * Transformation::translate(
+ -(layer.offset.x as f32),
+ -(layer.offset.y as f32),
+ );
+
+ self.svg_pipeline.draw(
+ &mut self.device,
+ encoder,
+ &layer.svgs,
+ translated,
+ bounds,
+ target,
+ (dpi * 96.0) as u16,
+ (dpi * 20.0) as u16,
+ );
+ }
+
if layer.text.len() > 0 {
for text in layer.text.iter() {
// Target physical coordinates directly to avoid blurry text

original_message: Implemented SVG support in iced_wgpu.


message: Added an `Icon` widget to native.

diff:
@@ -24,6 +24,7 @@ pub mod button;
pub mod checkbox;
pub mod column;
pub mod container;
+pub mod icon;
pub mod image;
pub mod radio;
pub mod row;
@@ -41,6 +42,8 @@ pub use column::Column;
#[doc(no_inline)]
pub use container::Container;
#[doc(no_inline)]
+pub use icon::Icon;
+#[doc(no_inline)]
pub use image::Image;
#[doc(no_inline)]
pub use radio::Radio;

diff:
+//! Display an icon.
+use crate::{layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget};
+
+use std::hash::Hash;
+use std::path::{Path, PathBuf};
+
+/// A simple icon_loader widget.
+#[derive(Debug, Clone)]
+pub struct Icon {
+ path: PathBuf,
+ size: Length,
+}
+
+impl Icon {
+ /// Create a new [`Icon`] from the file at `path`.
+ ///
+ /// [`Icon`]: struct.Icon.html
+ pub fn new(path: impl Into<PathBuf>) -> Self {
+ Icon {
+ path: path.into(),
+ size: Length::Fill,
+ }
+ }
+
+ /// Sets the size of the [`Icon`].
+ ///
+ /// [`Icon`]: struct.Icon.html
+ pub fn size(mut self, size: Length) -> Self {
+ self.size = size;
+ self
+ }
+}
+
+impl<Message, Renderer> Widget<Message, Renderer> for Icon
+where
+ Renderer: self::Renderer,
+{
+ fn width(&self) -> Length {
+ self.size
+ }
+
+ fn height(&self) -> Length {
+ self.size
+ }
+
+ fn layout(&self, _: &Renderer, limits: &layout::Limits) -> layout::Node {
+ let mut size = limits.width(self.size).height(self.size).max();
+
+ if size.width > size.height {
+ size.width = size.height;
+ } else if size.width < size.height {
+ size.height = size.width;
+ }
+
+ layout::Node::new(size)
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ layout: Layout<'_>,
+ _cursor_position: Point,
+ ) -> Renderer::Output {
+ let bounds = layout.bounds();
+
+ renderer.draw(
+ bounds,
+ self.path.as_path(),
+ )
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ self.size.hash(state);
+ }
+}
+
+/// The renderer of an [`Icon`].
+///
+/// Your [renderer] will need to implement this trait before being
+/// able to use [`Icon`] in your [`UserInterface`].
+///
+/// [`Icon`]: struct.Icon.html
+/// [renderer]: ../../renderer/index.html
+/// [`UserInterface`]: ../../struct.UserInterface.html
+pub trait Renderer: crate::Renderer {
+ /// Draws an [`Icon`].
+ ///
+ /// [`Icon`]: struct.Icon.html
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ path: &Path,
+ ) -> Self::Output;
+}
+
+impl<'a, Message, Renderer> From<Icon> for Element<'a, Message, Renderer>
+where
+ Renderer: self::Renderer,
+{
+ fn from(icon: Icon) -> Element<'a, Message, Renderer> {
+ Element::new(icon)
+ }
+}

diff:
@@ -80,11 +80,16 @@ pub mod widget {
pub use iced_winit::image::{Handle, Image};
}
+ pub mod icon {
+ //! Display icons in your user interface.
+ pub use iced_winit::icon::Icon;
+ }
+
pub use iced_winit::{Checkbox, Radio, Text};
#[doc(no_inline)]
pub use {
- button::Button, image::Image, scrollable::Scrollable, slider::Slider,
+ button::Button, icon::Icon, image::Image, scrollable::Scrollable, slider::Slider,
text_input::TextInput,
};

diff:
@@ -3,8 +3,6 @@ use iced_native::{
VerticalAlignment,
};
-use crate::svg;
-
/// A rendering primitive.
#[derive(Debug, Clone)]
pub enum Primitive {
@@ -50,8 +48,8 @@ pub enum Primitive {
},
/// A svg icon primitive
Svg {
- /// The handle of the icon
- handle: svg::Handle,
+ /// The path of the icon
+ handle: crate::svg::Handle,
/// The bounds of the icon
bounds: Rectangle,
},

diff:
mod button;
mod checkbox;
mod column;
+mod icon;
mod image;
mod radio;
mod row;

diff:
+use crate::{svg::Handle, Primitive, Renderer};
+use iced_native::{
+ icon, MouseCursor, Rectangle,
+};
+use std::path::Path;
+
+impl icon::Renderer for Renderer {
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ path: &Path,
+ ) -> Self::Output {
+ (
+ Primitive::Svg {
+ handle: Handle::from_path(path),
+ bounds,
+ },
+ MouseCursor::OutOfBounds,
+ )
+ }
+}
\ No newline at end of file

diff:
@@ -4,12 +4,13 @@ use iced_native::{Hasher, Rectangle};
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
+ fmt::Debug,
hash::{Hash, Hasher as _},
mem,
path::PathBuf,
rc::Rc,
+ u32,
};
-use std::fmt::Debug;
#[derive(Debug)]

original_message: Added an `Icon` widget to native.


message: Ran cargo_fmt over changed files.

diff:
//! Display an icon.
-use crate::{layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget};
+use crate::{
+ layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
+};
-use std::hash::Hash;
-use std::path::{Path, PathBuf};
+use std::{
+ hash::Hash,
+ path::{Path, PathBuf},
+};
/// A simple icon_loader widget.
#[derive(Debug, Clone)]
@@ -63,10 +67,7 @@ where
) -> Renderer::Output {
let bounds = layout.bounds();
- renderer.draw(
- bounds,
- self.path.as_path(),
- )
+ renderer.draw(bounds, self.path.as_path())
}
fn hash_layout(&self, state: &mut Hasher) {
@@ -86,11 +87,7 @@ pub trait Renderer: crate::Renderer {
/// Draws an [`Icon`].
///
/// [`Icon`]: struct.Icon.html
- fn draw(
- &mut self,
- bounds: Rectangle,
- path: &Path,
- ) -> Self::Output;
+ fn draw(&mut self, bounds: Rectangle, path: &Path) -> Self::Output;
}
impl<'a, Message, Renderer> From<Icon> for Element<'a, Message, Renderer>

diff:
@@ -89,8 +89,8 @@ pub mod widget {
#[doc(no_inline)]
pub use {
- button::Button, icon::Icon, image::Image, scrollable::Scrollable, slider::Slider,
- text_input::TextInput,
+ button::Button, icon::Icon, image::Image, scrollable::Scrollable,
+ slider::Slider, text_input::TextInput,
};
/// A container that distributes its contents vertically.

diff:
@@ -243,13 +243,11 @@ impl Renderer {
scale: [bounds.width, bounds.height],
});
}
- Primitive::Svg { handle, bounds } => {
- layer.svgs.push(Svg {
+ Primitive::Svg { handle, bounds } => layer.svgs.push(Svg {
handle: handle.clone(),
position: [bounds.x, bounds.y],
scale: [bounds.width, bounds.height],
- })
- },
+ }),
Primitive::Clip {
bounds,
offset,

diff:
use crate::{svg::Handle, Primitive, Renderer};
-use iced_native::{
- icon, MouseCursor, Rectangle,
-};
+use iced_native::{icon, MouseCursor, Rectangle};
use std::path::Path;
impl icon::Renderer for Renderer {
- fn draw(
- &mut self,
- bounds: Rectangle,
- path: &Path,
- ) -> Self::Output {
+ fn draw(&mut self, bounds: Rectangle, path: &Path) -> Self::Output {
(
Primitive::Svg {
handle: Handle::from_path(path),

diff:
@@ -12,7 +12,6 @@ use std::{
u32,
};
-
#[derive(Debug)]
pub struct Pipeline {
cache: RefCell<Cache>,
@@ -220,9 +219,10 @@ impl Pipeline {
opt.usvg.dpi = handle.dpi as f64;
opt.usvg.font_size = handle.font_size as f64;
- let mem = match resvg::usvg::Tree::from_file(&handle.path, &opt.usvg) {
+ let mem =
+ match resvg::usvg::Tree::from_file(&handle.path, &opt.usvg) {
Ok(tree) => Memory::Host { tree },
- Err(_) => Memory::Invalid
+ Err(_) => Memory::Invalid,
};
let _ = self.cache.borrow_mut().insert(&handle, mem);
@@ -265,12 +265,14 @@ impl Pipeline {
self.load(&handle);
- if let Some(texture) = self
- .cache
- .borrow_mut()
- .get(&handle)
- .unwrap()
- .upload(device, encoder, &self.texture_layout, svg.scale[0] as u32, svg.scale[1] as u32)
+ if let Some(texture) =
+ self.cache.borrow_mut().get(&handle).unwrap().upload(
+ device,
+ encoder,
+ &self.texture_layout,
+ svg.scale[0] as u32,
+ svg.scale[1] as u32,
+ )
{
let instance_buffer = device
.create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
@@ -409,12 +411,8 @@ impl Hash for Handle {
}
enum Memory {
- Host {
- tree: resvg::usvg::Tree,
- },
- Device {
- bind_group: Rc<wgpu::BindGroup>,
- },
+ Host { tree: resvg::usvg::Tree },
+ Device { bind_group: Rc<wgpu::BindGroup> },
NotFound,
Invalid,
}
@@ -426,7 +424,7 @@ impl Memory {
encoder: &mut wgpu::CommandEncoder,
texture_layout: &wgpu::BindGroupLayout,
width: u32,
- height: u32
+ height: u32,
) -> Option<Rc<wgpu::BindGroup>> {
match self {
Memory::Host { tree } => {
@@ -447,10 +445,17 @@ impl Memory {
| wgpu::TextureUsage::SAMPLED,
});
- let mut canvas = resvg::raqote::DrawTarget::new(width as i32, height as i32);
+ let mut canvas =
+ resvg::raqote::DrawTarget::new(width as i32, height as i32);
let opt = resvg::Options::default();
- let screen_size = resvg::ScreenSize::new(width, height).unwrap();
- resvg::backend_raqote::render_to_canvas(tree, &opt, screen_size, &mut canvas);
+ let screen_size =
+ resvg::ScreenSize::new(width, height).unwrap();
+ resvg::backend_raqote::render_to_canvas(
+ tree,
+ &opt,
+ screen_size,
+ &mut canvas,
+ );
let slice = canvas.get_data();
let temp_buf = device
.create_buffer_mapped(
@@ -506,7 +511,10 @@ impl Memory {
}
impl Debug for Memory {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ fn fmt(
+ &self,
+ f: &mut std::fmt::Formatter<'_>,
+ ) -> Result<(), std::fmt::Error> {
match self {
Memory::Host { .. } => write!(f, "Memory::Host"),
Memory::Device { .. } => write!(f, "Memory::Device"),

diff:
-pub use font_kit::error::SelectionError as LoadError;
-pub use font_kit::family_name::FamilyName as Family;
+pub use font_kit::{
+ error::SelectionError as LoadError, family_name::FamilyName as Family,
+};
pub struct Source {
raw: font_kit::source::SystemSource,

original_message: Ran cargo_fmt over changed files.


message: Improved dpi handling

diff:
@@ -370,8 +370,7 @@ impl Renderer {
translated,
bounds,
target,
- (dpi * 96.0) as u16,
- (dpi * 20.0) as u16,
+ dpi,
);
}

diff:
@@ -216,8 +216,6 @@ impl Pipeline {
let mut opt = resvg::Options::default();
opt.usvg.path = Some(handle.path.clone());
- opt.usvg.dpi = handle.dpi as f64;
- opt.usvg.font_size = handle.font_size as f64;
let mem =
match resvg::usvg::Tree::from_file(&handle.path, &opt.usvg) {
@@ -237,8 +235,7 @@ impl Pipeline {
transformation: Transformation,
bounds: Rectangle<u32>,
target: &wgpu::TextureView,
- dpi: u16,
- font_size: u16,
+ dpi: f32,
) {
let uniforms_buffer = device
.create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
@@ -259,19 +256,15 @@ impl Pipeline {
//
// [1]: https://github.com/nical/guillotiere
for svg in svgs {
- let mut handle = svg.handle.clone();
- handle.set_dpi(dpi);
- handle.set_font_size(font_size);
-
- self.load(&handle);
+ self.load(&svg.handle);
if let Some(texture) =
- self.cache.borrow_mut().get(&handle).unwrap().upload(
+ self.cache.borrow_mut().get(&svg.handle).unwrap().upload(
device,
encoder,
&self.texture_layout,
- svg.scale[0] as u32,
- svg.scale[1] as u32,
+ (svg.scale[0] * dpi) as u32,
+ (svg.scale[1] * dpi) as u32,
)
{
let instance_buffer = device
@@ -339,8 +332,6 @@ impl Pipeline {
pub struct Handle {
id: u64,
path: PathBuf,
- dpi: u16,
- font_size: u16,
}
impl Handle {
@@ -356,57 +347,19 @@ impl Handle {
/// [`Handle`]: struct.Handle.html
pub fn from_path<T: Into<PathBuf>>(path: T) -> Handle {
let path = path.into();
- let dpi = 96;
- let font_size = 20;
let mut hasher = Hasher::default();
path.hash(&mut hasher);
- dpi.hash(&mut hasher);
- font_size.hash(&mut hasher);
Self {
id: hasher.finish(),
path,
- dpi,
- font_size,
- }
- }
-
- fn set_dpi(&mut self, dpi: u16) {
- if self.dpi == dpi {
- return;
- }
-
- self.dpi = dpi;
-
- let mut hasher = Hasher::default();
- self.path.hash(&mut hasher);
- self.dpi.hash(&mut hasher);
- self.font_size.hash(&mut hasher);
-
- self.id = hasher.finish();
- }
-
- fn set_font_size(&mut self, font_size: u16) {
- if self.font_size == font_size {
- return;
}
-
- self.font_size = font_size;
-
- let mut hasher = Hasher::default();
- self.path.hash(&mut hasher);
- self.dpi.hash(&mut hasher);
- self.font_size.hash(&mut hasher);
-
- self.id = hasher.finish();
}
}
impl Hash for Handle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
- self.dpi.hash(state);
- self.font_size.hash(state);
}
}
@@ -428,6 +381,8 @@ impl Memory {
) -> Option<Rc<wgpu::BindGroup>> {
match self {
Memory::Host { tree } => {
+ println!("{} {}", width, height);
+
let extent = wgpu::Extent3d {
width,
height,

original_message: Improved dpi handling


message:
Renamed `Icon` widget to `Svg` and gave it separate width and height.
The aspect ratio is now preserved like in the `Image` widget.

diff:
@@ -24,7 +24,7 @@ pub mod button;
pub mod checkbox;
pub mod column;
pub mod container;
-pub mod icon;
+pub mod svg;
pub mod image;
pub mod radio;
pub mod row;
@@ -42,8 +42,6 @@ pub use column::Column;
#[doc(no_inline)]
pub use container::Container;
#[doc(no_inline)]
-pub use icon::Icon;
-#[doc(no_inline)]
pub use image::Image;
#[doc(no_inline)]
pub use radio::Radio;
@@ -54,6 +52,8 @@ pub use scrollable::Scrollable;
#[doc(no_inline)]
pub use slider::Slider;
#[doc(no_inline)]
+pub use svg::Svg;
+#[doc(no_inline)]
pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;

diff:
-//! Display an icon.
-use crate::{
- image, layout, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
-};
-
-use std::{
- hash::Hash,
- path::{Path, PathBuf},
-};
-
-/// A simple icon_loader widget.
-#[derive(Debug, Clone)]
-pub struct Icon {
- handle: image::Handle,
- size: Length,
-}
-
-impl Icon {
- /// Create a new [`Icon`] from the file at `path`.
- ///
- /// [`Icon`]: struct.Icon.html
- pub fn new(path: impl Into<PathBuf>) -> Self {
- Icon {
- handle: image::Handle::from_path(path),
- size: Length::Fill,
- }
- }
-
- /// Sets the size of the [`Icon`].
- ///
- /// [`Icon`]: struct.Icon.html
- pub fn size(mut self, size: Length) -> Self {
- self.size = size;
- self
- }
-}
-
-impl<Message, Renderer> Widget<Message, Renderer> for Icon
-where
- Renderer: image::Renderer,
-{
- fn width(&self) -> Length {
- self.size
- }
-
- fn height(&self) -> Length {
- self.size
- }
-
- fn layout(&self, _: &Renderer, limits: &layout::Limits) -> layout::Node {
- let mut size = limits.width(self.size).height(self.size).max();
-
- if size.width > size.height {
- size.width = size.height;
- } else if size.width < size.height {
- size.height = size.width;
- }
-
- layout::Node::new(size)
- }
-
- fn draw(
- &self,
- renderer: &mut Renderer,
- layout: Layout<'_>,
- _cursor_position: Point,
- ) -> Renderer::Output {
- renderer.draw(self.handle.clone(), layout)
- }
-
- fn hash_layout(&self, state: &mut Hasher) {
- self.size.hash(state);
- }
-}
-
-impl<'a, Message, Renderer> From<Icon> for Element<'a, Message, Renderer>
-where
- Renderer: image::Renderer,
-{
- fn from(icon: Icon) -> Element<'a, Message, Renderer> {
- Element::new(icon)
- }
-}

diff:
+//! Display an icon.
+use crate::{
+ image, layout, Element, Hasher, Layout, Length, Point, Size, Widget,
+};
+
+use std::{
+ hash::Hash,
+ path::PathBuf,
+};
+
+/// A simple icon_loader widget.
+#[derive(Debug, Clone)]
+pub struct Svg {
+ handle: image::Handle,
+ width: Length,
+ height: Length,
+}
+
+impl Svg {
+ /// Create a new [`Svg`] from the file at `path`.
+ ///
+ /// [`Svg`]: struct.Svg.html
+ pub fn new(path: impl Into<PathBuf>) -> Self {
+ Svg {
+ handle: image::Handle::from_path(path),
+ width: Length::Fill,
+ height: Length::Fill,
+ }
+ }
+
+ /// Sets the width of the [`Svg`].
+ ///
+ /// [`Svg`]: struct.Svg.html
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the height of the [`Svg`].
+ ///
+ /// [`Svg`]: struct.Svg.html
+ pub fn height(mut self, height: Length) -> Self {
+ self.height = height;
+ self
+ }
+}
+
+impl<Message, Renderer> Widget<Message, Renderer> for Svg
+where
+ Renderer: image::Renderer,
+{
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
+ let (width, height) = renderer.dimensions(&self.handle);
+
+ let aspect_ratio = width as f32 / height as f32;
+
+ let mut size = limits
+ .width(self.width)
+ .height(self.height)
+ .max();
+
+ let viewport_aspect_ratio = size.width / size.height;
+
+ if viewport_aspect_ratio > aspect_ratio {
+ size.width = width as f32 * size.height / height as f32;
+ } else {
+ size.height = height as f32 * size.width / width as f32;
+ }
+
+ layout::Node::new(size)
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ layout: Layout<'_>,
+ _cursor_position: Point,
+ ) -> Renderer::Output {
+ renderer.draw(self.handle.clone(), layout)
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ self.width.hash(state);
+ self.height.hash(state);
+ }
+}
+
+impl<'a, Message, Renderer> From<Svg> for Element<'a, Message, Renderer>
+where
+ Renderer: image::Renderer,
+{
+ fn from(icon: Svg) -> Element<'a, Message, Renderer> {
+ Element::new(icon)
+ }
+}

diff:
@@ -80,17 +80,17 @@ pub mod widget {
pub use iced_winit::image::{Handle, Image};
}
- pub mod icon {
+ pub mod svg {
//! Display icons in your user interface.
- pub use iced_winit::icon::Icon;
+ pub use iced_winit::svg::Svg;
}
pub use iced_winit::{Checkbox, Radio, Text};
#[doc(no_inline)]
pub use {
- button::Button, icon::Icon, image::Image, scrollable::Scrollable,
- slider::Slider, text_input::TextInput,
+ button::Button, image::Image, scrollable::Scrollable,
+ slider::Slider, svg::Svg, text_input::TextInput,
};
/// A container that distributes its contents vertically.


message: Implemented a texture atlas for images and svgs.

diff:
@@ -18,7 +18,7 @@ use std::{
/// ```
///
/// <img src="https://github.com/hecrj/iced/blob/9712b319bb7a32848001b96bd84977430f14b623/examples/resources/ferris.png?raw=true" width="300">
-#[derive(Debug)]
+#[derive(Debug, Hash)]
pub struct Image {
handle: Handle,
width: Length,

diff:
@@ -21,6 +21,7 @@ raw-window-handle = "0.3"
glam = "0.8"
font-kit = "0.4"
log = "0.4"
+guillotiere = "0.4"
[dependencies.image]
version = "0.22"

diff:
use iced_native::image;
use std::{
collections::{HashMap, HashSet},
- rc::Rc,
+ fmt,
};
+use guillotiere::{Allocation, AtlasAllocator, Size};
-#[derive(Debug)]
pub enum Memory {
Host(::image::ImageBuffer<::image::Bgra<u8>, Vec<u8>>),
- Device {
- bind_group: Rc<wgpu::BindGroup>,
- width: u32,
- height: u32,
- },
+ Device(Allocation),
NotFound,
Invalid,
}
@@ -20,21 +16,37 @@ impl Memory {
pub fn dimensions(&self) -> (u32, u32) {
match self {
Memory::Host(image) => image.dimensions(),
- Memory::Device { width, height, .. } => (*width, *height),
+ Memory::Device(allocation) => {
+ let size = &allocation.rectangle.size();
+ (size.width as u32, size.height as u32)
+ },
Memory::NotFound => (1, 1),
Memory::Invalid => (1, 1),
}
}
+}
- pub fn upload(
- &mut self,
- device: &wgpu::Device,
- encoder: &mut wgpu::CommandEncoder,
- texture_layout: &wgpu::BindGroupLayout,
- ) -> Option<Rc<wgpu::BindGroup>> {
- match self {
- Memory::Host(image) => {
- let (width, height) = image.dimensions();
+pub struct Cache {
+ allocator: AtlasAllocator,
+ atlas: wgpu::Texture,
+ map: HashMap<u64, Memory>,
+ hits: HashSet<u64>,
+}
+
+impl fmt::Debug for Cache {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt.debug_struct("Vector Cache")
+ .field("allocator", &String::from("AtlasAllocator"))
+ .field("atlas", &self.atlas)
+ .field("map", &String::from("HashMap<u64, Memory>"))
+ .field("hits", &self.hits)
+ .finish()
+ }
+}
+
+impl Cache {
+ pub fn new(device: &wgpu::Device) -> Self {
+ let (width, height) = (1000, 1000);
let extent = wgpu::Extent3d {
width,
@@ -42,7 +54,7 @@ impl Memory {
depth: 1,
};
- let texture = device.create_texture(&wgpu::TextureDescriptor {
+ let atlas = device.create_texture(&wgpu::TextureDescriptor {
size: extent,
array_layer_count: 1,
mip_level_count: 1,
@@ -50,9 +62,130 @@ impl Memory {
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
usage: wgpu::TextureUsage::COPY_DST
+ | wgpu::TextureUsage::COPY_SRC
| wgpu::TextureUsage::SAMPLED,
});
+ Self {
+ allocator: AtlasAllocator::new(Size::new(width as i32, height as i32)),
+ atlas,
+ map: HashMap::new(),
+ hits: HashSet::new(),
+ }
+ }
+
+ pub fn load(&mut self, handle: &image::Handle) -> &mut Memory {
+ if self.contains(handle) {
+ return self.get(handle).unwrap();
+ }
+
+ let memory = match handle.data() {
+ image::Data::Path(path) => {
+ if let Ok(image) = ::image::open(path) {
+ Memory::Host(image.to_bgra())
+ } else {
+ Memory::NotFound
+ }
+ }
+ image::Data::Bytes(bytes) => {
+ if let Ok(image) = ::image::load_from_memory(&bytes) {
+ Memory::Host(image.to_bgra())
+ } else {
+ Memory::Invalid
+ }
+ }
+ };
+
+ self.insert(handle, memory);
+ self.get(handle).unwrap()
+ }
+
+ pub fn atlas_size(&self) -> guillotiere::Size {
+ self.allocator.size()
+ }
+
+ pub fn upload(
+ &mut self,
+ handle: &image::Handle,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ ) -> &Memory {
+ let _ = self.load(handle);
+
+ let memory = self.map.get_mut(&handle.id()).unwrap();
+
+ if let Memory::Host(image) = memory {
+ let (width, height) = image.dimensions();
+ let size = Size::new(width as i32, height as i32);
+
+ let old_atlas_size = self.allocator.size();
+ let allocation;
+
+ loop {
+ if let Some(a) = self.allocator.allocate(size) {
+ allocation = a;
+ break;
+ }
+
+ self.allocator.grow(self.allocator.size() * 2);
+ }
+
+ let new_atlas_size = self.allocator.size();
+
+ if new_atlas_size != old_atlas_size {
+ let new_atlas = device.create_texture(&wgpu::TextureDescriptor {
+ size: wgpu::Extent3d {
+ width: new_atlas_size.width as u32,
+ height: new_atlas_size.height as u32,
+ depth: 1,
+ },
+ array_layer_count: 1,
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ usage: wgpu::TextureUsage::COPY_DST
+ | wgpu::TextureUsage::COPY_SRC
+ | wgpu::TextureUsage::SAMPLED,
+ });
+
+ encoder.copy_texture_to_texture(
+ wgpu::TextureCopyView {
+ texture: &self.atlas,
+ array_layer: 0,
+ mip_level: 0,
+ origin: wgpu::Origin3d {
+ x: 0.0,
+ y: 0.0,
+ z: 0.0,
+ },
+ },
+ wgpu::TextureCopyView {
+ texture: &new_atlas,
+ array_layer: 0,
+ mip_level: 0,
+ origin: wgpu::Origin3d {
+ x: 0.0,
+ y: 0.0,
+ z: 0.0,
+ },
+ },
+ wgpu::Extent3d {
+ width: old_atlas_size.width as u32,
+ height: old_atlas_size.height as u32,
+ depth: 1,
+ }
+ );
+
+ self.atlas = new_atlas;
+ }
+
+ let extent = wgpu::Extent3d {
+ width,
+ height,
+ depth: 1,
+ };
+
let temp_buf = {
let flat_samples = image.as_flat_samples();
let slice = flat_samples.as_slice();
@@ -69,93 +202,51 @@ impl Memory {
wgpu::BufferCopyView {
buffer: &temp_buf,
offset: 0,
- row_pitch: 4 * width as u32,
- image_height: height as u32,
+ row_pitch: 4 * width,
+ image_height: height,
},
wgpu::TextureCopyView {
- texture: &texture,
+ texture: &self.atlas,
array_layer: 0,
mip_level: 0,
origin: wgpu::Origin3d {
- x: 0.0,
- y: 0.0,
+ x: allocation.rectangle.min.x as f32,
+ y: allocation.rectangle.min.y as f32,
z: 0.0,
},
},
extent,
);
- let bind_group =
+ *memory = Memory::Device(allocation);
+ }
+
+ memory
+ }
+
+ pub fn atlas(&self, device: &wgpu::Device, texture_layout: &wgpu::BindGroupLayout) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: texture_layout,
bindings: &[wgpu::Binding {
binding: 0,
resource: wgpu::BindingResource::TextureView(
- &texture.create_default_view(),
+ &self.atlas.create_default_view(),
),
}],
- });
-
- let bind_group = Rc::new(bind_group);
-
- *self = Memory::Device {
- bind_group: bind_group.clone(),
- width,
- height,
- };
-
- Some(bind_group)
- }
- Memory::Device { bind_group, .. } => Some(bind_group.clone()),
- Memory::NotFound => None,
- Memory::Invalid => None,
- }
- }
-}
-
-#[derive(Debug)]
-pub struct Cache {
- map: HashMap<u64, Memory>,
- hits: HashSet<u64>,
-}
-
-impl Cache {
- pub fn new() -> Self {
- Self {
- map: HashMap::new(),
- hits: HashSet::new(),
- }
+ })
}
- pub fn load(&mut self, handle: &image::Handle) -> &mut Memory {
- if self.contains(handle) {
- return self.get(handle).unwrap();
- }
+ pub fn trim(&mut self) {
+ let hits = &self.hits;
- let memory = match handle.data() {
- image::Data::Path(path) => {
- if let Ok(image) = ::image::open(path) {
- Memory::Host(image.to_bgra())
- } else {
- Memory::NotFound
- }
- }
- image::Data::Bytes(bytes) => {
- if let Ok(image) = ::image::load_from_memory(&bytes) {
- Memory::Host(image.to_bgra())
- } else {
- Memory::Invalid
+ for (id, mem) in &mut self.map {
+ if let Memory::Device(allocation) = mem {
+ if !hits.contains(&id) {
+ self.allocator.deallocate(allocation.id);
}
}
- };
-
- self.insert(handle, memory);
- self.get(handle).unwrap()
}
- pub fn trim(&mut self) {
- let hits = &self.hits;
-
self.map.retain(|k, _| hits.contains(k));
self.hits.clear();
}

diff:
use iced_native::svg;
use std::{
collections::{HashMap, HashSet},
- rc::Rc,
+ fmt,
};
+use guillotiere::{Allocation, AtlasAllocator, Size};
pub enum Svg {
Loaded { tree: resvg::usvg::Tree },
@@ -22,27 +23,63 @@ impl Svg {
}
}
-impl std::fmt::Debug for Svg {
+impl fmt::Debug for Svg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Svg")
}
}
-#[derive(Debug)]
pub struct Cache {
+ allocator: AtlasAllocator,
+ atlas: wgpu::Texture,
svgs: HashMap<u64, Svg>,
- rasterized: HashMap<(u64, u32, u32), Rc<wgpu::BindGroup>>,
+ rasterized: HashMap<(u64, u32, u32), Allocation>,
svg_hits: HashSet<u64>,
rasterized_hits: HashSet<(u64, u32, u32)>,
}
+impl fmt::Debug for Cache {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt.debug_struct("Vector Cache")
+ .field("allocator", &String::from("AtlasAllocator"))
+ .field("atlas", &self.atlas)
+ .field("svgs", &self.svgs)
+ .field("rasterized", &String::from("HashMap<(u64, u32, u32), Allocation>"))
+ .field("svg_hits", &self.svg_hits)
+ .field("rasterized_hits", &self.rasterized_hits)
+ .finish()
+ }
+}
+
impl Cache {
- pub fn new() -> Self {
+ pub fn new(device: &wgpu::Device) -> Self {
+ let (width, height) = (512, 512);
+
+ let extent = wgpu::Extent3d {
+ width,
+ height,
+ depth: 1,
+ };
+
+ let atlas = device.create_texture(&wgpu::TextureDescriptor {
+ size: extent,
+ array_layer_count: 1,
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ usage: wgpu::TextureUsage::COPY_DST
+ | wgpu::TextureUsage::COPY_SRC
+ | wgpu::TextureUsage::SAMPLED,
+ });
+
Self {
svgs: HashMap::new(),
rasterized: HashMap::new(),
svg_hits: HashSet::new(),
rasterized_hits: HashSet::new(),
+ allocator: AtlasAllocator::new(Size::new(width as i32, height as i32)),
+ atlas,
}
}
@@ -62,6 +99,10 @@ impl Cache {
self.svgs.get(&handle.id()).unwrap()
}
+ pub fn atlas_size(&self) -> guillotiere::Size {
+ self.allocator.size()
+ }
+
pub fn upload(
&mut self,
handle: &svg::Handle,
@@ -69,8 +110,7 @@ impl Cache {
scale: f32,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
- texture_layout: &wgpu::BindGroupLayout,
- ) -> Option<Rc<wgpu::BindGroup>> {
+ ) -> Option<&Allocation> {
let id = handle.id();
let (width, height) = (
@@ -82,36 +122,88 @@ impl Cache {
// We currently rerasterize the SVG when its size changes. This is slow
// as heck. A GPU rasterizer like `pathfinder` may perform better.
// It would be cool to be able to smooth resize the `svg` example.
- if let Some(bind_group) = self.rasterized.get(&(id, width, height)) {
+ if self.rasterized.get(&(id, width, height)).is_some() {
let _ = self.svg_hits.insert(id);
let _ = self.rasterized_hits.insert((id, width, height));
- return Some(bind_group.clone());
+ return self.rasterized.get(&(id, width, height));
}
- match self.load(handle) {
+ let _ = self.load(handle);
+
+ match self.svgs.get(&handle.id()).unwrap() {
Svg::Loaded { tree } => {
if width == 0 || height == 0 {
return None;
}
- let extent = wgpu::Extent3d {
- width,
- height,
- depth: 1,
- };
+ let size = Size::new(width as i32, height as i32);
+ let old_atlas_size = self.allocator.size();
+ let allocation;
- let texture = device.create_texture(&wgpu::TextureDescriptor {
- size: extent,
+ loop {
+ if let Some(a) = self.allocator.allocate(size) {
+ allocation = a;
+ break;
+ }
+
+ self.allocator.grow(self.allocator.size() * 2);
+ }
+
+ let new_atlas_size = self.allocator.size();
+
+ if new_atlas_size != old_atlas_size {
+ let new_atlas = device.create_texture(&wgpu::TextureDescriptor {
+ size: wgpu::Extent3d {
+ width: new_atlas_size.width as u32,
+ height: new_atlas_size.height as u32,
+ depth: 1,
+ },
array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
usage: wgpu::TextureUsage::COPY_DST
+ | wgpu::TextureUsage::COPY_SRC
| wgpu::TextureUsage::SAMPLED,
});
+ encoder.copy_texture_to_texture(
+ wgpu::TextureCopyView {
+ texture: &self.atlas,
+ array_layer: 0,
+ mip_level: 0,
+ origin: wgpu::Origin3d {
+ x: 0.0,
+ y: 0.0,
+ z: 0.0,
+ },
+ },
+ wgpu::TextureCopyView {
+ texture: &new_atlas,
+ array_layer: 0,
+ mip_level: 0,
+ origin: wgpu::Origin3d {
+ x: 0.0,
+ y: 0.0,
+ z: 0.0,
+ },
+ },
+ wgpu::Extent3d {
+ width: old_atlas_size.width as u32,
+ height: old_atlas_size.height as u32,
+ depth: 1,
+ }
+ );
+
+ self.atlas = new_atlas;
+ }
+
+ // TODO: Optimize!
+ // We currently rerasterize the SVG when its size changes. This is slow
+ // as heck. A GPU rasterizer like `pathfinder` may perform better.
+ // It would be cool to be able to smooth resize the `svg` example.
let temp_buf = {
let screen_size =
resvg::ScreenSize::new(width, height).unwrap();
@@ -122,7 +214,7 @@ impl Cache {
);
resvg::backend_raqote::render_to_canvas(
- &tree,
+ tree,
&resvg::Options::default(),
screen_size,
&mut canvas,
@@ -146,48 +238,56 @@ impl Cache {
image_height: height as u32,
},
wgpu::TextureCopyView {
- texture: &texture,
+ texture: &self.atlas,
array_layer: 0,
mip_level: 0,
origin: wgpu::Origin3d {
- x: 0.0,
- y: 0.0,
+ x: allocation.rectangle.min.x as f32,
+ y: allocation.rectangle.min.y as f32,
z: 0.0,
},
},
- extent,
+ wgpu::Extent3d {
+ width,
+ height,
+ depth: 1,
+ },
);
- let bind_group =
+ let _ = self.svg_hits.insert(id);
+ let _ = self.rasterized_hits.insert((id, width, height));
+ let _ = self
+ .rasterized
+ .insert((id, width, height), allocation);
+
+ self.rasterized.get(&(id, width, height))
+ }
+ Svg::NotFound => None
+ }
+ }
+
+ pub fn atlas(&self, device: &wgpu::Device, texture_layout: &wgpu::BindGroupLayout) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: texture_layout,
bindings: &[wgpu::Binding {
binding: 0,
resource: wgpu::BindingResource::TextureView(
- &texture.create_default_view(),
+ &self.atlas.create_default_view(),
),
}],
- });
-
- let bind_group = Rc::new(bind_group);
-
- let _ = self
- .rasterized
- .insert((id, width, height), bind_group.clone());
-
- let _ = self.svg_hits.insert(id);
- let _ = self.rasterized_hits.insert((id, width, height));
-
- Some(bind_group)
- }
- Svg::NotFound => None,
- }
+ })
}
pub fn trim(&mut self) {
let svg_hits = &self.svg_hits;
let rasterized_hits = &self.rasterized_hits;
+ for (k, alloc) in &mut self.rasterized {
+ if !rasterized_hits.contains(&k) {
+ self.allocator.deallocate(alloc.id);
+ }
+ }
+
self.svgs.retain(|k, _| svg_hits.contains(k));
self.rasterized.retain(|k, _| rasterized_hits.contains(k));
self.svg_hits.clear();

diff:
+#version 450
+
+layout(location = 0) in vec2 v_Pos;
+layout(location = 1) in vec2 i_Pos;
+layout(location = 2) in vec2 i_Scale;
+layout(location = 3) in vec2 i_Atlas_Pos;
+layout(location = 4) in vec2 i_Atlas_Scale;
+
+layout (set = 0, binding = 0) uniform Globals {
+ mat4 u_Transform;
+};
+
+layout(location = 0) out vec2 o_Uv;
+
+void main() {
+ o_Uv = v_Pos * i_Atlas_Scale + i_Atlas_Pos;
+
+ mat4 i_Transform = mat4(
+ vec4(i_Scale.x, 0.0, 0.0, 0.0),
+ vec4(0.0, i_Scale.y, 0.0, 0.0),
+ vec4(0.0, 0.0, 1.0, 0.0),
+ vec4(i_Pos, 0.0, 1.0)
+ );
+
+ gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0);
+}

diff: Binary files a/wgpu/src/shader/image.vert.spv and b/wgpu/src/shader/image.vert.spv differ


message: Some small debug changes

diff:
@@ -8,7 +8,10 @@ use debug_stub_derive::*;
#[derive(DebugStub)]
pub enum Memory {
Host(::image::ImageBuffer<::image::Bgra<u8>, Vec<u8>>),
- Device(#[debug_stub="ReplacementValue"]Allocation),
+ Device(
+ #[debug_stub="ReplacementValue"]
+ Allocation
+ ),
NotFound,
Invalid,
}

diff:
use iced_native::svg;
use std::{
collections::{HashMap, HashSet},
- fmt,
};
use guillotiere::{Allocation, AtlasAllocator, Size};
use debug_stub_derive::*;
+#[derive(DebugStub)]
pub enum Svg {
- Loaded { tree: resvg::usvg::Tree },
+ Loaded(
+ #[debug_stub="ReplacementValue"]
+ resvg::usvg::Tree
+ ),
NotFound,
}
impl Svg {
pub fn viewport_dimensions(&self) -> (u32, u32) {
match self {
- Svg::Loaded { tree } => {
+ Svg::Loaded(tree) => {
let size = tree.svg_node().size;
(size.width() as u32, size.height() as u32)
@@ -24,12 +27,6 @@ impl Svg {
}
}
-impl fmt::Debug for Svg {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "Svg")
- }
-}
-
#[derive(DebugStub)]
pub struct Cache {
svgs: HashMap<u64, Svg>,
@@ -57,7 +54,7 @@ impl Cache {
let opt = resvg::Options::default();
let svg = match resvg::usvg::Tree::from_file(handle.path(), &opt.usvg) {
- Ok(tree) => Svg::Loaded { tree },
+ Ok(tree) => Svg::Loaded(tree),
Err(_) => Svg::NotFound,
};
@@ -96,7 +93,7 @@ impl Cache {
let _ = self.load(handle);
match self.svgs.get(&handle.id()).unwrap() {
- Svg::Loaded { tree } => {
+ Svg::Loaded(tree) => {
if width == 0 || height == 0 {
return None;
}

original_message: Some small debug changes


message: Fixed texture bleeding

diff:
@@ -356,10 +356,10 @@ impl Pipeline {
#[cfg(any(feature = "image", feature = "svg"))]
for (index, image) in instances.iter().enumerate() {
if let Some(rec) = recs.get(&index) {
- let x = rec.min.x as f32 / atlas_width;
- let y = rec.min.y as f32 / atlas_height;
- let w = (rec.size().width - 1) as f32 / atlas_width;
- let h = (rec.size().height - 1) as f32 / atlas_height;
+ let x = (rec.min.x as f32 + 0.5) / atlas_width;
+ let y = (rec.min.y as f32 + 0.5) / atlas_height;
+ let w = (rec.size().width as f32 - 0.5) / atlas_width;
+ let h = (rec.size().height as f32 - 0.5) / atlas_height;
let instance_buffer = device
.create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)

original_message: Fixed texture bleeding


message: Use array of atlases instead of one growing indefinitely.

diff:
@@ -9,15 +9,15 @@ use crate::image::raster::Memory;
use crate::Transformation;
use iced_native::{image, svg, Rectangle};
-use std::mem;
+use std::{collections::{HashMap, HashSet}, mem};
#[cfg(any(feature = "image", feature = "svg"))]
-use std::{cell::RefCell, collections::HashMap};
+use std::cell::RefCell;
-use guillotiere::{AtlasAllocator, Size};
+use guillotiere::{Allocation, AtlasAllocator, Size};
use debug_stub_derive::*;
-#[derive(DebugStub)]
+#[derive(Debug)]
pub struct Pipeline {
#[cfg(feature = "image")]
raster_cache: RefCell<raster::Cache>,
@@ -31,9 +31,7 @@ pub struct Pipeline {
instances: wgpu::Buffer,
constants: wgpu::BindGroup,
texture_layout: wgpu::BindGroupLayout,
- #[debug_stub="ReplacementValue"]
- allocator: AtlasAllocator,
- atlas: wgpu::Texture,
+ atlas_array: AtlasArray,
}
impl Pipeline {
@@ -193,6 +191,11 @@ impl Pipeline {
format: wgpu::VertexFormat::Float2,
offset: 4 * 6,
},
+ wgpu::VertexAttributeDescriptor {
+ shader_location: 5,
+ format: wgpu::VertexFormat::Float,
+ offset: 4 * 8,
+ },
],
},
],
@@ -214,25 +217,7 @@ impl Pipeline {
usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
});
- let (width, height) = (512, 512);
-
- let extent = wgpu::Extent3d {
- width,
- height,
- depth: 1,
- };
-
- let atlas = device.create_texture(&wgpu::TextureDescriptor {
- size: extent,
- array_layer_count: 1,
- mip_level_count: 1,
- sample_count: 1,
- dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Bgra8UnormSrgb,
- usage: wgpu::TextureUsage::COPY_DST
- | wgpu::TextureUsage::COPY_SRC
- | wgpu::TextureUsage::SAMPLED,
- });
+ let atlas_array = AtlasArray::new(1, device);
Pipeline {
#[cfg(feature = "image")]
@@ -248,8 +233,7 @@ impl Pipeline {
instances,
constants: constant_bind_group,
texture_layout,
- allocator: AtlasAllocator::new(Size::new(width as i32, height as i32)),
- atlas,
+ atlas_array,
}
}
@@ -303,14 +287,13 @@ impl Pipeline {
{
let mut raster_cache = self.raster_cache.borrow_mut();
- if let Memory::Device(allocation) = raster_cache.upload(
- handle,
+ if let Memory::Device { layer, allocation } = raster_cache.upload(
+ _handle,
device,
encoder,
- &mut self.allocator,
- &mut self.atlas
+ &mut self.atlas_array,
) {
- let rec = allocation.rectangle;
+ let rec = (*layer, allocation.rectangle);
let _ = recs.insert(index, rec);
}
@@ -322,16 +305,15 @@ impl Pipeline {
let mut vector_cache = self.vector_cache.borrow_mut();
// Upload rasterized svg to texture atlas
- if let Some(allocation) = vector_cache.upload(
+ if let Some((layer, allocation)) = vector_cache.upload(
_handle,
image.scale,
_scale,
device,
encoder,
- &mut self.allocator,
- &mut self.atlas,
+ &mut self.atlas_array,
) {
- let rec = allocation.rectangle;
+ let rec = (*layer, allocation.rectangle);
let _ = recs.insert(index, rec);
}
@@ -340,26 +322,23 @@ impl Pipeline {
}
}
- let atlas_width = self.allocator.size().width as f32;
- let atlas_height = self.allocator.size().height as f32;
-
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.texture_layout,
bindings: &[wgpu::Binding {
binding: 0,
resource: wgpu::BindingResource::TextureView(
- &self.atlas.create_default_view(),
+ &self.atlas_array.texture().create_default_view(),
),
}],
});
#[cfg(any(feature = "image", feature = "svg"))]
for (index, image) in instances.iter().enumerate() {
- if let Some(rec) = recs.get(&index) {
- let x = (rec.min.x as f32 + 0.5) / atlas_width;
- let y = (rec.min.y as f32 + 0.5) / atlas_height;
- let w = (rec.size().width as f32 - 0.5) / atlas_width;
- let h = (rec.size().height as f32 - 0.5) / atlas_height;
+ if let Some((layer, rec)) = recs.get(&index) {
+ let x = (rec.min.x as f32 + 0.5) / (ATLAS_SIZE as f32);
+ let y = (rec.min.y as f32 + 0.5) / (ATLAS_SIZE as f32);
+ let w = (rec.size().width as f32 - 0.5) / (ATLAS_SIZE as f32);
+ let h = (rec.size().height as f32 - 0.5) / (ATLAS_SIZE as f32);
let instance_buffer = device
.create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
@@ -367,7 +346,8 @@ impl Pipeline {
_position: image.position,
_scale: image.scale,
_position_in_atlas: [x, y],
- _scale_in_atlas: [w, h]
+ _scale_in_atlas: [w, h],
+ _layer: *layer as f32,
}]);
encoder.copy_buffer_to_buffer(
@@ -424,10 +404,10 @@ impl Pipeline {
pub fn trim_cache(&mut self) {
#[cfg(feature = "image")]
- self.raster_cache.borrow_mut().trim(&mut self.allocator);
+ self.raster_cache.borrow_mut().trim(&mut self.atlas_array);
#[cfg(feature = "svg")]
- self.vector_cache.borrow_mut().trim(&mut self.allocator);
+ self.vector_cache.borrow_mut().trim(&mut self.atlas_array);
}
}
@@ -442,6 +422,177 @@ pub enum Handle {
Vector(svg::Handle),
}
+#[derive(DebugStub)]
+pub struct AtlasArray {
+ texture: wgpu::Texture,
+ #[debug_stub="ReplacementValue"]
+ allocators: HashMap<u32, AtlasAllocator>,
+ layers_without_allocators: HashSet<u32>,
+ size: u32,
+}
+
+impl AtlasArray {
+ pub fn new(array_size: u32, device: &wgpu::Device) -> Self {
+ let (width, height) = (ATLAS_SIZE, ATLAS_SIZE);
+
+ let extent = wgpu::Extent3d {
+ width,
+ height,
+ depth: 1,
+ };
+
+ let texture = device.create_texture(&wgpu::TextureDescriptor {
+ size: extent,
+ array_layer_count: array_size,
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ usage: wgpu::TextureUsage::COPY_DST
+ | wgpu::TextureUsage::COPY_SRC
+ | wgpu::TextureUsage::SAMPLED,
+ });
+
+ AtlasArray {
+ texture,
+ allocators: HashMap::new(),
+ layers_without_allocators: HashSet::new(),
+ size: array_size,
+ }
+ }
+
+ pub fn texture(&self) -> &wgpu::Texture {
+ &self.texture
+ }
+
+ pub fn allocate(&mut self, size: Size) -> Option<(u32, Allocation)> {
+ for layer in 0..self.size {
+ if self.layers_without_allocators.contains(&layer) {
+ continue;
+ }
+
+ let allocator = self.allocators.entry(layer)
+ .or_insert_with(|| AtlasAllocator::new(
+ Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32)
+ ));
+
+ if let Some(a) = allocator.allocate(size.clone()) {
+ return Some((layer, a));
+ }
+ }
+
+ None
+ }
+
+ pub fn deallocate(&mut self, layer: u32, allocation: &Allocation) {
+ if let Some(allocator) = self.allocators.get_mut(&layer) {
+ allocator.deallocate(allocation.id);
+ }
+ }
+
+ pub fn upload<T: Copy + 'static>(
+ &mut self,
+ data: &[T],
+ layer: u32,
+ allocation: &guillotiere::Allocation,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ ) {
+ let size = allocation.rectangle.size();
+ let (width, height) = (size.width as u32, size.height as u32);
+
+ let extent = wgpu::Extent3d {
+ width,
+ height,
+ depth: 1,
+ };
+
+ let temp_buf = device
+ .create_buffer_mapped(
+ data.len(),
+ wgpu::BufferUsage::COPY_SRC,
+ )
+ .fill_from_slice(data);
+
+ encoder.copy_buffer_to_texture(
+ wgpu::BufferCopyView {
+ buffer: &temp_buf,
+ offset: 0,
+ row_pitch: 4 * width,
+ image_height: height,
+ },
+ wgpu::TextureCopyView {
+ texture: &self.texture,
+ array_layer: layer as u32,
+ mip_level: 0,
+ origin: wgpu::Origin3d {
+ x: allocation.rectangle.min.x as f32,
+ y: allocation.rectangle.min.y as f32,
+ z: 0.0,
+ },
+ },
+ extent,
+ );
+ }
+
+ pub fn grow(
+ &mut self,
+ grow_by: u32,
+ device: &wgpu::Device,
+ encoder: &mut wgpu::CommandEncoder,
+ ) {
+ let old_atlas_array_size = self.size;
+
+ let new_texture = device.create_texture(&wgpu::TextureDescriptor {
+ size: wgpu::Extent3d {
+ width: ATLAS_SIZE,
+ height: ATLAS_SIZE,
+ depth: 1,
+ },
+ array_layer_count: old_atlas_array_size + grow_by,
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ usage: wgpu::TextureUsage::COPY_DST
+ | wgpu::TextureUsage::COPY_SRC
+ | wgpu::TextureUsage::SAMPLED,
+ });
+
+ for i in 0..old_atlas_array_size {
+ encoder.copy_texture_to_texture(
+ wgpu::TextureCopyView {
+ texture: &self.texture,
+ array_layer: i,
+ mip_level: 0,
+ origin: wgpu::Origin3d {
+ x: 0.0,
+ y: 0.0,
+ z: 0.0,
+ },
+ },
+ wgpu::TextureCopyView {
+ texture: &new_texture,
+ array_layer: i,
+ mip_level: 0,
+ origin: wgpu::Origin3d {
+ x: 0.0,
+ y: 0.0,
+ z: 0.0,
+ },
+ },
+ wgpu::Extent3d {
+ width: ATLAS_SIZE,
+ height: ATLAS_SIZE,
+ depth: 1,
+ }
+ );
+ }
+
+ self.texture = new_texture;
+ }
+}
+
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Vertex {
@@ -465,6 +616,8 @@ const QUAD_VERTS: [Vertex; 4] = [
},
];
+const ATLAS_SIZE: u32 = 8192;
+
#[repr(C)]
#[derive(Clone, Copy)]
struct Instance {
@@ -472,6 +625,7 @@ struct Instance {
_scale: [f32; 2],
_position_in_atlas: [f32; 2],
_scale_in_atlas: [f32; 2],
+ _layer: f32,
}
#[repr(C)]

diff:
+use crate::image::AtlasArray;
use iced_native::image;
use std::{
collections::{HashMap, HashSet},
};
-use guillotiere::{Allocation, AtlasAllocator, Size};
+use guillotiere::{Allocation, Size};
use debug_stub_derive::*;
#[derive(DebugStub)]
pub enum Memory {
Host(::image::ImageBuffer<::image::Bgra<u8>, Vec<u8>>),
- Device(
+ Device {
+ layer: u32,
#[debug_stub="ReplacementValue"]
- Allocation
- ),
+ allocation: Allocation,
+ },
NotFound,
Invalid,
}
@@ -20,7 +22,7 @@ impl Memory {
pub fn dimensions(&self) -> (u32, u32) {
match self {
Memory::Host(image) => image.dimensions(),
- Memory::Device(allocation) => {
+ Memory::Device { allocation, .. } => {
let size = &allocation.rectangle.size();
(size.width as u32, size.height as u32)
},
@@ -75,8 +77,7 @@ impl Cache {
handle: &image::Handle,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
- allocator: &mut AtlasAllocator,
- atlas: &mut wgpu::Texture,
+ atlas_array: &mut AtlasArray,
) -> &Memory {
let _ = self.load(handle);
@@ -86,119 +87,29 @@ impl Cache {
let (width, height) = image.dimensions();
let size = Size::new(width as i32, height as i32);
- let old_atlas_size = allocator.size();
- let allocation;
-
- loop {
- if let Some(a) = allocator.allocate(size) {
- allocation = a;
- break;
- }
-
- allocator.grow(allocator.size() * 2);
- }
-
- let new_atlas_size = allocator.size();
-
- if new_atlas_size != old_atlas_size {
- let new_atlas = device.create_texture(&wgpu::TextureDescriptor {
- size: wgpu::Extent3d {
- width: new_atlas_size.width as u32,
- height: new_atlas_size.height as u32,
- depth: 1,
- },
- array_layer_count: 1,
- mip_level_count: 1,
- sample_count: 1,
- dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Bgra8UnormSrgb,
- usage: wgpu::TextureUsage::COPY_DST
- | wgpu::TextureUsage::COPY_SRC
- | wgpu::TextureUsage::SAMPLED,
+ let (layer, allocation) = atlas_array.allocate(size).unwrap_or_else(|| {
+ atlas_array.grow(1, device, encoder);
+ atlas_array.allocate(size).unwrap()
});
- encoder.copy_texture_to_texture(
- wgpu::TextureCopyView {
- texture: atlas,
- array_layer: 0,
- mip_level: 0,
- origin: wgpu::Origin3d {
- x: 0.0,
- y: 0.0,
- z: 0.0,
- },
- },
- wgpu::TextureCopyView {
- texture: &new_atlas,
- array_layer: 0,
- mip_level: 0,
- origin: wgpu::Origin3d {
- x: 0.0,
- y: 0.0,
- z: 0.0,
- },
- },
- wgpu::Extent3d {
- width: old_atlas_size.width as u32,
- height: old_atlas_size.height as u32,
- depth: 1,
- }
- );
-
- *atlas = new_atlas;
- }
-
- let extent = wgpu::Extent3d {
- width,
- height,
- depth: 1,
- };
-
- let temp_buf = {
let flat_samples = image.as_flat_samples();
let slice = flat_samples.as_slice();
- device
- .create_buffer_mapped(
- slice.len(),
- wgpu::BufferUsage::COPY_SRC,
- )
- .fill_from_slice(slice)
- };
-
- encoder.copy_buffer_to_texture(
- wgpu::BufferCopyView {
- buffer: &temp_buf,
- offset: 0,
- row_pitch: 4 * width,
- image_height: height,
- },
- wgpu::TextureCopyView {
- texture: atlas,
- array_layer: 0,
- mip_level: 0,
- origin: wgpu::Origin3d {
- x: allocation.rectangle.min.x as f32,
- y: allocation.rectangle.min.y as f32,
- z: 0.0,
- },
- },
- extent,
- );
+ atlas_array.upload(slice, layer, &allocation, device, encoder);
- *memory = Memory::Device(allocation);
+ *memory = Memory::Device { layer, allocation };
}
memory
}
- pub fn trim(&mut self, allocator: &mut AtlasAllocator) {
+ pub fn trim(&mut self, atlas_array: &mut AtlasArray) {
let hits = &self.hits;
- for (id, mem) in &mut self.map {
- if let Memory::Device(allocation) = mem {
+ for (id, mem) in &self.map {
+ if let Memory::Device { layer, allocation } = mem {
if !hits.contains(&id) {
- allocator.deallocate(allocation.id);
+ atlas_array.deallocate(*layer, allocation);
}
}
}

diff:
+use crate::image::AtlasArray;
use iced_native::svg;
use std::{
collections::{HashMap, HashSet},
};
-use guillotiere::{Allocation, AtlasAllocator, Size};
+use guillotiere::{Allocation, Size};
use debug_stub_derive::*;
#[derive(DebugStub)]
@@ -31,7 +32,7 @@ impl Svg {
pub struct Cache {
svgs: HashMap<u64, Svg>,
#[debug_stub="ReplacementValue"]
- rasterized: HashMap<(u64, u32, u32), Allocation>,
+ rasterized: HashMap<(u64, u32, u32), (u32, Allocation)>,
svg_hits: HashSet<u64>,
rasterized_hits: HashSet<(u64, u32, u32)>,
}
@@ -69,9 +70,8 @@ impl Cache {
scale: f32,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
- allocator: &mut AtlasAllocator,
- atlas: &mut wgpu::Texture,
- ) -> Option<&Allocation> {
+ atlas_array: &mut AtlasArray,
+ ) -> Option<&(u32, Allocation)> {
let id = handle.id();
let (width, height) = (
@@ -99,73 +99,16 @@ impl Cache {
}
let size = Size::new(width as i32, height as i32);
- let old_atlas_size = allocator.size();
- let allocation;
-
- loop {
- if let Some(a) = allocator.allocate(size) {
- allocation = a;
- break;
- }
-
- allocator.grow(allocator.size() * 2);
- }
-
- let new_atlas_size = allocator.size();
-
- if new_atlas_size != old_atlas_size {
- let new_atlas = device.create_texture(&wgpu::TextureDescriptor {
- size: wgpu::Extent3d {
- width: new_atlas_size.width as u32,
- height: new_atlas_size.height as u32,
- depth: 1,
- },
- array_layer_count: 1,
- mip_level_count: 1,
- sample_count: 1,
- dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Bgra8UnormSrgb,
- usage: wgpu::TextureUsage::COPY_DST
- | wgpu::TextureUsage::COPY_SRC
- | wgpu::TextureUsage::SAMPLED,
- });
- encoder.copy_texture_to_texture(
- wgpu::TextureCopyView {
- texture: atlas,
- array_layer: 0,
- mip_level: 0,
- origin: wgpu::Origin3d {
- x: 0.0,
- y: 0.0,
- z: 0.0,
- },
- },
- wgpu::TextureCopyView {
- texture: &new_atlas,
- array_layer: 0,
- mip_level: 0,
- origin: wgpu::Origin3d {
- x: 0.0,
- y: 0.0,
- z: 0.0,
- },
- },
- wgpu::Extent3d {
- width: old_atlas_size.width as u32,
- height: old_atlas_size.height as u32,
- depth: 1,
- }
- );
-
- *atlas = new_atlas;
- }
+ let (layer, allocation) = atlas_array.allocate(size).unwrap_or_else(|| {
+ atlas_array.grow(1, device, encoder);
+ atlas_array.allocate(size).unwrap()
+ });
// TODO: Optimize!
// We currently rerasterize the SVG when its size changes. This is slow
// as heck. A GPU rasterizer like `pathfinder` may perform better.
// It would be cool to be able to smooth resize the `svg` example.
- let temp_buf = {
let screen_size =
resvg::ScreenSize::new(width, height).unwrap();
@@ -183,43 +126,13 @@ impl Cache {
let slice = canvas.get_data();
- device
- .create_buffer_mapped(
- slice.len(),
- wgpu::BufferUsage::COPY_SRC,
- )
- .fill_from_slice(slice)
- };
-
- encoder.copy_buffer_to_texture(
- wgpu::BufferCopyView {
- buffer: &temp_buf,
- offset: 0,
- row_pitch: 4 * width as u32,
- image_height: height as u32,
- },
- wgpu::TextureCopyView {
- texture: atlas,
- array_layer: 0,
- mip_level: 0,
- origin: wgpu::Origin3d {
- x: allocation.rectangle.min.x as f32,
- y: allocation.rectangle.min.y as f32,
- z: 0.0,
- },
- },
- wgpu::Extent3d {
- width,
- height,
- depth: 1,
- },
- );
+ atlas_array.upload(slice, layer, &allocation, device, encoder);
let _ = self.svg_hits.insert(id);
let _ = self.rasterized_hits.insert((id, width, height));
let _ = self
.rasterized
- .insert((id, width, height), allocation);
+ .insert((id, width, height), (layer, allocation));
self.rasterized.get(&(id, width, height))
}
@@ -227,13 +140,13 @@ impl Cache {
}
}
- pub fn trim(&mut self, allocator: &mut AtlasAllocator) {
+ pub fn trim(&mut self, atlas_array: &mut AtlasArray) {
let svg_hits = &self.svg_hits;
let rasterized_hits = &self.rasterized_hits;
- for (k, alloc) in &mut self.rasterized {
- if !rasterized_hits.contains(&k) {
- allocator.deallocate(alloc.id);
+ for (k, (layer, allocation)) in &self.rasterized {
+ if !rasterized_hits.contains(k) {
+ atlas_array.deallocate(*layer, allocation);
}
}

diff:
#version 450
-layout(location = 0) in vec2 v_Uv;
+layout(location = 0) in vec3 v_Uv;
layout(set = 0, binding = 1) uniform sampler u_Sampler;
-layout(set = 1, binding = 0) uniform texture2D u_Texture;
+layout(set = 1, binding = 0) uniform texture2DArray u_Texture;
layout(location = 0) out vec4 o_Color;
void main() {
- o_Color = texture(sampler2D(u_Texture, u_Sampler), v_Uv);
+ o_Color = texture(sampler2DArray(u_Texture, u_Sampler), v_Uv);
}

diff:
@@ -5,15 +5,16 @@ layout(location = 1) in vec2 i_Pos;
layout(location = 2) in vec2 i_Scale;
layout(location = 3) in vec2 i_Atlas_Pos;
layout(location = 4) in vec2 i_Atlas_Scale;
+layout(location = 5) in float i_Layer;
layout (set = 0, binding = 0) uniform Globals {
mat4 u_Transform;
};
-layout(location = 0) out vec2 o_Uv;
+layout(location = 0) out vec3 o_Uv;
void main() {
- o_Uv = v_Pos * i_Atlas_Scale + i_Atlas_Pos;
+ o_Uv = vec3(v_Pos * i_Atlas_Scale + i_Atlas_Pos, i_Layer);
mat4 i_Transform = mat4(
vec4(i_Scale.x, 0.0, 0.0, 0.0),