Browse Source

Merge branch 'master' into fixes/Warnings/CS0436

pull/9987/head
workgroupengineering 3 years ago
committed by GitHub
parent
commit
3fc3c35919
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .editorconfig
  2. 1
      Avalonia.Desktop.slnf
  3. 7
      Avalonia.sln
  4. 1
      build/Base.props
  5. 15
      build/SharpDX.props
  6. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  7. 5
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  8. 113
      native/Avalonia.Native/src/OSX/PlatformSettings.mm
  9. 4
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  10. 20
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  11. 6
      native/Avalonia.Native/src/OSX/WindowImpl.h
  12. 18
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  13. 1
      native/Avalonia.Native/src/OSX/common.h
  14. 10
      native/Avalonia.Native/src/OSX/main.mm
  15. 3
      samples/ControlCatalog.NetCore/Program.cs
  16. 5
      samples/ControlCatalog/MainView.xaml
  17. 75
      samples/ControlCatalog/MainView.xaml.cs
  18. 4
      samples/ControlCatalog/Pages/CarouselPage.xaml
  19. 22
      samples/ControlCatalog/Pages/DataGridPage.xaml
  20. 13
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  21. 19
      samples/ControlCatalog/Pages/DialogsPage.xaml
  22. 118
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  23. 4
      samples/ControlCatalog/Pages/ExpanderPage.xaml.cs
  24. 35
      samples/ControlCatalog/Pages/GesturePage.cs
  25. 21
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  26. 83
      samples/ControlCatalog/Pages/ScreenPage.cs
  27. 222
      samples/ControlCatalog/Pages/ScrollSnapPage.xaml
  28. 68
      samples/ControlCatalog/Pages/ScrollSnapPage.xaml.cs
  29. 7
      samples/ControlCatalog/Pages/ScrollViewerPage.xaml
  30. 8
      samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs
  31. 2
      samples/ControlCatalog/Pages/TabControlPage.xaml
  32. 8
      samples/GpuInterop/App.axaml
  33. 22
      samples/GpuInterop/App.axaml.cs
  34. 146
      samples/GpuInterop/D3DDemo/D3D11DemoControl.cs
  35. 110
      samples/GpuInterop/D3DDemo/D3D11Swapchain.cs
  36. 110
      samples/GpuInterop/D3DDemo/D3DContent.cs
  37. 47
      samples/GpuInterop/D3DDemo/MiniCube.fx
  38. 141
      samples/GpuInterop/DrawingSurfaceDemoBase.cs
  39. 32
      samples/GpuInterop/GpuDemo.axaml
  40. 116
      samples/GpuInterop/GpuDemo.axaml.cs
  41. 51
      samples/GpuInterop/GpuInterop.csproj
  42. 13
      samples/GpuInterop/MainWindow.axaml
  43. 21
      samples/GpuInterop/MainWindow.axaml.cs
  44. 15
      samples/GpuInterop/Program.cs
  45. 12
      samples/GpuInterop/VulkanDemo/Assets/Shaders/Makefile
  46. 42
      samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.glsl
  47. BIN
      samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.spirv
  48. 36
      samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.glsl
  49. BIN
      samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.spirv
  50. 47
      samples/GpuInterop/VulkanDemo/ByteString.cs
  51. 54
      samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs
  52. 80
      samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs
  53. 224
      samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs
  54. 829
      samples/GpuInterop/VulkanDemo/VulkanContent.cs
  55. 335
      samples/GpuInterop/VulkanDemo/VulkanContext.cs
  56. 101
      samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs
  57. 12
      samples/GpuInterop/VulkanDemo/VulkanExtensions.cs
  58. 276
      samples/GpuInterop/VulkanDemo/VulkanImage.cs
  59. 59
      samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs
  60. 58
      samples/GpuInterop/VulkanDemo/VulkanSemaphorePair.cs
  61. 154
      samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs
  62. 4
      samples/RenderDemo/App.xaml.cs
  63. 4
      samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
  64. 5
      samples/VirtualizationDemo/MainWindow.xaml
  65. 10
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  66. 19
      src/Android/Avalonia.Android/AndroidPlatform.cs
  67. 27
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  68. 16
      src/Android/Avalonia.Android/AvaloniaView.cs
  69. 3
      src/Android/Avalonia.Android/IActivityResultHandler.cs
  70. 14
      src/Android/Avalonia.Android/IAndroidNavigationService.cs
  71. 94
      src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs
  72. 28
      src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
  73. 52
      src/Android/Avalonia.Android/Platform/PlatformSupport.cs
  74. 24
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  75. 101
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  76. 124
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
  77. 26
      src/Avalonia.Base/Animation/Animators/GradientBrushAnimator.cs
  78. 2
      src/Avalonia.Base/Animation/PageSlide.cs
  79. 2
      src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs
  80. 4
      src/Avalonia.Base/AttachedProperty.cs
  81. 8
      src/Avalonia.Base/Avalonia.Base.csproj
  82. 16
      src/Avalonia.Base/AvaloniaObject.cs
  83. 8
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  84. 8
      src/Avalonia.Base/AvaloniaProperty.cs
  85. 59
      src/Avalonia.Base/Data/BindingOperations.cs
  86. 2
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  87. 2
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  88. 2
      src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
  89. 2
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  90. 2
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  91. 2
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  92. 2
      src/Avalonia.Base/Data/Core/LogicalNotNode.cs
  93. 2
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  94. 28
      src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs
  95. 2
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  96. 2
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  97. 2
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  98. 2
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  99. 2
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  100. 4
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

3
.editorconfig

@ -137,6 +137,9 @@ space_within_single_line_array_initializer_braces = true
#Net Analyzer
dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed.
# CS0649: Field 'field' is never assigned to, and will always have its default value 'value'
dotnet_diagnostic.CS0649.severity = error
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = suggestion

1
Avalonia.Desktop.slnf

@ -5,6 +5,7 @@
"packages\\Avalonia\\Avalonia.csproj",
"samples\\ControlCatalog.NetCore\\ControlCatalog.NetCore.csproj",
"samples\\ControlCatalog\\ControlCatalog.csproj",
"samples\\GpuInterop\\GpuInterop.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\SampleControls\\ControlSamples.csproj",

7
Avalonia.sln

@ -231,6 +231,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blaz
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -542,6 +544,10 @@ Global
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -606,6 +612,7 @@ Global
{15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

1
build/Base.props

@ -1,6 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Condition="'$(TargetFramework)' != 'net6'">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
</ItemGroup>

15
build/SharpDX.props

@ -1,9 +1,14 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SharpDXPackageVersion>4.0.1</SharpDXPackageVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpDX" Version="4.0.1" />
<PackageReference Include="SharpDX.Direct2D1" Version="4.0.1" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.0.1" />
<PackageReference Include="SharpDX.DXGI" Version="4.0.1" />
<PackageReference Include="SharpDX.Direct3D9" Version="4.0.1" Condition="'$(UseDirect3D9)' == 'true'" />
<PackageReference Include="SharpDX" Version="$(SharpDXPackageVersion)" />
<PackageReference Include="SharpDX.Direct2D1" Version="$(SharpDXPackageVersion)" />
<PackageReference Include="SharpDX.Direct3D11" Version="$(SharpDXPackageVersion)" />
<PackageReference Include="SharpDX.DXGI" Version="$(SharpDXPackageVersion)" />
<PackageReference Include="SharpDX.Direct3D9" Version="$(SharpDXPackageVersion)" Condition="'$(UseDirect3D9)' == 'true'" />
<PackageReference Include="SharpDX.D3DCompiler" Version="$(SharpDXPackageVersion)" Condition="'$(UseD3DCompiler)' == 'true'" />
<PackageReference Include="SharpDX.Mathematics" Version="$(SharpDXPackageVersion)" Condition="'$(UseSharpDXMathematics)' == 'true'" />
</ItemGroup>
</Project>

4
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -50,6 +50,7 @@
BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; };
EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -103,6 +104,7 @@
BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = "<group>"; };
BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = "<group>"; };
ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformSettings.mm; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -163,6 +165,7 @@
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */,
AB7A61F02147C815003C5833 /* Products */,
AB661C1C2148230E00291242 /* Frameworks */,
18391676ECF0E983F4964357 /* WindowBaseImpl.mm */,
@ -299,6 +302,7 @@
1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

5
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -44,7 +44,7 @@
-(bool) isDialog
{
return _parent->IsDialog();
return _parent->IsModal();
}
-(double) getExtendedTitleBarHeight
@ -281,6 +281,9 @@
- (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification
{
if (_parent == nullptr)
return;
_parent->BringToFront();
dispatch_async(dispatch_get_main_queue(), ^{

113
native/Avalonia.Native/src/OSX/PlatformSettings.mm

@ -0,0 +1,113 @@
#include "common.h"
@interface CocoaThemeObserver : NSObject
-(id)initWithCallback:(IAvnActionCallback *)callback;
@end
class PlatformSettings : public ComSingleObject<IAvnPlatformSettings, &IID_IAvnPlatformSettings>
{
CocoaThemeObserver* observer;
public:
FORWARD_IUNKNOWN()
virtual AvnPlatformThemeVariant GetPlatformTheme() override
{
@autoreleasepool
{
if (@available(macOS 10.14, *))
{
if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAqua
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight) {
return AvnPlatformThemeVariant::Light;
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameDarkAqua
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantDark) {
return AvnPlatformThemeVariant::Dark;
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) {
return AvnPlatformThemeVariant::HighContrastLight;
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) {
return AvnPlatformThemeVariant::HighContrastDark;
}
}
return AvnPlatformThemeVariant::Light;
}
}
virtual unsigned int GetAccentColor() override
{
@autoreleasepool
{
if (@available(macOS 10.14, *))
{
auto color = [NSColor controlAccentColor];
return to_argb(color);
}
else
{
return 0;
}
}
}
virtual void RegisterColorsChange(IAvnActionCallback *callback) override
{
if (@available(macOS 10.14, *))
{
observer = [[CocoaThemeObserver alloc] initWithCallback: callback];
[[NSApplication sharedApplication] addObserver:observer forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew context:nil];
}
}
private:
unsigned int to_argb(NSColor* color)
{
const CGFloat* components = CGColorGetComponents(color.CGColor);
unsigned int alpha = static_cast<unsigned int>(CGColorGetAlpha(color.CGColor) * 0xFF);
unsigned int red = static_cast<unsigned int>(components[0] * 0xFF);
unsigned int green = static_cast<unsigned int>(components[1] * 0xFF);
unsigned int blue = static_cast<unsigned int>(components[2] * 0xFF);
return (alpha << 24) + (red << 16) + (green << 8) + blue;
}
};
@implementation CocoaThemeObserver
{
ComPtr<IAvnActionCallback> _callback;
}
- (id) initWithCallback:(IAvnActionCallback *)callback{
self = [super init];
if (self) {
_callback = callback;
}
return self;
}
/*- (void)didChangeValueForKey:(NSString *)key {
if([key isEqualToString:@"effectiveAppearance"]) {
_callback->Run();
}
else {
[super didChangeValueForKey:key];
}
}*/
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if([keyPath isEqualToString:@"effectiveAppearance"]) {
_callback->Run();
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
@end
extern IAvnPlatformSettings* CreatePlatformSettings()
{
return new PlatformSettings();
}

4
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@ -92,11 +92,13 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode) override;
virtual HRESULT SetFrameThemeVariant(AvnPlatformThemeVariant variant) override;
virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
IAvnClipboard *clipboard, IAvnDndResultCallback *cb,
void *sourceHandle) override;
virtual bool IsDialog();
virtual bool IsModal();
id<AvnWindowProtocol> GetWindowProtocol ();

20
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -498,6 +498,24 @@ HRESULT WindowBaseImpl::SetTransparencyMode(AvnWindowTransparencyMode mode) {
return S_OK;
}
HRESULT WindowBaseImpl::SetFrameThemeVariant(AvnPlatformThemeVariant variant) {
START_COM_CALL;
NSAppearanceName appearanceName;
if (@available(macOS 10.14, *))
{
appearanceName = variant == AvnPlatformThemeVariant::Dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua;
}
else
{
appearanceName = variant == AvnPlatformThemeVariant::Dark ? NSAppearanceNameVibrantDark : NSAppearanceNameAqua;
}
[Window setAppearance: [NSAppearance appearanceNamed: appearanceName]];
return S_OK;
}
HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) {
START_COM_CALL;
@ -542,7 +560,7 @@ HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, Av
return S_OK;
}
bool WindowBaseImpl::IsDialog() {
bool WindowBaseImpl::IsModal() {
return false;
}

6
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -23,7 +23,7 @@ private:
NSRect _preZoomSize;
bool _transitioningWindowState;
bool _isClientAreaExtended;
bool _isDialog;
bool _isModal;
WindowImpl* _parent;
std::list<WindowImpl*> _children;
AvnExtendClientAreaChromeHints _extendClientHints;
@ -91,7 +91,9 @@ BEGIN_INTERFACE_MAP()
virtual HRESULT SetWindowState (AvnWindowState state) override;
virtual bool IsDialog() override;
virtual bool IsModal() override;
bool IsOwned();
virtual void BringToFront () override;

18
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -63,7 +63,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
START_COM_CALL;
@autoreleasepool {
_isDialog = isDialog || _parent != nullptr;
_isModal = isDialog;
WindowBaseImpl::Show(activate, isDialog);
@ -97,7 +97,7 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
_parent = cparent;
_isDialog = _parent != nullptr;
_isModal = _parent != nullptr;
if(_parent != nullptr && Window != nullptr){
// If one tries to show a child window with a minimized parent window, then the parent window will be
@ -123,7 +123,7 @@ void WindowImpl::BringToFront()
{
if ([Window isVisible] && ![Window isMiniaturized])
{
if(IsDialog())
if(IsModal())
{
Activate();
}
@ -150,7 +150,7 @@ bool WindowImpl::CanBecomeKeyWindow()
{
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
if((*iterator)->IsDialog())
if((*iterator)->IsModal())
{
return false;
}
@ -569,8 +569,12 @@ HRESULT WindowImpl::SetWindowState(AvnWindowState state) {
}
}
bool WindowImpl::IsDialog() {
return _isDialog;
bool WindowImpl::IsModal() {
return _isModal;
}
bool WindowImpl::IsOwned() {
return _parent != nullptr;
}
NSWindowStyleMask WindowImpl::GetStyle() {
@ -599,7 +603,7 @@ NSWindowStyleMask WindowImpl::GetStyle() {
break;
}
if (!IsDialog()) {
if (!IsOwned()) {
s |= NSWindowStyleMaskMiniaturizable;
}

1
native/Avalonia.Native/src/OSX/common.h

@ -27,6 +27,7 @@ extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnApplicationCommands* CreateApplicationCommands();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern IAvnPlatformSettings* CreatePlatformSettings();
extern void SetAppMenu(IAvnMenu *menu);
extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu ();

10
native/Avalonia.Native/src/OSX/main.mm

@ -398,6 +398,16 @@ public:
}
}
virtual HRESULT CreatePlatformSettings (IAvnPlatformSettings** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreatePlatformSettings();
return S_OK;
}
}
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

3
samples/ControlCatalog.NetCore/Program.cs

@ -55,8 +55,7 @@ namespace ControlCatalog.NetCore
return builder
.UseHeadless(new AvaloniaHeadlessPlatformOptions
{
UseHeadlessDrawing = true,
UseCompositor = true
UseHeadlessDrawing = true
})
.AfterSetup(_ =>
{

5
samples/ControlCatalog/MainView.xaml

@ -144,9 +144,12 @@
<TabItem Header="RelativePanel">
<pages:RelativePanelPage />
</TabItem>
<TabItem Header="ScrollViewer">
<TabItem Header="ScrollViewer">
<pages:ScrollViewerPage />
</TabItem>
<TabItem Header="ScrollViewer Snapping">
<pages:ScrollSnapPage />
</TabItem>
<TabItem Header="Slider">
<pages:SliderPage />
</TabItem>

75
samples/ControlCatalog/MainView.xaml.cs

@ -3,9 +3,11 @@ using System.Collections;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.VisualTree;
using ControlCatalog.Models;
using ControlCatalog.Pages;
@ -14,10 +16,14 @@ namespace ControlCatalog
{
public class MainView : UserControl
{
private readonly IPlatformSettings _platformSettings;
public MainView()
{
AvaloniaXamlLoader.Load(this);
_platformSettings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
var sideBar = this.Get<TabControl>("Sidebar");
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
@ -37,6 +43,15 @@ namespace ControlCatalog
if (themes.SelectedItem is CatalogTheme theme)
{
App.SetThemeVariant(theme);
((TopLevel?)this.GetVisualRoot())?.PlatformImpl?.SetFrameThemeVariant(theme switch
{
CatalogTheme.FluentLight => PlatformThemeVariant.Light,
CatalogTheme.FluentDark => PlatformThemeVariant.Dark,
CatalogTheme.SimpleLight => PlatformThemeVariant.Light,
CatalogTheme.SimpleDark => PlatformThemeVariant.Dark,
_ => throw new ArgumentOutOfRangeException()
});
}
};
@ -45,7 +60,7 @@ namespace ControlCatalog
{
if (flowDirections.SelectedItem is FlowDirection flowDirection)
{
this.FlowDirection = flowDirection;
TopLevel.GetTopLevel(this).FlowDirection = flowDirection;
}
};
@ -89,6 +104,62 @@ namespace ControlCatalog
var decorations = this.Get<ComboBox>("Decorations");
if (VisualRoot is Window window)
decorations.SelectedIndex = (int)window.SystemDecorations;
_platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
_platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged;
}
private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
{
var themes = this.Get<ComboBox>("Themes");
var currentTheme = (CatalogTheme?)themes.SelectedItem ?? CatalogTheme.FluentLight;
var newTheme = (currentTheme, e.ThemeVariant) switch
{
(CatalogTheme.FluentDark, PlatformThemeVariant.Light) => CatalogTheme.FluentLight,
(CatalogTheme.FluentLight, PlatformThemeVariant.Dark) => CatalogTheme.FluentDark,
(CatalogTheme.SimpleDark, PlatformThemeVariant.Light) => CatalogTheme.SimpleLight,
(CatalogTheme.SimpleLight, PlatformThemeVariant.Dark) => CatalogTheme.SimpleDark,
_ => currentTheme
};
themes.SelectedItem = newTheme;
Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1;
Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
static Color ChangeColorLuminosity(Color color, double luminosityFactor)
{
var red = (double)color.R;
var green = (double)color.G;
var blue = (double)color.B;
if (luminosityFactor < 0)
{
luminosityFactor = 1 + luminosityFactor;
red *= luminosityFactor;
green *= luminosityFactor;
blue *= luminosityFactor;
}
else if (luminosityFactor >= 0)
{
red = (255 - red) * luminosityFactor + red;
green = (255 - green) * luminosityFactor + green;
blue = (255 - blue) * luminosityFactor + blue;
}
return new Color(color.A, (byte)red, (byte)green, (byte)blue);
}
}
}
}

4
samples/ControlCatalog/Pages/CarouselPage.xaml

@ -12,7 +12,7 @@
</Button>
<Carousel Name="carousel" Grid.Column="1">
<Carousel.PageTransition>
<PageSlide Duration="0.25" Orientation="Vertical" />
<PageSlide Duration="0.25" Orientation="Horizontal" />
</Carousel.PageTransition>
<Image Source="/Assets/delicate-arch-896885_640.jpg"/>
<Image Source="/Assets/hirsch-899118_640.jpg"/>
@ -35,7 +35,7 @@
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock VerticalAlignment="Center">Orientation</TextBlock>
<ComboBox Name="orientation" SelectedIndex="1" VerticalAlignment="Center">
<ComboBox Name="orientation" SelectedIndex="0" VerticalAlignment="Center">
<ComboBoxItem>Horizontal</ComboBoxItem>
<ComboBoxItem>Vertical</ComboBoxItem>
</ComboBox>

22
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -1,7 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:local="using:ControlCatalog.Models"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.DataGridPage">
xmlns:pages="clr-namespace:ControlCatalog.Pages"
x:Class="ControlCatalog.Pages.DataGridPage"
x:DataType="pages:DataGridPage">
<UserControl.Resources>
<local:GDPValueConverter x:Key="GDPConverter" />
<DataTemplate x:Key="Demo.DataTemplates.CountryHeader" x:DataType="local:Country">
@ -33,7 +35,7 @@
<DataGrid.Columns>
<!-- Using HeaderTemplate -->
<DataGridTextColumn Header="Country" HeaderTemplate="{StaticResource Demo.DataTemplates.CountryHeader}" Binding="{Binding Name}" Width="6*" x:DataType="local:Country" />
<DataGridTextColumn Header="Region" Binding="{CompiledBinding Region}" Width="4*" x:DataType="local:Country" />
<DataGridTextColumn Header="Region" Binding="{Binding Region}" Width="4*" x:DataType="local:Country" />
<DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" x:DataType="local:Country" />
<DataGridTextColumn Header="Area" Binding="{Binding Area}" Width="3*" x:DataType="local:Country" />
<DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*"
@ -90,19 +92,21 @@
</TabItem>
<TabItem x:Name="EditableTab" Header="Editable">
<Grid RowDefinitions="*,Auto">
<DataGrid Name="dataGridEdit" Margin="12" Grid.Row="0">
<!-- Example of columns inheriting the data type from the Items source -->
<DataGrid Name="dataGridEdit" Margin="12" Grid.Row="0"
Items="{Binding DataGrid3Source}">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" Width="2*" FontSize="{Binding #FontSizeSlider.Value, Mode=OneWay}" x:DataType="local:Person" />
<DataGridTextColumn Header="Last" Binding="{Binding LastName}" Width="2*" FontSize="{Binding #FontSizeSlider.Value, Mode=OneWay}" x:DataType="local:Person" />
<DataGridCheckBoxColumn Header="Is Banned" Binding="{Binding IsBanned}" Width="*" IsThreeState="{Binding #IsThreeStateCheckBox.IsChecked, Mode=OneWay}" x:DataType="local:Person" />
<DataGridTemplateColumn Header="Age" >
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" Width="2*" FontSize="{Binding #FontSizeSlider.Value, Mode=OneWay}" />
<DataGridTextColumn Header="Last" Binding="{Binding LastName}" Width="2*" FontSize="{Binding #FontSizeSlider.Value, Mode=OneWay}" />
<DataGridCheckBoxColumn Header="Is Banned" Binding="{Binding IsBanned}" Width="*" IsThreeState="{Binding #IsThreeStateCheckBox.IsChecked, Mode=OneWay}" />
<DataGridTemplateColumn Header="Age">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="local:Person">
<DataTemplate>
<TextBlock Text="{Binding Age, StringFormat='{}{0} years'}" VerticalAlignment="Center" HorizontalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="local:Person">
<DataTemplate>
<NumericUpDown Value="{Binding Age}" FormatString="N0" HorizontalAlignment="Stretch" Minimum="0" Maximum="120" TemplateApplied="NumericUpDown_OnTemplateApplied" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>

13
samples/ControlCatalog/Pages/DataGridPage.xaml.cs

@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls;
@ -48,20 +49,22 @@ namespace ControlCatalog.Pages
var dg3 = this.Get<DataGrid>("dataGridEdit");
dg3.IsReadOnly = false;
var items = new List<Person>
var list = new ObservableCollection<Person>
{
new Person { FirstName = "John", LastName = "Doe" , Age = 30},
new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true , Age = 40 },
new Person { FirstName = "Zack", LastName = "Ward" , Age = 50 }
};
var collectionView3 = new DataGridCollectionView(items);
dg3.Items = collectionView3;
DataGrid3Source = list;
var addButton = this.Get<Button>("btnAdd");
addButton.Click += (a, b) => collectionView3.AddNew();
addButton.Click += (a, b) => list.Add(new Person());
DataContext = this;
}
public IEnumerable<Person> DataGrid3Source { get; }
private void Dg1_LoadingRow(object? sender, DataGridRowEventArgs e)
{
e.Row.Header = e.Row.GetIndex() + 1;

19
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -1,6 +1,8 @@
<UserControl x:Class="ControlCatalog.Pages.DialogsPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:storage="clr-namespace:Avalonia.Platform.Storage;assembly=Avalonia.Base"
xmlns:generic="clr-namespace:System.Collections.Generic;assembly=System.Collections">
<StackPanel Margin="4"
Orientation="Vertical"
Spacing="4">
@ -42,11 +44,24 @@
</StackPanel>
</Expander>
<AutoCompleteBox x:Name="CurrentFolderBox" Watermark="Write full path/uri or well known folder name">
<AutoCompleteBox.Items>
<generic:List x:TypeArguments="storage:WellKnownFolder">
<storage:WellKnownFolder>Desktop</storage:WellKnownFolder>
<storage:WellKnownFolder>Documents</storage:WellKnownFolder>
<storage:WellKnownFolder>Downloads</storage:WellKnownFolder>
<storage:WellKnownFolder>Pictures</storage:WellKnownFolder>
<storage:WellKnownFolder>Videos</storage:WellKnownFolder>
<storage:WellKnownFolder>Music</storage:WellKnownFolder>
</generic:List>
</AutoCompleteBox.Items>
</AutoCompleteBox>
<TextBlock x:Name="PickerLastResultsVisible"
Classes="h2"
IsVisible="False"
Text="Last picker results:" />
<ItemsPresenter x:Name="PickerLastResults" />
<ItemsControl x:Name="PickerLastResults" />
<TextBox Name="BookmarkContainer" Watermark="Bookmark" />
<TextBox Name="OpenedFileContent"

118
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -24,30 +24,55 @@ namespace ControlCatalog.Pages
{
this.InitializeComponent();
var results = this.Get<ItemsPresenter>("PickerLastResults");
IStorageFolder? lastSelectedDirectory = null;
bool ignoreTextChanged = false;
var results = this.Get<ItemsControl>("PickerLastResults");
var resultsVisible = this.Get<TextBlock>("PickerLastResultsVisible");
var bookmarkContainer = this.Get<TextBox>("BookmarkContainer");
var openedFileContent = this.Get<TextBox>("OpenedFileContent");
var openMultiple = this.Get<CheckBox>("OpenMultiple");
var currentFolderBox = this.Get<AutoCompleteBox>("CurrentFolderBox");
IStorageFolder? lastSelectedDirectory = null;
List<FileDialogFilter> GetFilters()
currentFolderBox.TextChanged += async (sender, args) =>
{
if (this.Get<CheckBox>("UseFilters").IsChecked != true)
return new List<FileDialogFilter>();
return new List<FileDialogFilter>
if (ignoreTextChanged) return;
if (Enum.TryParse<WellKnownFolder>(currentFolderBox.Text, true, out var folderEnum))
{
new FileDialogFilter
lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolder(folderEnum);
}
else
{
if (!Uri.TryCreate(currentFolderBox.Text, UriKind.Absolute, out var folderLink))
{
Name = "Text files (.txt)", Extensions = new List<string> {"txt"}
},
new FileDialogFilter
Uri.TryCreate("file://" + currentFolderBox.Text, UriKind.Absolute, out folderLink);
}
if (folderLink is not null)
{
Name = "All files",
Extensions = new List<string> {"*"}
lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPath(folderLink);
}
};
}
};
List<FileDialogFilter> GetFilters()
{
if (this.Get<CheckBox>("UseFilters").IsChecked != true)
return new List<FileDialogFilter>();
return new List<FileDialogFilter>
{
new FileDialogFilter
{
Name = "Text files (.txt)", Extensions = new List<string> {"txt"}
},
new FileDialogFilter
{
Name = "All files",
Extensions = new List<string> {"*"}
}
};
}
List<FilePickerFileType>? GetFileTypes()
@ -55,10 +80,10 @@ namespace ControlCatalog.Pages
if (this.Get<CheckBox>("UseFilters").IsChecked != true)
return null;
return new List<FilePickerFileType>
{
FilePickerFileTypes.All,
FilePickerFileTypes.TextPlain
};
{
FilePickerFileTypes.All,
FilePickerFileTypes.TextPlain
};
}
this.Get<Button>("OpenFile").Click += async delegate
@ -84,7 +109,7 @@ namespace ControlCatalog.Pages
{
Title = "Open multiple files",
Filters = GetFilters(),
Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null,
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
AllowMultiple = true
}.ShowAsync(GetWindow());
results.Items = result;
@ -97,7 +122,7 @@ namespace ControlCatalog.Pages
{
Title = "Save file",
Filters = filters,
Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null,
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
DefaultExtension = filters?.Any() == true ? "txt" : null,
InitialFileName = "test.txt"
}.ShowAsync(GetWindow());
@ -109,7 +134,7 @@ namespace ControlCatalog.Pages
var result = await new OpenFolderDialog()
{
Title = "Select folder",
Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
}.ShowAsync(GetWindow());
if (string.IsNullOrEmpty(result))
{
@ -117,7 +142,7 @@ namespace ControlCatalog.Pages
}
else
{
lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result));
SetFolder(await GetStorageProvider().TryGetFolderFromPath(result));
results.Items = new[] { result };
resultsVisible.IsVisible = true;
}
@ -127,7 +152,7 @@ namespace ControlCatalog.Pages
var result = await new OpenFileDialog()
{
Title = "Select both",
Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null,
Directory = lastSelectedDirectory?.Path is {IsAbsoluteUri:true} path ? path.LocalPath : null,
AllowMultiple = true
}.ShowManagedAsync(GetWindow(), new ManagedFileDialogOptions
{
@ -205,15 +230,15 @@ namespace ControlCatalog.Pages
await using var stream = await file.OpenWriteAsync();
await using var reader = new System.IO.StreamWriter(stream);
#else
using var stream = await file.OpenWriteAsync();
using var reader = new System.IO.StreamWriter(stream);
using var stream = await file.OpenWriteAsync();
using var reader = new System.IO.StreamWriter(stream);
#endif
await reader.WriteLineAsync(openedFileContent.Text);
lastSelectedDirectory = await file.GetParentAsync();
SetFolder(await file.GetParentAsync());
}
await SetPickerResult(file is null ? null : new [] {file});
await SetPickerResult(file is null ? null : new[] { file });
};
this.Get<Button>("OpenFolderPicker").Click += async delegate
{
@ -226,7 +251,7 @@ namespace ControlCatalog.Pages
await SetPickerResult(folders);
lastSelectedDirectory = folders.FirstOrDefault();
SetFolder(folders.FirstOrDefault());
};
this.Get<Button>("OpenFileFromBookmark").Click += async delegate
{
@ -243,10 +268,16 @@ namespace ControlCatalog.Pages
: null;
await SetPickerResult(folder is null ? null : new[] { folder });
lastSelectedDirectory = folder;
SetFolder(folder);
};
void SetFolder(IStorageFolder? folder)
{
ignoreTextChanged = true;
lastSelectedDirectory = folder;
currentFolderBox.Text = folder?.Path.LocalPath;
ignoreTextChanged = false;
}
async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
{
items ??= Array.Empty<IStorageItem>();
@ -260,23 +291,23 @@ namespace ControlCatalog.Pages
var props = await item.GetBasicPropertiesAsync();
resultText += @$"Size: {props.Size}
DateCreated: {props.DateCreated}
DateModified: {props.DateModified}
CanBookmark: {item.CanBookmark}
";
DateCreated: {props.DateCreated}
DateModified: {props.DateModified}
CanBookmark: {item.CanBookmark}
";
if (item is IStorageFile file)
{
resultText += @$"
CanOpenRead: {file.CanOpenRead}
CanOpenWrite: {file.CanOpenWrite}
Content:
";
CanOpenRead: {file.CanOpenRead}
CanOpenWrite: {file.CanOpenWrite}
Content:
";
if (file.CanOpenRead)
{
#if NET6_0_OR_GREATER
await using var stream = await file.OpenReadAsync();
#else
using var stream = await file.OpenReadAsync();
using var stream = await file.OpenReadAsync();
#endif
using var reader = new System.IO.StreamReader(stream);
@ -297,10 +328,11 @@ Content:
openedFileContent.Text = resultText;
lastSelectedDirectory = await item.GetParentAsync();
if (lastSelectedDirectory is not null)
var parent = await item.GetParentAsync();
SetFolder(parent);
if (parent is not null)
{
mappedResults.Add(FullPathOrName(lastSelectedDirectory));
mappedResults.Add(FullPathOrName(parent));
}
foreach (var selectedItem in items)
@ -393,7 +425,7 @@ CanPickFolder: {storageProvider.CanPickFolder}";
private static string FullPathOrName(IStorageItem? item)
{
if (item is null) return "(null)";
return item.TryGetUri(out var uri) ? uri.ToString() : item.Name;
return item.Path is { IsAbsoluteUri: true } path ? path.ToString() : item.Name;
}
Window GetWindow() => TopLevel.GetTopLevel(this) as Window ?? throw new NullReferenceException("Invalid Owner");

4
samples/ControlCatalog/Pages/ExpanderPage.xaml.cs

@ -14,8 +14,8 @@ namespace ControlCatalog.Pages
var CollapsingDisabledExpander = this.Get<Expander>("CollapsingDisabledExpander");
var ExpandingDisabledExpander = this.Get<Expander>("ExpandingDisabledExpander");
CollapsingDisabledExpander.Collapsing += (s, e) => { e.Handled = true; };
ExpandingDisabledExpander.Expanding += (s, e) => { e.Handled = true; };
CollapsingDisabledExpander.Collapsing += (s, e) => { e.Cancel = true; };
ExpandingDisabledExpander.Expanding += (s, e) => { e.Cancel = true; };
}
private void InitializeComponent()

35
samples/ControlCatalog/Pages/GesturePage.cs

@ -6,6 +6,7 @@ using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Rendering.Composition;
using Avalonia.Utilities;
namespace ControlCatalog.Pages
{
@ -53,6 +54,7 @@ namespace ControlCatalog.Pages
{
_currentScale = 1;
compositionVisual.Scale = new Vector3(1,1,1);
compositionVisual.Offset = default;
image.InvalidateMeasure();
}
};
@ -100,13 +102,19 @@ namespace ControlCatalog.Pages
{
InitComposition(control!);
isZooming = true;
if(compositionVisual != null)
{
var scale = _currentScale * (float)e.Scale;
if (scale <= 1)
{
scale = 1;
compositionVisual.Offset = default;
}
compositionVisual.Scale = new(scale, scale, 1);
e.Handled = true;
}
});
@ -114,8 +122,6 @@ namespace ControlCatalog.Pages
{
InitComposition(control!);
isZooming = false;
if (compositionVisual != null)
{
_currentScale = compositionVisual.Scale.X;
@ -126,11 +132,19 @@ namespace ControlCatalog.Pages
{
InitComposition(control!);
if (compositionVisual != null && !isZooming)
if (compositionVisual != null && _currentScale != 1)
{
currentOffset -= new Vector3((float)e.Delta.X, (float)e.Delta.Y, 0);
currentOffset += new Vector3((float)e.Delta.X, (float)e.Delta.Y, 0);
var currentSize = control.Bounds.Size * _currentScale;
currentOffset = new Vector3((float)MathUtilities.Clamp(currentOffset.X, 0, currentSize.Width - control.Bounds.Width),
(float)MathUtilities.Clamp(currentOffset.Y, 0, currentSize.Height - control.Bounds.Height),
0);
compositionVisual.Offset = currentOffset;
compositionVisual.Offset = currentOffset * -1;
e.Handled = true;
}
});
}
@ -173,6 +187,8 @@ namespace ControlCatalog.Pages
if (ballCompositionVisual != null)
{
ballCompositionVisual.Offset = defaultOffset + new System.Numerics.Vector3((float)e.Delta.X * 0.4f, (float)e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1);
e.Handled = true;
}
});
@ -187,11 +203,6 @@ namespace ControlCatalog.Pages
void InitComposition(Control control)
{
if (ballCompositionVisual != null)
{
return;
}
ballCompositionVisual = ElementComposition.GetElementVisual(ball);
if (ballCompositionVisual != null)

21
samples/ControlCatalog/Pages/OpenGlPage.xaml.cs

@ -78,12 +78,7 @@ namespace ControlCatalog.Pages
get => _info;
private set => SetAndRaise(InfoProperty, ref _info, value);
}
static OpenGlPageControl()
{
AffectsRender<OpenGlPageControl>(YawProperty, PitchProperty, RollProperty, DiscoProperty);
}
private int _vertexShader;
private int _fragmentShader;
private int _shaderProgram;
@ -254,7 +249,7 @@ namespace ControlCatalog.Pages
Console.WriteLine(err);
}
protected unsafe override void OnOpenGlInit(GlInterface GL, int fb)
protected override unsafe void OnOpenGlInit(GlInterface GL)
{
CheckError(GL);
@ -309,7 +304,7 @@ namespace ControlCatalog.Pages
}
protected override void OnOpenGlDeinit(GlInterface GL, int fb)
protected override void OnOpenGlDeinit(GlInterface GL)
{
// Unbind everything
GL.BindBuffer(GL_ARRAY_BUFFER, 0);
@ -366,7 +361,15 @@ namespace ControlCatalog.Pages
CheckError(GL);
if (_disco > 0.01)
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
RequestNextFrameRendering();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == YawProperty || change.Property == RollProperty || change.Property == PitchProperty ||
change.Property == DiscoProperty)
RequestNextFrameRendering();
base.OnPropertyChanged(change);
}
}
}

83
samples/ControlCatalog/Pages/ScreenPage.cs

@ -1,5 +1,7 @@
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http.Headers;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
@ -12,6 +14,11 @@ namespace ControlCatalog.Pages
public class ScreenPage : UserControl
{
private double _leftMost;
private double _topMost;
private IBrush _primaryBrush = SolidColorBrush.Parse("#FF0078D7");
private IBrush _defaultBrush = Brushes.LightGray;
private IPen _activePen = new Pen(Brushes.Black);
private IPen _defaultPen = new Pen(Brushes.DarkGray);
protected override bool BypassFlowDirectionPolicies => true;
@ -34,54 +41,88 @@ namespace ControlCatalog.Pages
var screens = w.Screens.All;
var scaling = ((IRenderRoot)w).RenderScaling;
var drawBrush = Brushes.Black;
Pen p = new Pen(drawBrush);
var activeScreen = w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling)));
double maxBottom = 0;
foreach (Screen screen in screens)
for (int i = 0; i<screens.Count; i++ )
{
var screen = screens[i];
if (screen.Bounds.X / 10f < _leftMost)
{
_leftMost = screen.Bounds.X / 10f;
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
return;
}
if (screen.Bounds.Y / 10f < _topMost)
{
_topMost = screen.Bounds.Y / 10f;
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
return;
}
bool primary = screen.IsPrimary;
bool active = screen.Equals(activeScreen);
Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f, screen.Bounds.Width / 10f,
Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f+Math.Abs(_topMost), screen.Bounds.Width / 10f,
screen.Bounds.Height / 10f);
Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f,
Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f+Math.Abs(_topMost), screen.WorkingArea.Width / 10f,
screen.WorkingArea.Height / 10f);
context.DrawRectangle(p, boundsRect);
context.DrawRectangle(p, workingAreaRect);
context.DrawRectangle(primary ? _primaryBrush : _defaultBrush, active ? _activePen : _defaultPen, boundsRect);
context.DrawRectangle(primary ? _primaryBrush : _defaultBrush, active ? _activePen : _defaultPen, workingAreaRect);
var identifier = CreateScreenIdentifier((i+1).ToString(), primary);
var center = boundsRect.Center - new Point(identifier.Width / 2.0f, identifier.Height / 2.0f);
context.DrawText(identifier, center);
maxBottom = Math.Max(maxBottom, boundsRect.Bottom);
}
var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height));
double currentHeight = maxBottom;
formattedText =
CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20));
for(int i = 0; i< screens.Count; i++)
{
var screen = screens[i];
var formattedText = CreateFormattedText($"Screen {i+1}", 18);
context.DrawText(formattedText, new Point(0, currentHeight));
currentHeight += 25;
formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}");
context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 20;
formattedText = CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}");
context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 20;
formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40));
context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 20;
formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60));
context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 20;
formattedText = CreateFormattedText( $"Current: {screen.Equals(activeScreen)}");
context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 30;
formattedText =
CreateFormattedText(
$"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80));
}
context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10));
context.DrawRectangle(_activePen, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f+Math.Abs(_topMost), w.Bounds.Width / 10, w.Bounds.Height / 10));
}
private static FormattedText CreateFormattedText(string textToFormat)
private static FormattedText CreateFormattedText(string textToFormat, double size = 12)
{
return new FormattedText(textToFormat, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
Typeface.Default, 12, Brushes.Green);
Typeface.Default, size, Brushes.Green);
}
private static FormattedText CreateScreenIdentifier(string textToFormat, bool primary)
{
return new FormattedText(textToFormat, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Typeface.Default, 20, primary ? Brushes.White : Brushes.Black);
}
}
}

222
samples/ControlCatalog/Pages/ScrollSnapPage.xaml

@ -0,0 +1,222 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DesignHeight="800"
d:DesignWidth="400"
x:Class="ControlCatalog.Pages.ScrollSnapPage"
xmlns:pages="using:ControlCatalog.Pages"
x:DataType="pages:ScrollSnapPageViewModel">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock TextWrapping="Wrap"
Classes="h2">Scrollviewer can snap supported content both vertically and horizontally. Snapping occurs from scrolling with touch or pen, or using the pointer wheel.</TextBlock>
<Grid RowDefinitions="Auto, Auto, Auto, Auto, Auto">
<StackPanel Orientation="Horizontal"
Spacing="4">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Type" />
<ComboBox Items="{Binding AvailableSnapPointsType}"
SelectedItem="{Binding SnapPointsType}" />
</StackPanel>
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="Snap Point Alignment" />
<ComboBox Items="{Binding AvailableSnapPointsAlignment}"
SelectedItem="{Binding SnapPointsAlignment}" />
</StackPanel>
<ToggleSwitch IsChecked="{Binding AreSnapPointsRegular}"
OffContent="No"
OnContent="Yes"
Content="Are Snap Points regular?" />
</StackPanel>
<TextBlock TextWrapping="Wrap"
Grid.Row="1"
Margin="0,10"
Classes="h2">Vertical Snapping</TextBlock>
<Border
BorderBrush="Green"
BorderThickness="1"
Padding="0"
Grid.Row="2"
Margin="10, 5">
<ScrollViewer x:Name="VerticalSnapsScrollViewer"
VerticalSnapPointsType="{Binding SnapPointsType}"
VerticalSnapPointsAlignment="{Binding SnapPointsAlignment}"
HorizontalAlignment="Stretch"
Height="350"
HorizontalScrollBarVisibility="Disabled">
<StackPanel AreVerticalSnapPointsRegular="{Binding AreSnapPointsRegular}"
Orientation="Vertical"
HorizontalAlignment="Stretch">
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 1"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 2"/>
</Border>
<Border Padding="5, 20"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 3"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 4"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 5"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 6"/>
</Border>
<Border Padding="5,8"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 7"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 8"/>
</Border>
<Border Padding="5,4"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 9"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 20"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 11"/>
</Border>
<Border Padding="5, 30"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
Text="Child 12"/>
</Border>
</StackPanel>
</ScrollViewer>
</Border>
<TextBlock TextWrapping="Wrap"
Grid.Row="3"
Margin="0,10"
Classes="h2">Horizontal Snapping</TextBlock>
<Border
BorderBrush="Green"
BorderThickness="1"
Padding="0"
Grid.Row="4"
Margin="10, 10">
<ScrollViewer x:Name="HorizontalSnapsScrollViewer"
HorizontalSnapPointsType="{Binding SnapPointsType}"
HorizontalSnapPointsAlignment="{Binding SnapPointsAlignment}"
HorizontalAlignment="Stretch"
Height="350"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled">
<StackPanel AreHorizontalSnapPointsRegular="{Binding AreSnapPointsRegular}"
Orientation="Horizontal"
HorizontalAlignment="Stretch">
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
HorizontalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 1"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 2"/>
</Border>
<Border Padding="5, 20"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 3"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 4"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 5"/>
</Border>
<Border Padding="5, 30"
Width="300"
BorderBrush="Red"
VerticalAlignment="Stretch"
BorderThickness="1">
<TextBlock FontWeight="Bold"
VerticalAlignment="Center"
Text="Child 6"/>
</Border>
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
</StackPanel>
</UserControl>

68
samples/ControlCatalog/Pages/ScrollSnapPage.xaml.cs

@ -0,0 +1,68 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Xaml;
using MiniMvvm;
namespace ControlCatalog.Pages
{
public class ScrollSnapPageViewModel : ViewModelBase
{
private SnapPointsType _snapPointsType;
private SnapPointsAlignment _snapPointsAlignment;
private bool _areSnapPointsRegular;
public ScrollSnapPageViewModel()
{
AvailableSnapPointsType = new List<SnapPointsType>()
{
SnapPointsType.None,
SnapPointsType.Mandatory,
SnapPointsType.MandatorySingle
};
AvailableSnapPointsAlignment = new List<SnapPointsAlignment>()
{
SnapPointsAlignment.Near,
SnapPointsAlignment.Center,
SnapPointsAlignment.Far,
};
}
public bool AreSnapPointsRegular
{
get => _areSnapPointsRegular;
set => this.RaiseAndSetIfChanged(ref _areSnapPointsRegular, value);
}
public SnapPointsType SnapPointsType
{
get => _snapPointsType;
set => this.RaiseAndSetIfChanged(ref _snapPointsType, value);
}
public SnapPointsAlignment SnapPointsAlignment
{
get => _snapPointsAlignment;
set => this.RaiseAndSetIfChanged(ref _snapPointsAlignment, value);
}
public List<SnapPointsType> AvailableSnapPointsType { get; }
public List<SnapPointsAlignment> AvailableSnapPointsAlignment { get; }
}
public class ScrollSnapPage : UserControl
{
public ScrollSnapPage()
{
this.InitializeComponent();
DataContext = new ScrollSnapPageViewModel();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

7
samples/ControlCatalog/Pages/ScrollViewerPage.xaml

@ -3,12 +3,13 @@
xmlns:pages="using:ControlCatalog.Pages"
x:Class="ControlCatalog.Pages.ScrollViewerPage"
x:DataType="pages:ScrollViewerPageViewModel">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h2">Allows for horizontal and vertical content scrolling.</TextBlock>
<StackPanel Orientation="Vertical" Spacing="20">
<TextBlock TextWrapping="Wrap" Classes="h2">Allows for horizontal and vertical content scrolling. Supports snapping on touch and pointer wheel scrolling.</TextBlock>
<Grid ColumnDefinitions="Auto, *">
<StackPanel Orientation="Vertical" Spacing="4">
<ToggleSwitch IsChecked="{Binding AllowAutoHide}" Content="Allow auto hide" />
<ToggleSwitch IsChecked="{Binding EnableInertia}" Content="Enable Inertia" />
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Text="Horizontal Scroll" />
@ -24,6 +25,7 @@
<ScrollViewer x:Name="ScrollViewer"
Grid.Column="1"
Width="400" Height="400"
IsScrollInertiaEnabled="{Binding EnableInertia}"
AllowAutoHide="{Binding AllowAutoHide}"
HorizontalScrollBarVisibility="{Binding HorizontalScrollVisibility}"
VerticalScrollBarVisibility="{Binding VerticalScrollVisibility}">
@ -31,6 +33,5 @@
Source="/Assets/delicate-arch-896885_640.jpg" />
</ScrollViewer>
</Grid>
</StackPanel>
</UserControl>

8
samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs

@ -9,6 +9,7 @@ namespace ControlCatalog.Pages
public class ScrollViewerPageViewModel : ViewModelBase
{
private bool _allowAutoHide;
private bool _enableInertia;
private ScrollBarVisibility _horizontalScrollVisibility;
private ScrollBarVisibility _verticalScrollVisibility;
@ -25,6 +26,7 @@ namespace ControlCatalog.Pages
HorizontalScrollVisibility = ScrollBarVisibility.Auto;
VerticalScrollVisibility = ScrollBarVisibility.Auto;
AllowAutoHide = true;
EnableInertia = true;
}
public bool AllowAutoHide
@ -33,6 +35,12 @@ namespace ControlCatalog.Pages
set => this.RaiseAndSetIfChanged(ref _allowAutoHide, value);
}
public bool EnableInertia
{
get => _enableInertia;
set => this.RaiseAndSetIfChanged(ref _enableInertia, value);
}
public ScrollBarVisibility HorizontalScrollVisibility
{
get => _horizontalScrollVisibility;

2
samples/ControlCatalog/Pages/TabControlPage.xaml

@ -53,7 +53,7 @@
<TabControl
Items="{Binding Tabs}"
Margin="0 16"
HeaderDisplayMemberBinding="{Binding Header, x:DataType=viewModels:TabControlPageViewModelItem}"
DisplayMemberBinding="{Binding Header, x:DataType=viewModels:TabControlPageViewModelItem}"
TabStripPlacement="{Binding TabPlacement}">
<TabControl.ContentTemplate>
<DataTemplate x:DataType="viewModels:TabControlPageViewModelItem">

8
samples/GpuInterop/App.axaml

@ -0,0 +1,8 @@
<Application
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="GpuInterop.App">
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

22
samples/GpuInterop/App.axaml.cs

@ -0,0 +1,22 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace GpuInterop
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
desktopLifetime.MainWindow = new MainWindow();
}
}
}
}

146
samples/GpuInterop/D3DDemo/D3D11DemoControl.cs

@ -0,0 +1,146 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics.Interop;
using Buffer = SharpDX.Direct3D11.Buffer;
using DxgiFactory1 = SharpDX.DXGI.Factory1;
using Matrix = SharpDX.Matrix;
using D3DDevice = SharpDX.Direct3D11.Device;
using FeatureLevel = SharpDX.Direct3D.FeatureLevel;
using Vector3 = SharpDX.Vector3;
namespace GpuInterop.D3DDemo;
public class D3D11DemoControl : DrawingSurfaceDemoBase
{
private D3DDevice _device;
private D3D11Swapchain _swapchain;
private SharpDX.Direct3D11.DeviceContext _context;
private Matrix _view;
private PixelSize _lastSize;
private Texture2D _depthBuffer;
private DepthStencilView _depthView;
private Matrix _proj;
private Buffer _constantBuffer;
private Stopwatch _st = Stopwatch.StartNew();
protected override (bool success, string info) InitializeGraphicsResources(Compositor compositor,
CompositionDrawingSurface surface, ICompositionGpuInterop interop)
{
if (interop?.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
.D3D11TextureGlobalSharedHandle) != true)
return (false, "DXGI shared handle import is not supported by the current graphics backend");
var factory = new DxgiFactory1();
using var adapter = factory.GetAdapter1(0);
_device = new D3DDevice(adapter, DeviceCreationFlags.None, new[]
{
FeatureLevel.Level_12_1,
FeatureLevel.Level_12_0,
FeatureLevel.Level_11_1,
FeatureLevel.Level_11_0,
FeatureLevel.Level_10_0,
FeatureLevel.Level_9_3,
FeatureLevel.Level_9_2,
FeatureLevel.Level_9_1,
});
_swapchain = new D3D11Swapchain(_device, interop, surface);
_context = _device.ImmediateContext;
_constantBuffer = D3DContent.CreateMesh(_device);
_view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY);
return (true, $"D3D11 ({_device.FeatureLevel}) {adapter.Description1.Description}");
}
protected override void FreeGraphicsResources()
{
_swapchain.DisposeAsync();
_swapchain = null!;
Utilities.Dispose(ref _depthView);
Utilities.Dispose(ref _depthBuffer);
Utilities.Dispose(ref _constantBuffer);
Utilities.Dispose(ref _context);
Utilities.Dispose(ref _device);
}
protected override bool SupportsDisco => true;
protected override void RenderFrame(PixelSize pixelSize)
{
if (pixelSize == default)
return;
if (pixelSize != _lastSize)
{
_lastSize = pixelSize;
Resize(pixelSize);
}
using (_swapchain.BeginDraw(pixelSize, out var renderView))
{
_device.ImmediateContext.OutputMerger.SetTargets(_depthView, renderView);
var viewProj = Matrix.Multiply(_view, _proj);
var context = _device.ImmediateContext;
var now = _st.Elapsed.TotalSeconds * 5;
var scaleX = (float)(1f + Disco * (Math.Sin(now) + 1) / 6);
var scaleY = (float)(1f + Disco * (Math.Cos(now) + 1) / 8);
var colorOff =(float) (Math.Sin(now) + 1) / 2 * Disco;
// Clear views
context.ClearDepthStencilView(_depthView, DepthStencilClearFlags.Depth, 1.0f, 0);
context.ClearRenderTargetView(renderView,
new RawColor4(1 - colorOff, colorOff, (float)0.5 + colorOff / 2, 1));
var ypr = Matrix4x4.CreateFromYawPitchRoll(Yaw, Pitch, Roll);
// Update WorldViewProj Matrix
var worldViewProj = Matrix.RotationX((float)Yaw) * Matrix.RotationY((float)Pitch)
* Matrix.RotationZ((float)Roll)
* Matrix.Scaling(new Vector3(scaleX, scaleY, 1))
* viewProj;
worldViewProj.Transpose();
context.UpdateSubresource(ref worldViewProj, _constantBuffer);
// Draw the cube
context.Draw(36, 0);
_context.Flush();
}
}
private void Resize(PixelSize size)
{
Utilities.Dispose(ref _depthBuffer);
_depthBuffer = new Texture2D(_device,
new Texture2DDescription()
{
Format = Format.D32_Float_S8X24_UInt,
ArraySize = 1,
MipLevels = 1,
Width = (int)size.Width,
Height = (int)size.Height,
SampleDescription = new SampleDescription(1, 0),
Usage = ResourceUsage.Default,
BindFlags = BindFlags.DepthStencil,
CpuAccessFlags = CpuAccessFlags.None,
OptionFlags = ResourceOptionFlags.None
});
Utilities.Dispose(ref _depthView);
_depthView = new DepthStencilView(_device, _depthBuffer);
// Setup targets and viewport for rendering
_device.ImmediateContext.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f));
// Setup new projection matrix with correct aspect ratio
_proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f);
}
}

110
samples/GpuInterop/D3DDemo/D3D11Swapchain.cs

@ -0,0 +1,110 @@
using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using DxgiFactory1 = SharpDX.DXGI.Factory1;
using D3DDevice = SharpDX.Direct3D11.Device;
using DxgiResource = SharpDX.DXGI.Resource;
namespace GpuInterop.D3DDemo;
class D3D11Swapchain : SwapchainBase<D3D11SwapchainImage>
{
private readonly D3DDevice _device;
public D3D11Swapchain(D3DDevice device, ICompositionGpuInterop interop, CompositionDrawingSurface target)
: base(interop, target)
{
_device = device;
}
protected override D3D11SwapchainImage CreateImage(PixelSize size) => new(_device, size, Interop, Target);
public IDisposable BeginDraw(PixelSize size, out RenderTargetView view)
{
var rv = BeginDrawCore(size, out var image);
view = image.RenderTargetView;
return rv;
}
}
public class D3D11SwapchainImage : ISwapchainImage
{
public PixelSize Size { get; }
private readonly ICompositionGpuInterop _interop;
private readonly CompositionDrawingSurface _target;
private readonly Texture2D _texture;
private readonly KeyedMutex _mutex;
private readonly IntPtr _handle;
private PlatformGraphicsExternalImageProperties _properties;
private ICompositionImportedGpuImage? _imported;
public Task? LastPresent { get; private set; }
public RenderTargetView RenderTargetView { get; }
public D3D11SwapchainImage(D3DDevice device, PixelSize size,
ICompositionGpuInterop interop,
CompositionDrawingSurface target)
{
Size = size;
_interop = interop;
_target = target;
_texture = new Texture2D(device,
new Texture2DDescription
{
Format = Format.R8G8B8A8_UNorm,
Width = size.Width,
Height = size.Height,
ArraySize = 1,
MipLevels = 1,
SampleDescription = new SampleDescription { Count = 1, Quality = 0 },
CpuAccessFlags = default,
OptionFlags = ResourceOptionFlags.SharedKeyedmutex,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource
});
_mutex = _texture.QueryInterface<KeyedMutex>();
using (var res = _texture.QueryInterface<DxgiResource>())
_handle = res.SharedHandle;
_properties = new PlatformGraphicsExternalImageProperties
{
Width = size.Width, Height = size.Height, Format = PlatformGraphicsExternalImageFormat.B8G8R8A8UNorm
};
RenderTargetView = new RenderTargetView(device, _texture);
}
public void BeginDraw()
{
_mutex.Acquire(0, int.MaxValue);
}
public void Present()
{
_mutex.Release(1);
_imported ??= _interop.ImportImage(
new PlatformHandle(_handle, KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle),
_properties);
LastPresent = _target.UpdateWithKeyedMutexAsync(_imported, 1, 0);
}
public async ValueTask DisposeAsync()
{
if (LastPresent != null)
try
{
await LastPresent;
}
catch
{
// Ignore
}
RenderTargetView.Dispose();
_mutex.Dispose();
_texture.Dispose();
}
}

110
samples/GpuInterop/D3DDemo/D3DContent.cs

@ -0,0 +1,110 @@
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D;
using System;
using System.Linq;
using System.Threading.Tasks;
using SharpDX.Direct2D1;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics.Interop;
using Buffer = SharpDX.Direct3D11.Buffer;
using DeviceContext = SharpDX.Direct2D1.DeviceContext;
using DxgiFactory1 = SharpDX.DXGI.Factory1;
using Matrix = SharpDX.Matrix;
using D3DDevice = SharpDX.Direct3D11.Device;
using DxgiResource = SharpDX.DXGI.Resource;
using FeatureLevel = SharpDX.Direct3D.FeatureLevel;
using InputElement = SharpDX.Direct3D11.InputElement;
namespace GpuInterop.D3DDemo;
public class D3DContent
{
public static Buffer CreateMesh(D3DDevice device)
{
// Compile Vertex and Pixel shaders
var vertexShaderByteCode = ShaderBytecode.CompileFromFile("D3DDemo\\MiniCube.fx", "VS", "vs_4_0");
var vertexShader = new VertexShader(device, vertexShaderByteCode);
var pixelShaderByteCode = ShaderBytecode.CompileFromFile("D3DDemo\\MiniCube.fx", "PS", "ps_4_0");
var pixelShader = new PixelShader(device, pixelShaderByteCode);
var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode);
var inputElements = new[]
{
new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0),
new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0)
};
// Layout from VertexShader input signature
var layout = new InputLayout(
device,
signature,
inputElements);
// Instantiate Vertex buffer from vertex data
using var vertices = Buffer.Create(
device,
BindFlags.VertexBuffer,
new[]
{
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4(1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4(1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4(1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK
new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4(1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom
new Vector4(1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right
new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4(1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4(1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4(1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4(1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
});
// Create Constant Buffer
var constantBuffer = new Buffer(device, Utilities.SizeOf<Matrix>(), ResourceUsage.Default,
BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
var context = device.ImmediateContext;
// Prepare All the stages
context.InputAssembler.InputLayout = layout;
context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
context.InputAssembler.SetVertexBuffers(0,
new VertexBufferBinding(vertices, Utilities.SizeOf<Vector4>() * 2, 0));
context.VertexShader.SetConstantBuffer(0, constantBuffer);
context.VertexShader.Set(vertexShader);
context.PixelShader.Set(pixelShader);
return constantBuffer;
}
}

47
samples/GpuInterop/D3DDemo/MiniCube.fx

@ -0,0 +1,47 @@
// Copyright (c) 2010-2013 SharpDX - Alexandre Mutel
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
struct VS_IN
{
float4 pos : POSITION;
float4 col : COLOR;
};
struct PS_IN
{
float4 pos : SV_POSITION;
float4 col : COLOR;
};
float4x4 worldViewProj;
PS_IN VS( VS_IN input )
{
PS_IN output = (PS_IN)0;
output.pos = mul(input.pos, worldViewProj);
output.col = input.col;
return output;
}
float4 PS( PS_IN input ) : SV_Target
{
return input.col;
}

141
samples/GpuInterop/DrawingSurfaceDemoBase.cs

@ -0,0 +1,141 @@
using System;
using System.Numerics;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Rendering.Composition;
using Avalonia.VisualTree;
namespace GpuInterop;
public abstract class DrawingSurfaceDemoBase : Control, IGpuDemo
{
private CompositionSurfaceVisual? _visual;
private Compositor? _compositor;
private Action _update;
private string _info;
private bool _updateQueued;
private bool _initialized;
protected CompositionDrawingSurface Surface { get; private set; }
public DrawingSurfaceDemoBase()
{
_update = UpdateFrame;
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
Initialize();
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_initialized)
FreeGraphicsResources();
_initialized = false;
base.OnDetachedFromLogicalTree(e);
}
async void Initialize()
{
try
{
var selfVisual = ElementComposition.GetElementVisual(this)!;
_compositor = selfVisual.Compositor;
Surface = _compositor.CreateDrawingSurface();
_visual = _compositor.CreateSurfaceVisual();
_visual.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
_visual.Surface = Surface;
ElementComposition.SetElementChildVisual(this, _visual);
var (res, info) = await DoInitialize(_compositor, Surface);
_info = info;
if (ParentControl != null)
ParentControl.Info = info;
_initialized = res;
QueueNextFrame();
}
catch (Exception e)
{
if (ParentControl != null)
ParentControl.Info = e.ToString();
}
}
void UpdateFrame()
{
_updateQueued = false;
var root = this.GetVisualRoot();
if (root == null)
return;
_visual!.Size = new Vector2((float)Bounds.Width, (float)Bounds.Height);
var size = PixelSize.FromSize(Bounds.Size, root.RenderScaling);
RenderFrame(size);
if (SupportsDisco && Disco > 0)
QueueNextFrame();
}
void QueueNextFrame()
{
if (_initialized && !_updateQueued && _compositor != null)
{
_updateQueued = true;
_compositor?.RequestCompositionUpdate(_update);
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if(change.Property == BoundsProperty)
QueueNextFrame();
base.OnPropertyChanged(change);
}
async Task<(bool success, string info)> DoInitialize(Compositor compositor,
CompositionDrawingSurface compositionDrawingSurface)
{
var interop = await compositor.TryGetCompositionGpuInterop();
if (interop == null)
return (false, "Compositor doesn't support interop for the current backend");
return InitializeGraphicsResources(compositor, compositionDrawingSurface, interop);
}
protected abstract (bool success, string info) InitializeGraphicsResources(Compositor compositor,
CompositionDrawingSurface compositionDrawingSurface, ICompositionGpuInterop gpuInterop);
protected abstract void FreeGraphicsResources();
protected abstract void RenderFrame(PixelSize pixelSize);
protected virtual bool SupportsDisco => false;
public void Update(GpuDemo parent, float yaw, float pitch, float roll, float disco)
{
ParentControl = parent;
if (ParentControl != null)
{
ParentControl.Info = _info;
ParentControl.DiscoVisible = true;
}
Yaw = yaw;
Pitch = pitch;
Roll = roll;
Disco = disco;
QueueNextFrame();
}
public GpuDemo? ParentControl { get; private set; }
public float Disco { get; private set; }
public float Roll { get; private set; }
public float Pitch { get; private set; }
public float Yaw { get; private set; }
}

32
samples/GpuInterop/GpuDemo.axaml

@ -0,0 +1,32 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="GpuInterop.GpuDemo"
xmlns:local="clr-namespace:GpuInterop;assembly=GpuInterop">
<Grid>
<ContentControl Content="{Binding $parent[local:GpuDemo].Demo}" />
<StackPanel>
<TextBlock Margin="0 40 0 0" Text="{Binding $parent[local:GpuDemo].Info}"/>
</StackPanel>
<Grid ColumnDefinitions="*,Auto" Margin="20">
<StackPanel Grid.Column="1" MinWidth="300">
<TextBlock>Yaw</TextBlock>
<Slider Value="{Binding $parent[local:GpuDemo].Yaw, Mode=TwoWay}" Maximum="10"/>
<TextBlock>Pitch</TextBlock>
<Slider Value="{Binding $parent[local:GpuDemo].Pitch, Mode=TwoWay}" Maximum="10"/>
<TextBlock>Roll</TextBlock>
<Slider Value="{Binding $parent[local:GpuDemo].Roll, Mode=TwoWay}" Maximum="10"/>
<StackPanel IsVisible="{Binding $parent[local:GpuDemo].DiscoVisible}">
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Foreground="#C000C0">D</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#00C090">I</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#90C000">S</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#C09000">C</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#00C090">O</TextBlock>
</StackPanel>
<Slider Value="{Binding $parent[local:GpuDemo].Disco, Mode=TwoWay}" Maximum="1"/>
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</UserControl>

116
samples/GpuInterop/GpuDemo.axaml.cs

@ -0,0 +1,116 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace GpuInterop;
public class GpuDemo : UserControl
{
public GpuDemo()
{
AvaloniaXamlLoader.Load(this);
}
private float _yaw = 5;
public static readonly DirectProperty<GpuDemo, float> YawProperty =
AvaloniaProperty.RegisterDirect<GpuDemo, float>("Yaw", o => o.Yaw, (o, v) => o.Yaw = v);
public float Yaw
{
get => _yaw;
set => SetAndRaise(YawProperty, ref _yaw, value);
}
private float _pitch = 5;
public static readonly DirectProperty<GpuDemo, float> PitchProperty =
AvaloniaProperty.RegisterDirect<GpuDemo, float>("Pitch", o => o.Pitch, (o, v) => o.Pitch = v);
public float Pitch
{
get => _pitch;
set => SetAndRaise(PitchProperty, ref _pitch, value);
}
private float _roll = 5;
public static readonly DirectProperty<GpuDemo, float> RollProperty =
AvaloniaProperty.RegisterDirect<GpuDemo, float>("Roll", o => o.Roll, (o, v) => o.Roll = v);
public float Roll
{
get => _roll;
set => SetAndRaise(RollProperty, ref _roll, value);
}
private float _disco;
public static readonly DirectProperty<GpuDemo, float> DiscoProperty =
AvaloniaProperty.RegisterDirect<GpuDemo, float>("Disco", o => o.Disco, (o, v) => o.Disco = v);
public float Disco
{
get => _disco;
set => SetAndRaise(DiscoProperty, ref _disco, value);
}
private string _info = string.Empty;
public static readonly DirectProperty<GpuDemo, string> InfoProperty =
AvaloniaProperty.RegisterDirect<GpuDemo, string>("Info", o => o.Info, (o, v) => o.Info = v);
public string Info
{
get => _info;
set => SetAndRaise(InfoProperty, ref _info, value);
}
private bool _discoVisible;
public static readonly DirectProperty<GpuDemo, bool> DiscoVisibleProperty =
AvaloniaProperty.RegisterDirect<GpuDemo, bool>("DiscoVisible", o => o.DiscoVisible,
(o, v) => o._discoVisible = v);
public bool DiscoVisible
{
get => _discoVisible;
set => SetAndRaise(DiscoVisibleProperty, ref _discoVisible, value);
}
private IGpuDemo _demo;
public static readonly DirectProperty<GpuDemo, IGpuDemo> DemoProperty =
AvaloniaProperty.RegisterDirect<GpuDemo, IGpuDemo>("Demo", o => o.Demo,
(o, v) => o._demo = v);
public IGpuDemo Demo
{
get => _demo;
set => SetAndRaise(DemoProperty, ref _demo, value);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == YawProperty
|| change.Property == PitchProperty
|| change.Property == RollProperty
|| change.Property == DiscoProperty
|| change.Property == DemoProperty
)
{
if (change.Property == DemoProperty)
((IGpuDemo)change.OldValue)?.Update(null, 0, 0, 0, 0);
_demo?.Update(this, Yaw, Pitch, Roll, Disco);
}
base.OnPropertyChanged(change);
}
}
public interface IGpuDemo
{
void Update(GpuDemo parent, float yaw, float pitch, float roll, float disco);
}

51
samples/GpuInterop/GpuInterop.csproj

@ -0,0 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<UseD3DCompiler>true</UseD3DCompiler>
<UseSharpDXMathematics>true</UseSharpDXMathematics>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="D3DDemo\MiniCube.fx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="VulkanDemo\Assets\Shaders\Assets" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\src\Avalonia.Base\Rendering\SwapchainBase.cs" />
<None Remove="VulkanDemo\Assets\Shaders\frag.spirv" />
<EmbeddedResource Include="VulkanDemo\Assets\Shaders\frag.spirv" />
<None Remove="VulkanDemo\Assets\Shaders\vert.spirv" />
<EmbeddedResource Include="VulkanDemo\Assets\Shaders\vert.spirv" />
<EmbeddedResource Include="../ControlCatalog/Pages/teapot.bin" />
</ItemGroup>
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\ReferenceCoreLibraries.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\SharpDX.props" />
</Project>

13
samples/GpuInterop/MainWindow.axaml

@ -0,0 +1,13 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:gpuInterop="clr-namespace:GpuInterop"
xmlns:d3DDemo="clr-namespace:GpuInterop.D3DDemo"
xmlns:vulkanDemo="clr-namespace:GpuInterop.VulkanDemo"
x:Class="GpuInterop.MainWindow">
<gpuInterop:GpuDemo>
<gpuInterop:GpuDemo.Demo>
<!--<d3DDemo:D3D11DemoControl/>-->
<vulkanDemo:VulkanDemoControl/>
</gpuInterop:GpuDemo.Demo>
</gpuInterop:GpuDemo>
</Window>

21
samples/GpuInterop/MainWindow.axaml.cs

@ -0,0 +1,21 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace GpuInterop
{
public class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.AttachDevTools();
this.Renderer.DrawFps = true;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

15
samples/GpuInterop/Program.cs

@ -0,0 +1,15 @@
global using System.Reactive.Disposables;
using Avalonia;
namespace GpuInterop
{
public class Program
{
static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
}

12
samples/GpuInterop/VulkanDemo/Assets/Shaders/Makefile

@ -0,0 +1,12 @@
#!/usr/bin/make -f
all: vert.spirv frag.spirv
.PHONY: all
vert.spirv: vert.glsl
glslc -fshader-stage=vert vert.glsl -o vert.spirv
frag.spirv: frag.glsl
glslc -fshader-stage=frag frag.glsl -o frag.spirv

42
samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.glsl

@ -0,0 +1,42 @@
#version 450
layout(location = 0) in vec3 FragPos;
layout(location = 1) in vec3 VecPos;
layout(location = 2) in vec3 Normal;
layout(push_constant) uniform constants{
layout(offset = 0) float maxY;
layout(offset = 4) float minY;
layout(offset = 8) float time;
layout(offset = 12) float disco;
};
layout(location = 0) out vec4 outFragColor;
void main()
{
float y = (VecPos.y - minY) / (maxY - minY);
float c = cos(atan(VecPos.x, VecPos.z) * 20.0 + time * 40.0 + y * 50.0);
float s = sin(-atan(VecPos.z, VecPos.x) * 20.0 - time * 20.0 - y * 30.0);
vec3 discoColor = vec3(
0.5 + abs(0.5 - y) * cos(time * 10.0),
0.25 + (smoothstep(0.3, 0.8, y) * (0.5 - c / 4.0)),
0.25 + abs((smoothstep(0.1, 0.4, y) * (0.5 - s / 4.0))));
vec3 objectColor = vec3((1.0 - y), 0.40 + y / 4.0, y * 0.75 + 0.25);
objectColor = objectColor * (1.0 - disco) + discoColor * disco;
float ambientStrength = 0.3;
vec3 lightColor = vec3(1.0, 1.0, 1.0);
vec3 lightPos = vec3(maxY * 2.0, maxY * 2.0, maxY * 2.0);
vec3 ambient = ambientStrength * lightColor;
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
vec3 result = (ambient + diffuse) * objectColor;
outFragColor = vec4(result, 1.0);
}

BIN
samples/GpuInterop/VulkanDemo/Assets/Shaders/frag.spirv

Binary file not shown.

36
samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.glsl

@ -0,0 +1,36 @@
#version 450
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
layout(location = 0) out vec3 FragPos;
layout(location = 1) out vec3 VecPos;
layout(location = 2) out vec3 Normal;
layout(push_constant) uniform constants{
float maxY;
float minY;
float time;
float disco;
mat4 model;
};
layout(binding = 0) uniform UniformBufferObject {
mat4 projection;
} ubo;
void main()
{
float discoScale = sin(time * 10.0) / 10.0;
float distortionX = 1.0 + disco * cos(time * 20.0) / 10.0;
float scale = 1.0 + disco * discoScale;
vec3 scaledPos = aPos;
scaledPos.x = scaledPos.x * distortionX;
scaledPos *= scale;
gl_Position = ubo.projection * model * vec4(scaledPos, 1.0);
FragPos = vec3(model * vec4(aPos, 1.0));
VecPos = aPos;
Normal = normalize(vec3(model * vec4(aNormal, 1.0)));
}

BIN
samples/GpuInterop/VulkanDemo/Assets/Shaders/vert.spirv

Binary file not shown.

47
samples/GpuInterop/VulkanDemo/ByteString.cs

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace GpuInterop.VulkanDemo;
unsafe class ByteString : IDisposable
{
public IntPtr Pointer { get; }
public ByteString(string s)
{
Pointer = Marshal.StringToHGlobalAnsi(s);
}
public void Dispose()
{
Marshal.FreeHGlobal(Pointer);
}
public static implicit operator byte*(ByteString h) => (byte*)h.Pointer;
}
unsafe class ByteStringList : IDisposable
{
private List<ByteString> _inner;
private byte** _ptr;
public ByteStringList(IEnumerable<string> items)
{
_inner = items.Select(x => new ByteString(x)).ToList();
_ptr = (byte**)Marshal.AllocHGlobal(IntPtr.Size * _inner.Count + 1);
for (var c = 0; c < _inner.Count; c++)
_ptr[c] = (byte*)_inner[c].Pointer;
}
public int Count => _inner.Count;
public uint UCount => (uint)_inner.Count;
public void Dispose()
{
Marshal.FreeHGlobal(new IntPtr(_ptr));
}
public static implicit operator byte**(ByteStringList h) => h._ptr;
}

54
samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs

@ -0,0 +1,54 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Avalonia;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using D3DDevice = SharpDX.Direct3D11.Device;
using DxgiFactory1 = SharpDX.DXGI.Factory1;
namespace GpuInterop.VulkanDemo;
public class D3DMemoryHelper
{
public static D3DDevice CreateDeviceByLuid(Span<byte> luid)
{
var factory = new DxgiFactory1();
var longLuid = MemoryMarshal.Cast<byte, long>(luid)[0];
for (var c = 0; c < factory.GetAdapterCount1(); c++)
{
using var adapter = factory.GetAdapter1(0);
if (adapter.Description1.Luid != longLuid)
continue;
return new D3DDevice(adapter, DeviceCreationFlags.None,
new[]
{
FeatureLevel.Level_12_1, FeatureLevel.Level_12_0, FeatureLevel.Level_11_1,
FeatureLevel.Level_11_0, FeatureLevel.Level_10_0, FeatureLevel.Level_9_3,
FeatureLevel.Level_9_2, FeatureLevel.Level_9_1,
});
}
throw new ArgumentException("Device with the corresponding LUID not found");
}
public static Texture2D CreateMemoryHandle(D3DDevice device, PixelSize size, Silk.NET.Vulkan.Format format)
{
if (format != Silk.NET.Vulkan.Format.R8G8B8A8Unorm)
throw new ArgumentException("Not supported format");
return new Texture2D(device,
new Texture2DDescription
{
Format = Format.R8G8B8A8_UNorm,
Width = size.Width,
Height = size.Height,
ArraySize = 1,
MipLevels = 1,
SampleDescription = new SampleDescription { Count = 1, Quality = 0 },
CpuAccessFlags = default,
OptionFlags = ResourceOptionFlags.SharedKeyedmutex,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource
});
}
}

80
samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs

@ -0,0 +1,80 @@
using System;
using System.Runtime.CompilerServices;
using Silk.NET.Vulkan;
using SilkNetDemo;
using Buffer = Silk.NET.Vulkan.Buffer;
using SystemBuffer = System.Buffer;
namespace GpuInterop.VulkanDemo;
static class VulkanBufferHelper
{
public unsafe static void AllocateBuffer<T>(VulkanContext vk,
BufferUsageFlags bufferUsageFlags,
out Buffer buffer, out DeviceMemory memory,
Span<T> initialData) where T:unmanaged
{
var api = vk.Api;
var device = vk.Device;
var size = Unsafe.SizeOf<T>() * initialData.Length;
var bufferInfo = new BufferCreateInfo()
{
SType = StructureType.BufferCreateInfo,
Size = (ulong)size,
Usage = bufferUsageFlags,
SharingMode = SharingMode.Exclusive
};
api.CreateBuffer(device, bufferInfo, null, out buffer).ThrowOnError();
api.GetBufferMemoryRequirements(device, buffer, out var memoryRequirements);
var physicalDevice = vk.PhysicalDevice;
var memoryAllocateInfo = new MemoryAllocateInfo
{
SType = StructureType.MemoryAllocateInfo,
AllocationSize = memoryRequirements.Size,
MemoryTypeIndex = (uint)FindSuitableMemoryTypeIndex(api,
physicalDevice,
memoryRequirements.MemoryTypeBits,
MemoryPropertyFlags.MemoryPropertyHostCoherentBit |
MemoryPropertyFlags.MemoryPropertyHostVisibleBit)
};
api.AllocateMemory(device, memoryAllocateInfo, null, out memory).ThrowOnError();
api.BindBufferMemory(device, buffer, memory, 0);
UpdateBufferMemory(vk, memory, initialData);
}
public static unsafe void UpdateBufferMemory<T>(VulkanContext vk, DeviceMemory memory,
Span<T> data) where T : unmanaged
{
var api = vk.Api;
var device = vk.Device;
var size = data.Length * Unsafe.SizeOf<T>();
void* pointer = null;
api.MapMemory(device, memory, 0, (ulong)size, 0, ref pointer);
data.CopyTo(new Span<T>(pointer, size));
api.UnmapMemory(device, memory);
}
private static int FindSuitableMemoryTypeIndex(Vk api, PhysicalDevice physicalDevice, uint memoryTypeBits,
MemoryPropertyFlags flags)
{
api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
for (var i = 0; i < properties.MemoryTypeCount; i++)
{
var type = properties.MemoryTypes[i];
if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags)) return i;
}
return -1;
}
}

224
samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs

@ -0,0 +1,224 @@
using System;
using System.Collections.Generic;
using Avalonia.Input;
using Silk.NET.Vulkan;
using SilkNetDemo;
namespace Avalonia.Vulkan
{
public class VulkanCommandBufferPool : IDisposable
{
private readonly Vk _api;
private readonly Device _device;
private readonly Queue _queue;
private readonly CommandPool _commandPool;
private readonly List<VulkanCommandBuffer> _usedCommandBuffers = new();
private object _lock = new object();
public unsafe VulkanCommandBufferPool(Vk api, Device device, Queue queue, uint queueFamilyIndex)
{
_api = api;
_device = device;
_queue = queue;
var commandPoolCreateInfo = new CommandPoolCreateInfo
{
SType = StructureType.CommandPoolCreateInfo,
Flags = CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit,
QueueFamilyIndex = queueFamilyIndex
};
_api.CreateCommandPool(_device, commandPoolCreateInfo, null, out _commandPool)
.ThrowOnError();
}
public unsafe void Dispose()
{
lock (_lock)
{
FreeUsedCommandBuffers();
_api.DestroyCommandPool(_device, _commandPool, null);
}
}
private CommandBuffer AllocateCommandBuffer()
{
var commandBufferAllocateInfo = new CommandBufferAllocateInfo
{
SType = StructureType.CommandBufferAllocateInfo,
CommandPool = _commandPool,
CommandBufferCount = 1,
Level = CommandBufferLevel.Primary
};
lock (_lock)
{
_api.AllocateCommandBuffers(_device, commandBufferAllocateInfo, out var commandBuffer);
return commandBuffer;
}
}
public VulkanCommandBuffer CreateCommandBuffer()
{
return new(_api, _device, _queue, this);
}
public void FreeUsedCommandBuffers()
{
lock (_lock)
{
foreach (var usedCommandBuffer in _usedCommandBuffers) usedCommandBuffer.Dispose();
_usedCommandBuffers.Clear();
}
}
private void DisposeCommandBuffer(VulkanCommandBuffer commandBuffer)
{
lock (_lock)
{
_usedCommandBuffers.Add(commandBuffer);
}
}
public class VulkanCommandBuffer : IDisposable
{
private readonly VulkanCommandBufferPool _commandBufferPool;
private readonly Vk _api;
private readonly Device _device;
private readonly Queue _queue;
private readonly Fence _fence;
private bool _hasEnded;
private bool _hasStarted;
public IntPtr Handle => InternalHandle.Handle;
internal CommandBuffer InternalHandle { get; }
internal unsafe VulkanCommandBuffer(Vk api, Device device, Queue queue, VulkanCommandBufferPool commandBufferPool)
{
_api = api;
_device = device;
_queue = queue;
_commandBufferPool = commandBufferPool;
InternalHandle = _commandBufferPool.AllocateCommandBuffer();
var fenceCreateInfo = new FenceCreateInfo()
{
SType = StructureType.FenceCreateInfo,
Flags = FenceCreateFlags.FenceCreateSignaledBit
};
api.CreateFence(device, fenceCreateInfo, null, out _fence);
}
public unsafe void Dispose()
{
_api.WaitForFences(_device, 1, _fence, true, ulong.MaxValue);
lock (_commandBufferPool._lock)
{
_api.FreeCommandBuffers(_device, _commandBufferPool._commandPool, 1, InternalHandle);
}
_api.DestroyFence(_device, _fence, null);
}
public void BeginRecording()
{
if (!_hasStarted)
{
_hasStarted = true;
var beginInfo = new CommandBufferBeginInfo
{
SType = StructureType.CommandBufferBeginInfo,
Flags = CommandBufferUsageFlags.CommandBufferUsageOneTimeSubmitBit
};
_api.BeginCommandBuffer(InternalHandle, beginInfo);
}
}
public void EndRecording()
{
if (_hasStarted && !_hasEnded)
{
_hasEnded = true;
_api.EndCommandBuffer(InternalHandle);
}
}
public void Submit()
{
Submit(null, null, null, _fence);
}
public class KeyedMutexSubmitInfo
{
public ulong? AcquireKey { get; set; }
public ulong? ReleaseKey { get; set; }
public DeviceMemory DeviceMemory { get; set; }
}
public unsafe void Submit(
ReadOnlySpan<Semaphore> waitSemaphores,
ReadOnlySpan<PipelineStageFlags> waitDstStageMask = default,
ReadOnlySpan<Semaphore> signalSemaphores = default,
Fence? fence = null,
KeyedMutexSubmitInfo keyedMutex = null)
{
EndRecording();
if (!fence.HasValue)
fence = _fence;
ulong acquireKey = keyedMutex?.AcquireKey ?? 0, releaseKey = keyedMutex?.ReleaseKey ?? 0;
DeviceMemory devMem = keyedMutex?.DeviceMemory ?? default;
uint timeout = uint.MaxValue;
Win32KeyedMutexAcquireReleaseInfoKHR mutex = default;
if (keyedMutex != null)
mutex = new Win32KeyedMutexAcquireReleaseInfoKHR
{
SType = StructureType.Win32KeyedMutexAcquireReleaseInfoKhr,
AcquireCount = keyedMutex.AcquireKey.HasValue ? 1u : 0u,
ReleaseCount = keyedMutex.ReleaseKey.HasValue ? 1u : 0u,
PAcquireKeys = &acquireKey,
PReleaseKeys = &releaseKey,
PAcquireSyncs = &devMem,
PReleaseSyncs = &devMem,
PAcquireTimeouts = &timeout
};
fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores)
{
fixed (PipelineStageFlags* pWaitDstStageMask = waitDstStageMask)
{
var commandBuffer = InternalHandle;
var submitInfo = new SubmitInfo
{
PNext = keyedMutex != null ? &mutex : null,
SType = StructureType.SubmitInfo,
WaitSemaphoreCount = waitSemaphores != null ? (uint)waitSemaphores.Length : 0,
PWaitSemaphores = pWaitSemaphores,
PWaitDstStageMask = pWaitDstStageMask,
CommandBufferCount = 1,
PCommandBuffers = &commandBuffer,
SignalSemaphoreCount = signalSemaphores != null ? (uint)signalSemaphores.Length : 0,
PSignalSemaphores = pSignalSemaphores,
};
_api.ResetFences(_device, 1, fence.Value);
_api.QueueSubmit(_queue, 1, submitInfo, fence.Value);
}
}
_commandBufferPool.DisposeCommandBuffer(this);
}
}
}
}

829
samples/GpuInterop/VulkanDemo/VulkanContent.cs

@ -0,0 +1,829 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Threading;
using Silk.NET.Vulkan;
using SilkNetDemo;
using Buffer = System.Buffer;
using Image = Silk.NET.Vulkan.Image;
namespace GpuInterop.VulkanDemo;
unsafe class VulkanContent : IDisposable
{
private readonly VulkanContext _context;
private ShaderModule _vertShader;
private ShaderModule _fragShader;
private PipelineLayout _pipelineLayout;
private RenderPass _renderPass;
private Pipeline _pipeline;
private DescriptorSetLayout _descriptorSetLayout;
private Silk.NET.Vulkan.Buffer _vertexBuffer;
private DeviceMemory _vertexBufferMemory;
private Silk.NET.Vulkan.Buffer _indexBuffer;
private DeviceMemory _indexBufferMemory;
private Silk.NET.Vulkan.Buffer _uniformBuffer;
private DeviceMemory _uniformBufferMemory;
private Framebuffer _framebuffer;
private Image _depthImage;
private DeviceMemory _depthImageMemory;
private ImageView _depthImageView;
public VulkanContent(VulkanContext context)
{
_context = context;
var name = typeof(VulkanContent).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin"));
using (var sr = new BinaryReader(typeof(VulkanContent).Assembly.GetManifestResourceStream(name)))
{
var buf = new byte[sr.ReadInt32()];
sr.Read(buf, 0, buf.Length);
var points = new float[buf.Length / 4];
Buffer.BlockCopy(buf, 0, points, 0, buf.Length);
buf = new byte[sr.ReadInt32()];
sr.Read(buf, 0, buf.Length);
_indices = new ushort[buf.Length / 2];
Buffer.BlockCopy(buf, 0, _indices, 0, buf.Length);
_points = new Vertex[points.Length / 3];
for (var primitive = 0; primitive < points.Length / 3; primitive++)
{
var srci = primitive * 3;
_points[primitive] = new Vertex
{
Position = new Vector3(points[srci], points[srci + 1], points[srci + 2])
};
}
for (int i = 0; i < _indices.Length; i += 3)
{
Vector3 a = _points[_indices[i]].Position;
Vector3 b = _points[_indices[i + 1]].Position;
Vector3 c = _points[_indices[i + 2]].Position;
var normal = Vector3.Normalize(Vector3.Cross(c - b, a - b));
_points[_indices[i]].Normal += normal;
_points[_indices[i + 1]].Normal += normal;
_points[_indices[i + 2]].Normal += normal;
}
for (int i = 0; i < _points.Length; i++)
{
_points[i].Normal = Vector3.Normalize(_points[i].Normal);
_maxY = Math.Max(_maxY, _points[i].Position.Y);
_minY = Math.Min(_minY, _points[i].Position.Y);
}
}
var api = _context.Api;
var device = _context.Device;
var vertShaderData = GetShader(false);
var fragShaderData = GetShader(true);
fixed (byte* ptr = vertShaderData)
{
var shaderCreateInfo = new ShaderModuleCreateInfo()
{
SType = StructureType.ShaderModuleCreateInfo,
CodeSize = (nuint)vertShaderData.Length,
PCode = (uint*)ptr,
};
api.CreateShaderModule(device, shaderCreateInfo, null, out _vertShader);
}
fixed (byte* ptr = fragShaderData)
{
var shaderCreateInfo = new ShaderModuleCreateInfo()
{
SType = StructureType.ShaderModuleCreateInfo,
CodeSize = (nuint)fragShaderData.Length,
PCode = (uint*)ptr,
};
api.CreateShaderModule(device, shaderCreateInfo, null, out _fragShader);
}
CreateBuffers();
}
private byte[] GetShader(bool fragment)
{
var name = typeof(VulkanContent).Assembly.GetManifestResourceNames()
.First(x => x.Contains((fragment ? "frag" : "vert") + ".spirv"));
using (var sr = typeof(VulkanContent).Assembly.GetManifestResourceStream(name))
{
using (var mem = new MemoryStream())
{
sr.CopyTo(mem);
return mem.ToArray();
}
}
}
private PixelSize? _previousImageSize = PixelSize.Empty;
public void Render(VulkanImage image,
double yaw, double pitch, double roll, double disco)
{
var api = _context.Api;
if (image.Size != _previousImageSize)
CreateTemporalObjects(image.Size);
_previousImageSize = image.Size;
var model = Matrix4x4.CreateFromYawPitchRoll((float)yaw, (float)pitch, (float)roll);
var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0));
var projection =
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)((float)image.Size.Width / image.Size.Height),
0.01f, 1000);
var vertexConstant = new VertextPushConstant()
{
Disco = (float)disco,
MinY = _minY,
MaxY = _maxY,
Model = model,
Time = (float)St.Elapsed.TotalSeconds
};
var commandBuffer = _context.Pool.CreateCommandBuffer();
commandBuffer.BeginRecording();
_colorAttachment.TransitionLayout(commandBuffer.InternalHandle,
ImageLayout.Undefined, AccessFlags.None,
ImageLayout.ColorAttachmentOptimal, AccessFlags.ColorAttachmentWriteBit);
var commandBufferHandle = new CommandBuffer(commandBuffer.Handle);
api.CmdSetViewport(commandBufferHandle, 0, 1,
new Viewport()
{
Width = (float)image.Size.Width,
Height = (float)image.Size.Height,
MaxDepth = 1,
MinDepth = 0,
X = 0,
Y = 0
});
var scissor = new Rect2D
{
Extent = new Extent2D((uint?)image.Size.Width, (uint?)image.Size.Height)
};
api.CmdSetScissor(commandBufferHandle, 0, 1, &scissor);
var clearColor = new ClearValue(new ClearColorValue(1, 0, 0, 0.1f), new ClearDepthStencilValue(1, 0));
var clearValues = new[] { clearColor, clearColor };
fixed (ClearValue* clearValue = clearValues)
{
var beginInfo = new RenderPassBeginInfo()
{
SType = StructureType.RenderPassBeginInfo,
RenderPass = _renderPass,
Framebuffer = _framebuffer,
RenderArea = new Rect2D(new Offset2D(0, 0), new Extent2D((uint?)image.Size.Width, (uint?)image.Size.Height)),
ClearValueCount = 2,
PClearValues = clearValue
};
api.CmdBeginRenderPass(commandBufferHandle, beginInfo, SubpassContents.Inline);
}
api.CmdBindPipeline(commandBufferHandle, PipelineBindPoint.Graphics, _pipeline);
var dset = _descriptorSet;
api.CmdBindDescriptorSets(commandBufferHandle, PipelineBindPoint.Graphics,
_pipelineLayout,0,1, &dset, null);
api.CmdPushConstants(commandBufferHandle, _pipelineLayout, ShaderStageFlags.ShaderStageVertexBit | ShaderStageFlags.FragmentBit, 0,
(uint)Marshal.SizeOf<VertextPushConstant>(), &vertexConstant);
api.CmdBindVertexBuffers(commandBufferHandle, 0, 1, _vertexBuffer, 0);
api.CmdBindIndexBuffer(commandBufferHandle, _indexBuffer, 0, IndexType.Uint16);
api.CmdDrawIndexed(commandBufferHandle, (uint)_indices.Length, 1, 0, 0, 0);
api.CmdEndRenderPass(commandBufferHandle);
_colorAttachment.TransitionLayout(commandBuffer.InternalHandle, ImageLayout.TransferSrcOptimal, AccessFlags.TransferReadBit);
image.TransitionLayout(commandBuffer.InternalHandle, ImageLayout.TransferDstOptimal, AccessFlags.TransferWriteBit);
var srcBlitRegion = new ImageBlit
{
SrcOffsets = new ImageBlit.SrcOffsetsBuffer
{
Element0 = new Offset3D(0, 0, 0),
Element1 = new Offset3D(image.Size.Width, image.Size.Height, 1),
},
DstOffsets = new ImageBlit.DstOffsetsBuffer
{
Element0 = new Offset3D(0, 0, 0),
Element1 = new Offset3D(image.Size.Width, image.Size.Height, 1),
},
SrcSubresource =
new ImageSubresourceLayers
{
AspectMask = ImageAspectFlags.ImageAspectColorBit,
BaseArrayLayer = 0,
LayerCount = 1,
MipLevel = 0
},
DstSubresource = new ImageSubresourceLayers
{
AspectMask = ImageAspectFlags.ImageAspectColorBit,
BaseArrayLayer = 0,
LayerCount = 1,
MipLevel = 0
}
};
api.CmdBlitImage(commandBuffer.InternalHandle, _colorAttachment.InternalHandle.Value,
ImageLayout.TransferSrcOptimal,
image.InternalHandle.Value, ImageLayout.TransferDstOptimal, 1, srcBlitRegion, Filter.Linear);
commandBuffer.Submit();
}
public unsafe void Dispose()
{
if (_isInit)
{
var api = _context.Api;
var device = _context.Device;
DestroyTemporalObjects();
api.DestroyShaderModule(device, _vertShader, null);
api.DestroyShaderModule(device, _fragShader, null);
api.DestroyBuffer(device, _vertexBuffer, null);
api.FreeMemory(device, _vertexBufferMemory, null);
api.DestroyBuffer(device, _indexBuffer, null);
api.FreeMemory(device, _indexBufferMemory, null);
}
_isInit = false;
}
public unsafe void DestroyTemporalObjects()
{
if (_isInit)
{
if (_renderPass.Handle != 0)
{
var api = _context.Api;
var device = _context.Device;
api.FreeDescriptorSets(_context.Device, _context.DescriptorPool, new[] { _descriptorSet });
api.DestroyImageView(device, _depthImageView, null);
api.DestroyImage(device, _depthImage, null);
api.FreeMemory(device, _depthImageMemory, null);
api.DestroyFramebuffer(device, _framebuffer, null);
api.DestroyPipeline(device, _pipeline, null);
api.DestroyPipelineLayout(device, _pipelineLayout, null);
api.DestroyRenderPass(device, _renderPass, null);
api.DestroyDescriptorSetLayout(device, _descriptorSetLayout, null);
api.DestroyBuffer(device, _uniformBuffer, null);
api.FreeMemory(device, _uniformBufferMemory, null);
_colorAttachment?.Dispose();
_colorAttachment = null;
_depthImage = default;
_depthImageView = default;
_depthImageView = default;
_framebuffer = default;
_pipeline = default;
_renderPass = default;
_pipelineLayout = default;
_descriptorSetLayout = default;
_uniformBuffer = default;
_uniformBufferMemory = default;
}
}
}
private unsafe void CreateDepthAttachment(PixelSize size)
{
var imageCreateInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
ImageType = ImageType.ImageType2D,
Format = Format.D32Sfloat,
Extent =
new Extent3D((uint?)size.Width,
(uint?)size.Height, 1),
MipLevels = 1,
ArrayLayers = 1,
Samples = SampleCountFlags.SampleCount1Bit,
Tiling = ImageTiling.Optimal,
Usage = ImageUsageFlags.ImageUsageDepthStencilAttachmentBit,
SharingMode = SharingMode.Exclusive,
InitialLayout = ImageLayout.Undefined,
Flags = ImageCreateFlags.ImageCreateMutableFormatBit
};
var api = _context.Api;
var device = _context.Device;
api
.CreateImage(device, imageCreateInfo, null, out _depthImage).ThrowOnError();
api.GetImageMemoryRequirements(device, _depthImage,
out var memoryRequirements);
var memoryAllocateInfo = new MemoryAllocateInfo
{
SType = StructureType.MemoryAllocateInfo,
AllocationSize = memoryRequirements.Size,
MemoryTypeIndex = (uint)FindSuitableMemoryTypeIndex(api,
_context.PhysicalDevice,
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit)
};
api.AllocateMemory(device, memoryAllocateInfo, null,
out _depthImageMemory).ThrowOnError();
api.BindImageMemory(device, _depthImage, _depthImageMemory, 0);
var componentMapping = new ComponentMapping(
ComponentSwizzle.R,
ComponentSwizzle.G,
ComponentSwizzle.B,
ComponentSwizzle.A);
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectDepthBit,
0, 1, 0, 1);
var imageViewCreateInfo = new ImageViewCreateInfo
{
SType = StructureType.ImageViewCreateInfo,
Image = _depthImage,
ViewType = ImageViewType.ImageViewType2D,
Format = Format.D32Sfloat,
Components = componentMapping,
SubresourceRange = subresourceRange
};
api
.CreateImageView(device, imageViewCreateInfo, null, out _depthImageView)
.ThrowOnError();
}
private unsafe void CreateTemporalObjects(PixelSize size)
{
DestroyTemporalObjects();
var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0));
var projection =
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)((float)size.Width / size.Height),
0.01f, 1000);
_colorAttachment = new VulkanImage(_context, (uint)Format.R8G8B8A8Unorm, size, false);
CreateDepthAttachment(size);
var api = _context.Api;
var device = _context.Device;
// create renderpasses
var colorAttachment = new AttachmentDescription()
{
Format = Format.R8G8B8A8Unorm,
Samples = SampleCountFlags.SampleCount1Bit,
LoadOp = AttachmentLoadOp.Clear,
StoreOp = AttachmentStoreOp.Store,
InitialLayout = ImageLayout.Undefined,
FinalLayout = ImageLayout.ColorAttachmentOptimal,
StencilLoadOp = AttachmentLoadOp.DontCare,
StencilStoreOp = AttachmentStoreOp.DontCare
};
var depthAttachment = new AttachmentDescription()
{
Format = Format.D32Sfloat,
Samples = SampleCountFlags.SampleCount1Bit,
LoadOp = AttachmentLoadOp.Clear,
StoreOp = AttachmentStoreOp.DontCare,
InitialLayout = ImageLayout.Undefined,
FinalLayout = ImageLayout.DepthStencilAttachmentOptimal,
StencilLoadOp = AttachmentLoadOp.DontCare,
StencilStoreOp = AttachmentStoreOp.DontCare
};
var subpassDependency = new SubpassDependency()
{
SrcSubpass = Vk.SubpassExternal,
DstSubpass = 0,
SrcStageMask = PipelineStageFlags.PipelineStageColorAttachmentOutputBit,
SrcAccessMask = 0,
DstStageMask = PipelineStageFlags.PipelineStageColorAttachmentOutputBit,
DstAccessMask = AccessFlags.AccessColorAttachmentWriteBit
};
var colorAttachmentReference = new AttachmentReference()
{
Attachment = 0, Layout = ImageLayout.ColorAttachmentOptimal
};
var depthAttachmentReference = new AttachmentReference()
{
Attachment = 1, Layout = ImageLayout.DepthStencilAttachmentOptimal
};
var subpassDescription = new SubpassDescription()
{
PipelineBindPoint = PipelineBindPoint.Graphics,
ColorAttachmentCount = 1,
PColorAttachments = &colorAttachmentReference,
PDepthStencilAttachment = &depthAttachmentReference
};
var attachments = new[] { colorAttachment, depthAttachment };
fixed (AttachmentDescription* atPtr = attachments)
{
var renderPassCreateInfo = new RenderPassCreateInfo()
{
SType = StructureType.RenderPassCreateInfo,
AttachmentCount = (uint)attachments.Length,
PAttachments = atPtr,
SubpassCount = 1,
PSubpasses = &subpassDescription,
DependencyCount = 1,
PDependencies = &subpassDependency
};
api.CreateRenderPass(device, renderPassCreateInfo, null, out _renderPass).ThrowOnError();
// create framebuffer
var frameBufferAttachments = new[] { new ImageView(_colorAttachment.ViewHandle), _depthImageView };
fixed (ImageView* frAtPtr = frameBufferAttachments)
{
var framebufferCreateInfo = new FramebufferCreateInfo()
{
SType = StructureType.FramebufferCreateInfo,
RenderPass = _renderPass,
AttachmentCount = (uint)frameBufferAttachments.Length,
PAttachments = frAtPtr,
Width = (uint)size.Width,
Height = (uint)size.Height,
Layers = 1
};
api.CreateFramebuffer(device, framebufferCreateInfo, null, out _framebuffer).ThrowOnError();
}
}
// Create pipeline
var pname = Marshal.StringToHGlobalAnsi("main");
var vertShaderStageInfo = new PipelineShaderStageCreateInfo()
{
SType = StructureType.PipelineShaderStageCreateInfo,
Stage = ShaderStageFlags.ShaderStageVertexBit,
Module = _vertShader,
PName = (byte*)pname,
};
var fragShaderStageInfo = new PipelineShaderStageCreateInfo()
{
SType = StructureType.PipelineShaderStageCreateInfo,
Stage = ShaderStageFlags.ShaderStageFragmentBit,
Module = _fragShader,
PName = (byte*)pname,
};
var stages = new[] { vertShaderStageInfo, fragShaderStageInfo };
var bindingDescription = Vertex.VertexInputBindingDescription;
var attributeDescription = Vertex.VertexInputAttributeDescription;
fixed (VertexInputAttributeDescription* attrPtr = attributeDescription)
{
var vertextInputInfo = new PipelineVertexInputStateCreateInfo()
{
SType = StructureType.PipelineVertexInputStateCreateInfo,
VertexAttributeDescriptionCount = (uint)attributeDescription.Length,
VertexBindingDescriptionCount = 1,
PVertexAttributeDescriptions = attrPtr,
PVertexBindingDescriptions = &bindingDescription
};
var inputAssembly = new PipelineInputAssemblyStateCreateInfo()
{
SType = StructureType.PipelineInputAssemblyStateCreateInfo,
Topology = PrimitiveTopology.TriangleList,
PrimitiveRestartEnable = false
};
var viewport = new Viewport()
{
X = 0,
Y = 0,
Width = (float)size.Width,
Height = (float)size.Height,
MinDepth = 0,
MaxDepth = 1
};
var scissor = new Rect2D()
{
Offset = new Offset2D(0, 0), Extent = new Extent2D((uint)viewport.Width, (uint)viewport.Height)
};
var pipelineViewPortCreateInfo = new PipelineViewportStateCreateInfo()
{
SType = StructureType.PipelineViewportStateCreateInfo,
ViewportCount = 1,
PViewports = &viewport,
ScissorCount = 1,
PScissors = &scissor
};
var rasterizerStateCreateInfo = new PipelineRasterizationStateCreateInfo()
{
SType = StructureType.PipelineRasterizationStateCreateInfo,
DepthClampEnable = false,
RasterizerDiscardEnable = false,
PolygonMode = PolygonMode.Fill,
LineWidth = 1,
CullMode = CullModeFlags.CullModeNone,
DepthBiasEnable = false
};
var multisampleStateCreateInfo = new PipelineMultisampleStateCreateInfo()
{
SType = StructureType.PipelineMultisampleStateCreateInfo,
SampleShadingEnable = false,
RasterizationSamples = SampleCountFlags.SampleCount1Bit
};
var depthStencilCreateInfo = new PipelineDepthStencilStateCreateInfo()
{
SType = StructureType.PipelineDepthStencilStateCreateInfo,
StencilTestEnable = false,
DepthCompareOp = CompareOp.Less,
DepthTestEnable = true,
DepthWriteEnable = true,
DepthBoundsTestEnable = false,
};
var colorBlendAttachmentState = new PipelineColorBlendAttachmentState()
{
ColorWriteMask = ColorComponentFlags.ColorComponentABit |
ColorComponentFlags.ColorComponentRBit |
ColorComponentFlags.ColorComponentGBit |
ColorComponentFlags.ColorComponentBBit,
BlendEnable = false
};
var colorBlendState = new PipelineColorBlendStateCreateInfo()
{
SType = StructureType.PipelineColorBlendStateCreateInfo,
LogicOpEnable = false,
AttachmentCount = 1,
PAttachments = &colorBlendAttachmentState
};
var dynamicStates = new DynamicState[] { DynamicState.Viewport, DynamicState.Scissor };
fixed (DynamicState* states = dynamicStates)
{
var dynamicStateCreateInfo = new PipelineDynamicStateCreateInfo()
{
SType = StructureType.PipelineDynamicStateCreateInfo,
DynamicStateCount = (uint)dynamicStates.Length,
PDynamicStates = states
};
var vertexPushConstantRange = new PushConstantRange()
{
Offset = 0,
Size = (uint)Marshal.SizeOf<VertextPushConstant>(),
StageFlags = ShaderStageFlags.ShaderStageVertexBit
};
var fragPushConstantRange = new PushConstantRange()
{
//Offset = vertexPushConstantRange.Size,
Size = (uint)Marshal.SizeOf<VertextPushConstant>(),
StageFlags = ShaderStageFlags.ShaderStageFragmentBit
};
var layoutBindingInfo = new DescriptorSetLayoutBinding
{
Binding = 0,
StageFlags = ShaderStageFlags.VertexBit,
DescriptorCount = 1,
DescriptorType = DescriptorType.UniformBuffer,
};
var layoutInfo = new DescriptorSetLayoutCreateInfo
{
SType = StructureType.DescriptorSetLayoutCreateInfo,
BindingCount = 1,
PBindings = &layoutBindingInfo
};
api.CreateDescriptorSetLayout(device, &layoutInfo, null, out _descriptorSetLayout).ThrowOnError();
var projView = view * projection;
VulkanBufferHelper.AllocateBuffer<UniformBuffer>(_context, BufferUsageFlags.UniformBufferBit,
out _uniformBuffer,
out _uniformBufferMemory, new[]
{
new UniformBuffer
{
Projection = projView
}
});
var descriptorSetLayout = _descriptorSetLayout;
var descriptorCreateInfo = new DescriptorSetAllocateInfo
{
SType = StructureType.DescriptorSetAllocateInfo,
DescriptorPool = _context.DescriptorPool,
DescriptorSetCount = 1,
PSetLayouts = &descriptorSetLayout
};
api.AllocateDescriptorSets(device, &descriptorCreateInfo, out _descriptorSet).ThrowOnError();
var descriptorBufferInfo = new DescriptorBufferInfo
{
Buffer = _uniformBuffer,
Range = (ulong)Unsafe.SizeOf<UniformBuffer>(),
};
var descriptorWrite = new WriteDescriptorSet
{
SType = StructureType.WriteDescriptorSet,
DstSet = _descriptorSet,
DescriptorType = DescriptorType.UniformBuffer,
DescriptorCount = 1,
PBufferInfo = &descriptorBufferInfo,
};
api.UpdateDescriptorSets(device, 1, &descriptorWrite, 0, null);
var constants = new[] { vertexPushConstantRange, fragPushConstantRange };
fixed (PushConstantRange* constant = constants)
{
var setLayout = _descriptorSetLayout;
var pipelineLayoutCreateInfo = new PipelineLayoutCreateInfo()
{
SType = StructureType.PipelineLayoutCreateInfo,
PushConstantRangeCount = (uint)constants.Length,
PPushConstantRanges = constant,
SetLayoutCount = 1,
PSetLayouts = &setLayout
};
api.CreatePipelineLayout(device, pipelineLayoutCreateInfo, null, out _pipelineLayout)
.ThrowOnError();
}
fixed (PipelineShaderStageCreateInfo* stPtr = stages)
{
var pipelineCreateInfo = new GraphicsPipelineCreateInfo()
{
SType = StructureType.GraphicsPipelineCreateInfo,
StageCount = 2,
PStages = stPtr,
PVertexInputState = &vertextInputInfo,
PInputAssemblyState = &inputAssembly,
PViewportState = &pipelineViewPortCreateInfo,
PRasterizationState = &rasterizerStateCreateInfo,
PMultisampleState = &multisampleStateCreateInfo,
PDepthStencilState = &depthStencilCreateInfo,
PColorBlendState = &colorBlendState,
PDynamicState = &dynamicStateCreateInfo,
Layout = _pipelineLayout,
RenderPass = _renderPass,
Subpass = 0,
BasePipelineHandle = _pipeline.Handle != 0 ? _pipeline : new Pipeline(),
BasePipelineIndex = _pipeline.Handle != 0 ? 0 : -1
};
api.CreateGraphicsPipelines(device, new PipelineCache(), 1, &pipelineCreateInfo, null,
out _pipeline).ThrowOnError();
}
}
}
Marshal.FreeHGlobal(pname);
_isInit = true;
}
private unsafe void CreateBuffers()
{
VulkanBufferHelper.AllocateBuffer<Vertex>(_context, BufferUsageFlags.VertexBufferBit, out _vertexBuffer,
out _vertexBufferMemory, _points);
VulkanBufferHelper.AllocateBuffer<ushort>(_context, BufferUsageFlags.IndexBufferBit, out _indexBuffer,
out _indexBufferMemory, _indices);
}
private static int FindSuitableMemoryTypeIndex(Vk api, PhysicalDevice physicalDevice, uint memoryTypeBits,
MemoryPropertyFlags flags)
{
api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
for (var i = 0; i < properties.MemoryTypeCount; i++)
{
var type = properties.MemoryTypes[i];
if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags)) return i;
}
return -1;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct Vertex
{
public Vector3 Position;
public Vector3 Normal;
public static unsafe VertexInputBindingDescription VertexInputBindingDescription
{
get
{
return new VertexInputBindingDescription()
{
Binding = 0,
Stride = (uint)Marshal.SizeOf<Vertex>(),
InputRate = VertexInputRate.Vertex
};
}
}
public static unsafe VertexInputAttributeDescription[] VertexInputAttributeDescription
{
get
{
return new VertexInputAttributeDescription[]
{
new VertexInputAttributeDescription
{
Binding = 0,
Location = 0,
Format = Format.R32G32B32Sfloat,
Offset = (uint)Marshal.OffsetOf<Vertex>("Position")
},
new VertexInputAttributeDescription
{
Binding = 0,
Location = 1,
Format = Format.R32G32B32Sfloat,
Offset = (uint)Marshal.OffsetOf<Vertex>("Normal")
}
};
}
}
}
private readonly Vertex[] _points;
private readonly ushort[] _indices;
private readonly float _minY;
private readonly float _maxY;
static Stopwatch St = Stopwatch.StartNew();
private bool _isInit;
private VulkanImage _colorAttachment;
private DescriptorSet _descriptorSet;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct VertextPushConstant
{
public float MaxY;
public float MinY;
public float Time;
public float Disco;
public Matrix4x4 Model;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct UniformBuffer
{
public Matrix4x4 Projection;
}
}

335
samples/GpuInterop/VulkanDemo/VulkanContext.cs

@ -0,0 +1,335 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Vulkan;
using Silk.NET.Core;
using Silk.NET.Core.Native;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.EXT;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
using SkiaSharp;
using D3DDevice = SharpDX.Direct3D11.Device;
using DxgiDevice = SharpDX.DXGI.Device;
namespace GpuInterop.VulkanDemo;
public unsafe class VulkanContext : IDisposable
{
public Vk Api { get; init; }
public Instance Instance { get; init; }
public PhysicalDevice PhysicalDevice { get; init; }
public Device Device { get; init; }
public Queue Queue { get; init; }
public uint QueueFamilyIndex { get; init; }
public VulkanCommandBufferPool Pool { get; init; }
public GRContext GrContext { get; init; }
public DescriptorPool DescriptorPool { get; init; }
public D3DDevice? D3DDevice { get; init; }
public static (VulkanContext? result, string info) TryCreate(ICompositionGpuInterop gpuInterop)
{
using var appName = new ByteString("GpuInterop");
using var engineName = new ByteString("Test");
var applicationInfo = new ApplicationInfo
{
SType = StructureType.ApplicationInfo,
PApplicationName = appName,
ApiVersion = new Version32(1, 1, 0),
PEngineName = appName,
EngineVersion = new Version32(1, 0, 0),
ApplicationVersion = new Version32(1, 0, 0)
};
var enabledExtensions = new List<string>()
{
"VK_KHR_get_physical_device_properties2",
"VK_KHR_external_memory_capabilities",
"VK_KHR_external_semaphore_capabilities"
};
var enabledLayers = new List<string>();
Vk api = Vk.GetApi();
enabledExtensions.Add("VK_EXT_debug_utils");
if (IsLayerAvailable(api, "VK_LAYER_KHRONOS_validation"))
enabledLayers.Add("VK_LAYER_KHRONOS_validation");
Instance vkInstance = default;
Silk.NET.Vulkan.PhysicalDevice physicalDevice = default;
Device device = default;
DescriptorPool descriptorPool = default;
VulkanCommandBufferPool? pool = null;
GRContext? grContext = null;
try
{
using var pRequiredExtensions = new ByteStringList(enabledExtensions);
using var pEnabledLayers = new ByteStringList(enabledLayers);
api.CreateInstance(new InstanceCreateInfo
{
SType = StructureType.InstanceCreateInfo,
PApplicationInfo = &applicationInfo,
PpEnabledExtensionNames = pRequiredExtensions,
EnabledExtensionCount = pRequiredExtensions.UCount,
PpEnabledLayerNames = pEnabledLayers,
EnabledLayerCount = pEnabledLayers.UCount
}, null, out vkInstance).ThrowOnError();
if (api.TryGetInstanceExtension(vkInstance, out ExtDebugUtils debugUtils))
{
var debugCreateInfo = new DebugUtilsMessengerCreateInfoEXT
{
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt |
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
MessageType = DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt |
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(LogCallback),
};
debugUtils.CreateDebugUtilsMessenger(vkInstance, debugCreateInfo, null, out var messenger);
}
var requireDeviceExtensions = new List<string>
{
"VK_KHR_external_memory",
"VK_KHR_external_semaphore"
};
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (!gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
.D3D11TextureGlobalSharedHandle)
)
return (null, "Image sharing is not supported by the current backend");
requireDeviceExtensions.Add(KhrExternalMemoryWin32.ExtensionName);
requireDeviceExtensions.Add(KhrExternalSemaphoreWin32.ExtensionName);
requireDeviceExtensions.Add("VK_KHR_dedicated_allocation");
requireDeviceExtensions.Add("VK_KHR_get_memory_requirements2");
}
else
{
if (!gpuInterop.SupportedImageHandleTypes.Contains(KnownPlatformGraphicsExternalImageHandleTypes
.VulkanOpaquePosixFileDescriptor)
|| !gpuInterop.SupportedSemaphoreTypes.Contains(KnownPlatformGraphicsExternalSemaphoreHandleTypes
.VulkanOpaquePosixFileDescriptor)
)
return (null, "Image sharing is not supported by the current backend");
requireDeviceExtensions.Add(KhrExternalMemoryFd.ExtensionName);
requireDeviceExtensions.Add(KhrExternalSemaphoreFd.ExtensionName);
}
uint count = 0;
api.EnumeratePhysicalDevices(vkInstance, ref count, null).ThrowOnError();
var physicalDevices = stackalloc PhysicalDevice[(int)count];
api.EnumeratePhysicalDevices(vkInstance, ref count, physicalDevices)
.ThrowOnError();
for (uint c = 0; c < count; c++)
{
if (requireDeviceExtensions.Any(ext => !api.IsDeviceExtensionPresent(physicalDevices[c], ext)))
continue;
var physicalDeviceIDProperties = new PhysicalDeviceIDProperties()
{
SType = StructureType.PhysicalDeviceIDProperties
};
var physicalDeviceProperties2 = new PhysicalDeviceProperties2()
{
SType = StructureType.PhysicalDeviceProperties2,
PNext = &physicalDeviceIDProperties
};
api.GetPhysicalDeviceProperties2(physicalDevices[c], &physicalDeviceProperties2);
if (gpuInterop.DeviceLuid != null && physicalDeviceIDProperties.DeviceLuidvalid)
{
if (!new Span<byte>(physicalDeviceIDProperties.DeviceLuid, 8)
.SequenceEqual(gpuInterop.DeviceLuid))
continue;
}
else if (gpuInterop.DeviceUuid != null)
{
if (!new Span<byte>(physicalDeviceIDProperties.DeviceUuid, 16)
.SequenceEqual(gpuInterop?.DeviceUuid))
continue;
}
physicalDevice = physicalDevices[c];
var name = Marshal.PtrToStringAnsi(new IntPtr(physicalDeviceProperties2.Properties.DeviceName))!;
uint queueFamilyCount = 0;
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, null);
var familyProperties = stackalloc QueueFamilyProperties[(int)queueFamilyCount];
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, familyProperties);
for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++)
{
var family = familyProperties[c];
if (!family.QueueFlags.HasAllFlags(QueueFlags.GraphicsBit))
continue;
var queuePriorities = stackalloc float[(int)family.QueueCount];
for (var i = 0; i < family.QueueCount; i++)
queuePriorities[i] = 1f;
var features = new PhysicalDeviceFeatures();
var queueCreateInfo = new DeviceQueueCreateInfo
{
SType = StructureType.DeviceQueueCreateInfo,
QueueFamilyIndex = queueFamilyIndex,
QueueCount = family.QueueCount,
PQueuePriorities = queuePriorities
};
using var pEnabledDeviceExtensions = new ByteStringList(requireDeviceExtensions);
var deviceCreateInfo = new DeviceCreateInfo
{
SType = StructureType.DeviceCreateInfo,
QueueCreateInfoCount = 1,
PQueueCreateInfos = &queueCreateInfo,
PpEnabledExtensionNames = pEnabledDeviceExtensions,
EnabledExtensionCount = pEnabledDeviceExtensions.UCount,
PEnabledFeatures = &features
};
api.CreateDevice(physicalDevice, in deviceCreateInfo, null, out device)
.ThrowOnError();
api.GetDeviceQueue(device, queueFamilyIndex, 0, out var queue);
var descriptorPoolSize = new DescriptorPoolSize
{
Type = DescriptorType.UniformBuffer, DescriptorCount = 16
};
var descriptorPoolInfo = new DescriptorPoolCreateInfo
{
SType = StructureType.DescriptorPoolCreateInfo,
PoolSizeCount = 1,
PPoolSizes = &descriptorPoolSize,
MaxSets = 16,
Flags = DescriptorPoolCreateFlags.FreeDescriptorSetBit
};
api.CreateDescriptorPool(device, &descriptorPoolInfo, null, out descriptorPool)
.ThrowOnError();
pool = new VulkanCommandBufferPool(api, device, queue, queueFamilyIndex);
grContext = GRContext.CreateVulkan(new GRVkBackendContext
{
VkInstance = vkInstance.Handle,
VkDevice = device.Handle,
VkQueue = queue.Handle,
GraphicsQueueIndex = queueFamilyIndex,
VkPhysicalDevice = physicalDevice.Handle,
GetProcedureAddress = (proc, _, _) =>
{
var rv = api.GetDeviceProcAddr(device, proc);
if (rv != IntPtr.Zero)
return rv;
rv = api.GetInstanceProcAddr(vkInstance, proc);
if (rv != IntPtr.Zero)
return rv;
return api.GetInstanceProcAddr(default, proc);
}
});
D3DDevice? d3dDevice = null;
if (physicalDeviceIDProperties.DeviceLuidvalid &&
RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
d3dDevice = D3DMemoryHelper.CreateDeviceByLuid(
new Span<byte>(physicalDeviceIDProperties.DeviceLuid, 8));
var dxgiDevice = d3dDevice?.QueryInterface<DxgiDevice>();
return (new VulkanContext
{
Api = api,
Device = device,
Instance = vkInstance,
PhysicalDevice = physicalDevice,
Queue = queue,
QueueFamilyIndex = queueFamilyIndex,
Pool = pool,
DescriptorPool = descriptorPool,
GrContext = grContext,
D3DDevice = d3dDevice
}, name);
}
return (null, "No suitable device queue found");
}
return (null, "Suitable device not found");
}
catch (Exception e)
{
return (null, e.ToString());
}
finally
{
if (grContext == null && api != null)
{
pool?.Dispose();
if (descriptorPool.Handle != default)
api.DestroyDescriptorPool(device, descriptorPool, null);
if (device.Handle != default)
api.DestroyDevice(device, null);
}
}
}
private static unsafe bool IsLayerAvailable(Vk api, string layerName)
{
uint layerPropertiesCount;
api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError();
var layerProperties = new LayerProperties[layerPropertiesCount];
fixed (LayerProperties* pLayerProperties = layerProperties)
{
api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError();
for (var i = 0; i < layerPropertiesCount; i++)
{
var currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName);
if (currentLayerName == layerName) return true;
}
}
return false;
}
private static unsafe uint LogCallback(DebugUtilsMessageSeverityFlagsEXT messageSeverity, DebugUtilsMessageTypeFlagsEXT messageTypes, DebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData)
{
if (messageSeverity != DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt)
{
var message = Marshal.PtrToStringAnsi((nint)pCallbackData->PMessage);
Console.WriteLine(message);
}
return Vk.False;
}
public void Dispose()
{
D3DDevice?.Dispose();
GrContext.Dispose();
Pool.Dispose();
Api.DestroyDescriptorPool(Device, DescriptorPool, null);
Api.DestroyDevice(Device, null);
}
}

101
samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs

@ -0,0 +1,101 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Silk.NET.Core;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
namespace GpuInterop.VulkanDemo;
public class VulkanDemoControl : DrawingSurfaceDemoBase
{
private Instance _vkInstance;
private Vk _api;
class VulkanResources : IAsyncDisposable
{
public VulkanContext Context { get; }
public VulkanSwapchain Swapchain { get; }
public VulkanContent Content { get; }
public VulkanResources(VulkanContext context, VulkanSwapchain swapchain, VulkanContent content)
{
Context = context;
Swapchain = swapchain;
Content = content;
}
public async ValueTask DisposeAsync()
{
Context.Pool.FreeUsedCommandBuffers();
Content.Dispose();
await Swapchain.DisposeAsync();
Context.Dispose();
}
}
protected override bool SupportsDisco => true;
private VulkanResources? _resources;
protected override (bool success, string info) InitializeGraphicsResources(Compositor compositor,
CompositionDrawingSurface compositionDrawingSurface, ICompositionGpuInterop gpuInterop)
{
var (context, info) = VulkanContext.TryCreate(gpuInterop);
if (context == null)
return (false, info);
try
{
var content = new VulkanContent(context);
_resources = new VulkanResources(context,
new VulkanSwapchain(context, gpuInterop, compositionDrawingSurface), content);
return (true, info);
}
catch(Exception e)
{
return (false, e.ToString());
}
}
protected override void FreeGraphicsResources()
{
_resources?.DisposeAsync();
_resources = null;
}
protected override unsafe void RenderFrame(PixelSize pixelSize)
{
if (_resources == null)
return;
using (_resources.Swapchain.BeginDraw(pixelSize, out var image))
{
/*
var commandBuffer = _resources.Context.Pool.CreateCommandBuffer();
commandBuffer.BeginRecording();
image.TransitionLayout(commandBuffer.InternalHandle, ImageLayout.TransferDstOptimal, AccessFlags.None);
var range = new ImageSubresourceRange
{
AspectMask = ImageAspectFlags.ColorBit,
LayerCount = 1,
LevelCount = 1,
BaseArrayLayer = 0,
BaseMipLevel = 0
};
var color = new ClearColorValue
{
Float32_0 = 1, Float32_1 = 0, Float32_2 = 0, Float32_3 = 1
};
_resources.Context.Api.CmdClearColorImage(commandBuffer.InternalHandle, image.InternalHandle.Value, ImageLayout.TransferDstOptimal,
&color, 1, &range);
commandBuffer.Submit();*/
_resources.Content.Render(image, Yaw, Pitch, Roll, Disco);
}
}
}

12
samples/GpuInterop/VulkanDemo/VulkanExtensions.cs

@ -0,0 +1,12 @@
using System;
using Silk.NET.Vulkan;
namespace SilkNetDemo;
public static class VulkanExtensions
{
public static void ThrowOnError(this Result result)
{
if (result != Result.Success) throw new Exception($"Unexpected API error \"{result}\".");
}
}

276
samples/GpuInterop/VulkanDemo/VulkanImage.cs

@ -0,0 +1,276 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Vulkan;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
using SkiaSharp;
namespace GpuInterop.VulkanDemo;
public unsafe class VulkanImage : IDisposable
{
private readonly VulkanContext _vk;
private readonly Instance _instance;
private readonly Device _device;
private readonly PhysicalDevice _physicalDevice;
private readonly VulkanCommandBufferPool _commandBufferPool;
private ImageLayout _currentLayout;
private AccessFlags _currentAccessFlags;
private ImageUsageFlags _imageUsageFlags { get; }
private ImageView? _imageView { get; set; }
private DeviceMemory _imageMemory { get; set; }
private SharpDX.Direct3D11.Texture2D? _d3dTexture2D;
private IntPtr _win32ShareHandle;
internal Image? InternalHandle { get; private set; }
internal Format Format { get; }
internal ImageAspectFlags AspectFlags { get; private set; }
public ulong Handle => InternalHandle?.Handle ?? 0;
public ulong ViewHandle => _imageView?.Handle ?? 0;
public uint UsageFlags => (uint) _imageUsageFlags;
public ulong MemoryHandle => _imageMemory.Handle;
public DeviceMemory DeviceMemory => _imageMemory;
public uint MipLevels { get; private set; }
public Vk Api { get; }
public PixelSize Size { get; }
public ulong MemorySize { get; private set; }
public uint CurrentLayout => (uint) _currentLayout;
public VulkanImage(VulkanContext vk, uint format, PixelSize size,
bool exportable, uint mipLevels = 0)
{
_vk = vk;
_instance = vk.Instance;
_device = vk.Device;
_physicalDevice = vk.PhysicalDevice;
_commandBufferPool = vk.Pool;
Format = (Format)format;
Api = vk.Api;
Size = size;
MipLevels = 1;//mipLevels;
_imageUsageFlags =
ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit |
ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageSampledBit;
//MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2));
var handleType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit :
ExternalMemoryHandleTypeFlags.OpaqueFDBit;
var externalMemoryCreateInfo = new ExternalMemoryImageCreateInfo
{
SType = StructureType.ExternalMemoryImageCreateInfo,
HandleTypes = handleType
};
var imageCreateInfo = new ImageCreateInfo
{
PNext = exportable ? &externalMemoryCreateInfo : null,
SType = StructureType.ImageCreateInfo,
ImageType = ImageType.ImageType2D,
Format = Format,
Extent =
new Extent3D((uint?)Size.Width,
(uint?)Size.Height, 1),
MipLevels = MipLevels,
ArrayLayers = 1,
Samples = SampleCountFlags.SampleCount1Bit,
Tiling = Tiling,
Usage = _imageUsageFlags,
SharingMode = SharingMode.Exclusive,
InitialLayout = ImageLayout.Undefined,
Flags = ImageCreateFlags.ImageCreateMutableFormatBit
};
Api
.CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError();
InternalHandle = image;
Api.GetImageMemoryRequirements(_device, InternalHandle.Value,
out var memoryRequirements);
var fdExport = new ExportMemoryAllocateInfo
{
HandleTypes = handleType, SType = StructureType.ExportMemoryAllocateInfo
};
var dedicatedAllocation = new MemoryDedicatedAllocateInfoKHR
{
SType = StructureType.MemoryDedicatedAllocateInfoKhr,
Image = image
};
ImportMemoryWin32HandleInfoKHR handleImport = default;
if (exportable && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(vk.D3DDevice, size, Format);
using var dxgi = _d3dTexture2D.QueryInterface<SharpDX.DXGI.Resource>();
_win32ShareHandle = dxgi.SharedHandle;
handleImport = new ImportMemoryWin32HandleInfoKHR
{
PNext = &dedicatedAllocation,
SType = StructureType.ImportMemoryWin32HandleInfoKhr,
HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit,
Handle = _win32ShareHandle,
};
}
var memoryAllocateInfo = new MemoryAllocateInfo
{
PNext =
exportable ? RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? &handleImport : &fdExport : null,
SType = StructureType.MemoryAllocateInfo,
AllocationSize = memoryRequirements.Size,
MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex(
Api,
_physicalDevice,
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit)
};
Api.AllocateMemory(_device, memoryAllocateInfo, null,
out var imageMemory).ThrowOnError();
_imageMemory = imageMemory;
MemorySize = memoryRequirements.Size;
Api.BindImageMemory(_device, InternalHandle.Value, _imageMemory, 0).ThrowOnError();
var componentMapping = new ComponentMapping(
ComponentSwizzle.Identity,
ComponentSwizzle.Identity,
ComponentSwizzle.Identity,
ComponentSwizzle.Identity);
AspectFlags = ImageAspectFlags.ImageAspectColorBit;
var subresourceRange = new ImageSubresourceRange(AspectFlags, 0, MipLevels, 0, 1);
var imageViewCreateInfo = new ImageViewCreateInfo
{
SType = StructureType.ImageViewCreateInfo,
Image = InternalHandle.Value,
ViewType = ImageViewType.ImageViewType2D,
Format = Format,
Components = componentMapping,
SubresourceRange = subresourceRange
};
Api
.CreateImageView(_device, imageViewCreateInfo, null, out var imageView)
.ThrowOnError();
_imageView = imageView;
_currentLayout = ImageLayout.Undefined;
TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
}
public int ExportFd()
{
if (!Api.TryGetDeviceExtension<KhrExternalMemoryFd>(_instance, _device, out var ext))
throw new InvalidOperationException();
var info = new MemoryGetFdInfoKHR
{
Memory = _imageMemory,
SType = StructureType.MemoryGetFDInfoKhr,
HandleType = ExternalMemoryHandleTypeFlags.OpaqueFDBit
};
ext.GetMemoryF(_device, info, out var fd).ThrowOnError();
return fd;
}
public IPlatformHandle Export() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
new PlatformHandle(_win32ShareHandle,
KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle) :
new PlatformHandle(new IntPtr(ExportFd()),
KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
public ImageTiling Tiling => ImageTiling.Optimal;
internal void TransitionLayout(CommandBuffer commandBuffer,
ImageLayout fromLayout, AccessFlags fromAccessFlags,
ImageLayout destinationLayout, AccessFlags destinationAccessFlags)
{
VulkanMemoryHelper.TransitionLayout(Api, commandBuffer, InternalHandle.Value,
fromLayout,
fromAccessFlags,
destinationLayout, destinationAccessFlags,
MipLevels);
_currentLayout = destinationLayout;
_currentAccessFlags = destinationAccessFlags;
}
internal void TransitionLayout(CommandBuffer commandBuffer,
ImageLayout destinationLayout, AccessFlags destinationAccessFlags)
=> TransitionLayout(commandBuffer, _currentLayout, _currentAccessFlags, destinationLayout,
destinationAccessFlags);
internal void TransitionLayout(ImageLayout destinationLayout, AccessFlags destinationAccessFlags)
{
var commandBuffer = _commandBufferPool.CreateCommandBuffer();
commandBuffer.BeginRecording();
TransitionLayout(commandBuffer.InternalHandle, destinationLayout, destinationAccessFlags);
commandBuffer.EndRecording();
commandBuffer.Submit();
}
public void TransitionLayout(uint destinationLayout, uint destinationAccessFlags)
{
TransitionLayout((ImageLayout)destinationLayout, (AccessFlags)destinationAccessFlags);
}
public unsafe void Dispose()
{
Api.DestroyImageView(_device, _imageView.Value, null);
Api.DestroyImage(_device, InternalHandle.Value, null);
Api.FreeMemory(_device, _imageMemory, null);
_imageView = default;
InternalHandle = default;
_imageMemory = default;
}
public void SaveTexture(string path)
{
_vk.GrContext.ResetContext();
var _image = this;
var imageInfo = new GRVkImageInfo()
{
CurrentQueueFamily = _vk.QueueFamilyIndex,
Format = (uint)_image.Format,
Image = _image.Handle,
ImageLayout = (uint)_image.CurrentLayout,
ImageTiling = (uint)_image.Tiling,
ImageUsageFlags = (uint)_image.UsageFlags,
LevelCount = _image.MipLevels,
SampleCount = 1,
Protected = false,
Alloc = new GRVkAlloc()
{
Memory = _image.MemoryHandle, Flags = 0, Offset = 0, Size = _image.MemorySize
}
};
using (var backendTexture = new GRBackendRenderTarget(_image.Size.Width, _image.Size.Height, 1,
imageInfo))
using (var surface = SKSurface.Create(_vk.GrContext, backendTexture,
GRSurfaceOrigin.TopLeft,
SKColorType.Rgba8888, SKColorSpace.CreateSrgb()))
{
using var snap = surface.Snapshot();
using var encoded = snap.Encode();
using (var s = File.Create(path))
encoded.SaveTo(s);
}
}
}

59
samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs

@ -0,0 +1,59 @@
using Silk.NET.Vulkan;
namespace GpuInterop.VulkanDemo;
internal static class VulkanMemoryHelper
{
internal static int FindSuitableMemoryTypeIndex(Vk api, PhysicalDevice physicalDevice, uint memoryTypeBits,
MemoryPropertyFlags flags)
{
api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
for (var i = 0; i < properties.MemoryTypeCount; i++)
{
var type = properties.MemoryTypes[i];
if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags)) return i;
}
return -1;
}
internal static unsafe void TransitionLayout(
Vk api,
CommandBuffer commandBuffer,
Image image,
ImageLayout sourceLayout,
AccessFlags sourceAccessMask,
ImageLayout destinationLayout,
AccessFlags destinationAccessMask,
uint mipLevels)
{
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, mipLevels, 0, 1);
var barrier = new ImageMemoryBarrier
{
SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = sourceAccessMask,
DstAccessMask = destinationAccessMask,
OldLayout = sourceLayout,
NewLayout = destinationLayout,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = image,
SubresourceRange = subresourceRange
};
api.CmdPipelineBarrier(
commandBuffer,
PipelineStageFlags.PipelineStageAllCommandsBit,
PipelineStageFlags.PipelineStageAllCommandsBit,
0,
0,
null,
0,
null,
1,
barrier);
}
}

58
samples/GpuInterop/VulkanDemo/VulkanSemaphorePair.cs

@ -0,0 +1,58 @@
using System;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
namespace GpuInterop.VulkanDemo;
class VulkanSemaphorePair : IDisposable
{
private readonly VulkanContext _resources;
public unsafe VulkanSemaphorePair(VulkanContext resources, bool exportable)
{
_resources = resources;
var semaphoreExportInfo = new ExportSemaphoreCreateInfo
{
SType = StructureType.ExportSemaphoreCreateInfo,
HandleTypes = ExternalSemaphoreHandleTypeFlags.OpaqueFDBit
};
var semaphoreCreateInfo = new SemaphoreCreateInfo
{
SType = StructureType.SemaphoreCreateInfo,
PNext = exportable ? &semaphoreExportInfo : null
};
resources.Api.CreateSemaphore(resources.Device, semaphoreCreateInfo, null, out var semaphore).ThrowOnError();
ImageAvailableSemaphore = semaphore;
resources.Api.CreateSemaphore(resources.Device, semaphoreCreateInfo, null, out semaphore).ThrowOnError();
RenderFinishedSemaphore = semaphore;
}
public int ExportFd(bool renderFinished)
{
if (!_resources.Api.TryGetDeviceExtension<KhrExternalSemaphoreFd>(_resources.Instance, _resources.Device,
out var ext))
throw new InvalidOperationException();
var info = new SemaphoreGetFdInfoKHR()
{
SType = StructureType.SemaphoreGetFDInfoKhr,
Semaphore = renderFinished ? RenderFinishedSemaphore : ImageAvailableSemaphore,
HandleType = ExternalSemaphoreHandleTypeFlags.OpaqueFDBit
};
ext.GetSemaphoreF(_resources.Device, info, out var fd).ThrowOnError();
return fd;
}
internal Semaphore ImageAvailableSemaphore { get; }
internal Semaphore RenderFinishedSemaphore { get; }
public unsafe void Dispose()
{
_resources.Api.DestroySemaphore(_resources.Device, ImageAvailableSemaphore, null);
_resources.Api.DestroySemaphore(_resources.Device, RenderFinishedSemaphore, null);
}
}

154
samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs

@ -0,0 +1,154 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Vulkan;
using Metsys.Bson;
using Silk.NET.Vulkan;
using SkiaSharp;
namespace GpuInterop.VulkanDemo;
class VulkanSwapchain : SwapchainBase<VulkanSwapchainImage>
{
private readonly VulkanContext _vk;
public VulkanSwapchain(VulkanContext vk, ICompositionGpuInterop interop, CompositionDrawingSurface target) : base(interop, target)
{
_vk = vk;
}
protected override VulkanSwapchainImage CreateImage(PixelSize size)
{
return new VulkanSwapchainImage(_vk, size, Interop, Target);
}
public IDisposable BeginDraw(PixelSize size, out VulkanImage image)
{
_vk.Pool.FreeUsedCommandBuffers();
var rv = BeginDrawCore(size, out var swapchainImage);
image = swapchainImage.Image;
return rv;
}
}
class VulkanSwapchainImage : ISwapchainImage
{
private readonly VulkanContext _vk;
private readonly ICompositionGpuInterop _interop;
private readonly CompositionDrawingSurface _target;
private readonly VulkanImage _image;
private readonly VulkanSemaphorePair _semaphorePair;
private ICompositionImportedGpuSemaphore? _availableSemaphore, _renderCompletedSemaphore;
private ICompositionImportedGpuImage? _importedImage;
private Task? _lastPresent;
public VulkanImage Image => _image;
private bool _initial = true;
public VulkanSwapchainImage(VulkanContext vk, PixelSize size, ICompositionGpuInterop interop, CompositionDrawingSurface target)
{
_vk = vk;
_interop = interop;
_target = target;
Size = size;
_image = new VulkanImage(vk, (uint)Format.R8G8B8A8Unorm, size, true);
_semaphorePair = new VulkanSemaphorePair(vk, true);
}
public async ValueTask DisposeAsync()
{
if (LastPresent != null)
await LastPresent;
if (_importedImage != null)
await _importedImage.DisposeAsync();
if (_availableSemaphore != null)
await _availableSemaphore.DisposeAsync();
if (_renderCompletedSemaphore != null)
await _renderCompletedSemaphore.DisposeAsync();
_semaphorePair.Dispose();
_image.Dispose();
}
public PixelSize Size { get; }
public Task? LastPresent => _lastPresent;
public void BeginDraw()
{
var buffer = _vk.Pool.CreateCommandBuffer();
buffer.BeginRecording();
_image.TransitionLayout(buffer.InternalHandle,
ImageLayout.Undefined, AccessFlags.None,
ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessColorAttachmentReadBit);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
buffer.Submit(null,null,null, null, new VulkanCommandBufferPool.VulkanCommandBuffer.KeyedMutexSubmitInfo
{
AcquireKey = 0,
DeviceMemory = _image.DeviceMemory
});
else if (_initial)
{
_initial = false;
buffer.Submit();
}
else
buffer.Submit(new[] { _semaphorePair.ImageAvailableSemaphore },
new[]
{
PipelineStageFlags.AllGraphicsBit
});
}
public void Present()
{
var buffer = _vk.Pool.CreateCommandBuffer();
buffer.BeginRecording();
_image.TransitionLayout(buffer.InternalHandle, ImageLayout.TransferSrcOptimal, AccessFlags.TransferWriteBit);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
buffer.Submit(null, null, null, null,
new VulkanCommandBufferPool.VulkanCommandBuffer.KeyedMutexSubmitInfo
{
DeviceMemory = _image.DeviceMemory, ReleaseKey = 1
});
}
else
buffer.Submit(null, null, new[] { _semaphorePair.RenderFinishedSemaphore });
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_availableSemaphore ??= _interop.ImportSemaphore(new PlatformHandle(
new IntPtr(_semaphorePair.ExportFd(false)),
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor));
_renderCompletedSemaphore ??= _interop.ImportSemaphore(new PlatformHandle(
new IntPtr(_semaphorePair.ExportFd(true)),
KnownPlatformGraphicsExternalSemaphoreHandleTypes.VulkanOpaquePosixFileDescriptor));
}
_importedImage ??= _interop.ImportImage(_image.Export(),
new PlatformGraphicsExternalImageProperties
{
Format = PlatformGraphicsExternalImageFormat.R8G8B8A8UNorm,
Width = Size.Width,
Height = Size.Height,
MemorySize = _image.MemorySize
});
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
_lastPresent = _target.UpdateWithKeyedMutexAsync(_importedImage, 1, 0);
else
_lastPresent = _target.UpdateWithSemaphoresAsync(_importedImage, _renderCompletedSemaphore, _availableSemaphore);
}
}

4
samples/RenderDemo/App.xaml.cs

@ -29,10 +29,6 @@ namespace RenderDemo
.With(new Win32PlatformOptions
{
OverlayPopups = true,
})
.With(new X11PlatformOptions
{
UseCompositor = true
})
.UsePlatformDetect()
.LogToTrace();

4
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@ -168,9 +168,7 @@
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<ItemsPresenter Name="PART_ItemsPresenter"
HorizontalAlignment="Stretch"
ItemTemplate="{TemplateBinding ItemTemplate}"
Items="{TemplateBinding Items}">
HorizontalAlignment="Stretch">
<ItemsPresenter.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel x:Name="HamburgerItemsPanel"

5
samples/VirtualizationDemo/MainWindow.xaml

@ -9,10 +9,8 @@
<DockPanel LastChildFill="True" Margin="16">
<StackPanel DockPanel.Dock="Right"
Margin="16 0 0 0"
MinWidth="150"
Width="150"
Spacing="4">
<ComboBox Items="{Binding VirtualizationModes}"
SelectedItem="{Binding VirtualizationMode}"/>
<ComboBox Items="{Binding Orientations}"
SelectedItem="{Binding Orientation}"/>
<TextBox Watermark="Item Count"
@ -49,7 +47,6 @@
Items="{Binding Items}"
Selection="{Binding Selection}"
SelectionMode="Multiple"
VirtualizationMode="{Binding VirtualizationMode}"
ScrollViewer.HorizontalScrollBarVisibility="{Binding HorizontalScrollBarVisibility, Mode=TwoWay}"
ScrollViewer.VerticalScrollBarVisibility="{Binding VerticalScrollBarVisibility, Mode=TwoWay}">
<ListBox.ItemsPanel>

10
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -21,7 +21,6 @@ namespace VirtualizationDemo.ViewModels
private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
private Orientation _orientation = Orientation.Vertical;
private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple;
public MainWindowViewModel()
{
@ -81,15 +80,6 @@ namespace VirtualizationDemo.ViewModels
public IEnumerable<ScrollBarVisibility> ScrollBarVisibilities =>
Enum.GetValues(typeof(ScrollBarVisibility)).Cast<ScrollBarVisibility>();
public ItemVirtualizationMode VirtualizationMode
{
get { return _virtualizationMode; }
set { this.RaiseAndSetIfChanged(ref _virtualizationMode, value); }
}
public IEnumerable<ItemVirtualizationMode> VirtualizationModes =>
Enum.GetValues(typeof(ItemVirtualizationMode)).Cast<ItemVirtualizationMode>();
public MiniCommand AddItemCommand { get; private set; }
public MiniCommand RecreateCommand { get; private set; }
public MiniCommand RemoveItemCommand { get; private set; }

19
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -32,7 +32,6 @@ namespace Avalonia.Android
public static AndroidPlatformOptions Options { get; private set; }
internal static Compositor Compositor { get; private set; }
internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public static void Initialize()
{
@ -43,7 +42,7 @@ namespace Avalonia.Android
.Bind<ICursorFactory>().ToTransient<CursorFactory>()
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformSettings>().ToSingleton<AndroidPlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
.Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())
@ -55,16 +54,11 @@ namespace Avalonia.Android
EglPlatformGraphics.TryInitialize();
}
if (Options.UseCompositor)
{
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
AvaloniaLocator.Current.GetService<IPlatformGraphics>());
}
else
RenderInterface =
new PlatformRenderInterfaceContextManager(AvaloniaLocator.Current
.GetService<IPlatformGraphics>());
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
AvaloniaLocator.Current.GetService<IPlatformGraphics>());
}
}
@ -72,6 +66,5 @@ namespace Avalonia.Android
{
public bool UseDeferredRendering { get; set; } = false;
public bool UseGpu { get; set; } = true;
public bool UseCompositor { get; set; } = true;
}
}

27
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -1,22 +1,21 @@
using System;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Views;
using AndroidX.AppCompat.App;
using AndroidX.Lifecycle;
using AndroidRect = Android.Graphics.Rect;
namespace Avalonia.Android
{
public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler
public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService
{
internal static object ViewContent;
public Action<int, Result, Intent> ActivityResult { get; set; }
public Action<int, string[], Permission[]> RequestPermissionsResult { get; set; }
internal AvaloniaView View;
private GlobalLayoutListener _listener;
@ -56,9 +55,18 @@ namespace Avalonia.Android
}
}
public override void OnConfigurationChanged(Configuration newConfig)
public event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
public override void OnBackPressed()
{
base.OnConfigurationChanged(newConfig);
var eventArgs = new AndroidBackRequestedEventArgs();
BackRequested?.Invoke(this, eventArgs);
if (!eventArgs.Handled)
{
base.OnBackPressed();
}
}
protected override void OnDestroy()
@ -77,6 +85,13 @@ namespace Avalonia.Android
ActivityResult?.Invoke(requestCode, resultCode, data);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
RequestPermissionsResult?.Invoke(requestCode, permissions, grantResults);
}
class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
private AvaloniaView _view;

16
src/Android/Avalonia.Android/AvaloniaView.cs

@ -1,11 +1,14 @@
using System;
using Android.Content;
using Android.Content.Res;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Android.Platform;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Android
@ -26,6 +29,7 @@ namespace Avalonia.Android
_root.Prepare();
this.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
OnConfigurationChanged();
}
internal TopLevelImpl TopLevelImpl => _view;
@ -70,6 +74,18 @@ namespace Avalonia.Android
_timerSubscription?.Dispose();
}
}
protected override void OnConfigurationChanged(Configuration newConfig)
{
base.OnConfigurationChanged(newConfig);
OnConfigurationChanged();
}
private void OnConfigurationChanged()
{
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings;
settings?.OnViewConfigurationChanged(Context);
}
class ViewImpl : TopLevelImpl
{

3
src/Android/Avalonia.Android/IActivityResultHandler.cs

@ -1,11 +1,14 @@
using System;
using Android.App;
using Android.Content;
using Android.Content.PM;
namespace Avalonia.Android
{
public interface IActivityResultHandler
{
public Action<int, Result, Intent> ActivityResult { get; set; }
public Action<int, string[], Permission[]> RequestPermissionsResult { get; set; }
}
}

14
src/Android/Avalonia.Android/IAndroidNavigationService.cs

@ -0,0 +1,14 @@
using System;
namespace Avalonia.Android
{
public interface IActivityNavigationService
{
event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
}
public class AndroidBackRequestedEventArgs : EventArgs
{
public bool Handled { get; set; }
}
}

94
src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs

@ -0,0 +1,94 @@
using System;
using Android;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Provider;
using Android.Views.Accessibility;
using AndroidX.Core.Content.Resources;
using Avalonia.Media;
using Avalonia.Platform;
using Color = Avalonia.Media.Color;
namespace Avalonia.Android.Platform;
// TODO: ideally should be created per view/activity.
internal class AndroidPlatformSettings : DefaultPlatformSettings
{
private PlatformColorValues _latestValues;
public AndroidPlatformSettings()
{
_latestValues = base.GetColorValues();
}
public override PlatformColorValues GetColorValues()
{
return _latestValues;
}
internal void OnViewConfigurationChanged(Context context)
{
if (context.Resources?.Configuration is null)
{
return;
}
var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch
{
UiMode.NightYes => PlatformThemeVariant.Dark,
UiMode.NightNo => PlatformThemeVariant.Light,
_ => throw new ArgumentOutOfRangeException()
};
if (OperatingSystem.IsAndroidVersionAtLeast(31))
{
// See https://developer.android.com/reference/android/R.color
var accent1 = context.Resources.GetColor(17170494, context.Theme); // Resource.Color.SystemAccent1500
var accent2 = context.Resources.GetColor(17170507, context.Theme); // Resource.Color.SystemAccent2500
var accent3 = context.Resources.GetColor(17170520, context.Theme); // Resource.Color.SystemAccent3500
_latestValues = new PlatformColorValues
{
ThemeVariant = systemTheme,
ContrastPreference = IsHighContrast(context),
AccentColor1 = new Color(accent1.A, accent1.R, accent1.G, accent1.B),
AccentColor2 = new Color(accent2.A, accent2.R, accent2.G, accent2.B),
AccentColor3 = new Color(accent3.A, accent3.R, accent3.G, accent3.B),
};
}
else if (OperatingSystem.IsAndroidVersionAtLeast(23))
{
// See https://developer.android.com/reference/android/R.attr
var array = context.Theme.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent
var accent = array.GetColor(0, 0);
_latestValues = new PlatformColorValues
{
ThemeVariant = systemTheme,
ContrastPreference = IsHighContrast(context),
AccentColor1 = new Color(accent.A, accent.R, accent.G, accent.B)
};
array.Recycle();
}
else
{
_latestValues = _latestValues with { ThemeVariant = systemTheme };
}
OnColorValuesChanged(_latestValues);
}
private static ColorContrastPreference IsHighContrast(Context context)
{
try
{
return Settings.Secure.GetInt(context.ContentResolver, "high_text_contrast_enabled", 0) == 1
? ColorContrastPreference.High : ColorContrastPreference.NoPreference;
}
catch
{
return ColorContrastPreference.NoPreference;
}
}
}

28
src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs

@ -0,0 +1,28 @@
using System;
using Avalonia.Interactivity;
using Avalonia.Platform;
namespace Avalonia.Android.Platform
{
internal class AndroidSystemNavigationManager : ISystemNavigationManager
{
public event EventHandler<RoutedEventArgs> BackRequested;
public AndroidSystemNavigationManager(IActivityNavigationService? navigationService)
{
if(navigationService != null)
{
navigationService.BackRequested += OnBackRequested;
}
}
private void OnBackRequested(object sender, AndroidBackRequestedEventArgs e)
{
var routedEventArgs = new RoutedEventArgs();
BackRequested?.Invoke(this, routedEventArgs);
e.Handled = routedEventArgs.Handled;
}
}
}

52
src/Android/Avalonia.Android/Platform/PlatformSupport.cs

@ -0,0 +1,52 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
namespace Avalonia.Android.Platform;
internal static class PlatformSupport
{
private static int s_lastRequestCode = 20000;
public static int GetNextRequestCode() => s_lastRequestCode++;
public static async Task<bool> CheckPermission(this Activity activity, string permission)
{
if (activity is not IActivityResultHandler mainActivity)
{
throw new InvalidOperationException("Main activity must implement IActivityResultHandler interface.");
}
if (!OperatingSystem.IsAndroidVersionAtLeast(23))
{
return true;
}
if (activity.CheckSelfPermission(permission) == Permission.Granted)
{
return true;
}
var currentRequestCode = GetNextRequestCode();
var tcs = new TaskCompletionSource<bool>();
mainActivity.RequestPermissionsResult += RequestPermissionsResult;
activity.RequestPermissions(new [] { permission }, currentRequestCode);
return await tcs.Task;
void RequestPermissionsResult(int requestCode, string[] arg2, Permission[] arg3)
{
if (currentRequestCode != requestCode)
{
return;
}
mainActivity.RequestPermissionsResult -= RequestPermissionsResult;
_ = tcs.TrySetResult(arg3.All(p => p == Permission.Granted));
}
}
}

24
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -26,12 +26,14 @@ using Avalonia.Rendering.Composition;
using Java.Lang;
using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Window = Android.Views.Window;
using Android.Graphics.Drawables;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
ITopLevelWithSystemNavigationManager
{
private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer;
@ -57,6 +59,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
SystemNavigationManager = new AndroidSystemNavigationManager(avaloniaView.Context as IActivityNavigationService);
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@ -105,16 +109,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle };
public IRenderer CreateRenderer(IRenderRoot root) =>
AndroidPlatform.Options.UseCompositor
? new CompositingRenderer(root, AndroidPlatform.Compositor, () => Surfaces)
: AndroidPlatform.Options.UseDeferredRendering
? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
() => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces),
AndroidPlatform.RenderInterface)
{ RenderOnlyOnRenderThread = true }
: new ImmediateRenderer((Visual)root,
() => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces),
AndroidPlatform.RenderInterface);
new CompositingRenderer(root, AndroidPlatform.Compositor, () => Surfaces);
public virtual void Hide()
{
@ -286,6 +281,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public WindowTransparencyLevel TransparencyLevel { get; private set; }
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
// TODO adjust status bar depending on full screen mode.
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
@ -300,6 +300,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IStorageProvider StorageProvider { get; }
public ISystemNavigationManager SystemNavigationManager { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
if (TransparencyLevel != transparencyLevel)

101
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

@ -2,10 +2,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Android;
using Android.App;
using Android.Content;
using Android.Provider;
using Avalonia.Logging;
@ -19,41 +20,48 @@ namespace Avalonia.Android.Platform.Storage;
internal abstract class AndroidStorageItem : IStorageBookmarkItem
{
private Context? _context;
private Activity? _activity;
private readonly bool _needsExternalFilesPermission;
protected AndroidStorageItem(Context context, AndroidUri uri)
protected AndroidStorageItem(Activity activity, AndroidUri uri, bool needsExternalFilesPermission)
{
_context = context;
_activity = activity;
_needsExternalFilesPermission = needsExternalFilesPermission;
Uri = uri;
}
internal AndroidUri Uri { get; }
protected Activity Activity => _activity ?? throw new ObjectDisposedException(nameof(AndroidStorageItem));
protected Context Context => _context ?? throw new ObjectDisposedException(nameof(AndroidStorageItem));
public string Name => GetColumnValue(Context, Uri, MediaStore.IMediaColumns.DisplayName)
public virtual string Name => GetColumnValue(Activity, Uri, MediaStore.IMediaColumns.DisplayName)
?? Uri.PathSegments?.LastOrDefault() ?? string.Empty;
public Uri Path => new(Uri.ToString()!);
public bool CanBookmark => true;
public Task<string?> SaveBookmarkAsync()
public async Task<string?> SaveBookmarkAsync()
{
Context.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Task.FromResult(Uri.ToString());
}
if (!await EnsureExternalFilesPermission(false))
{
return null;
}
public Task ReleaseBookmarkAsync()
{
Context.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Task.CompletedTask;
Activity.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Uri.ToString();
}
public bool TryGetUri([NotNullWhen(true)] out Uri? uri)
public async Task ReleaseBookmarkAsync()
{
uri = new Uri(Uri.ToString()!);
return true;
}
if (!await EnsureExternalFilesPermission(false))
{
return;
}
Activity.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
}
public abstract Task<StorageItemProperties> GetBasicPropertiesAsync();
protected string? GetColumnValue(Context context, AndroidUri contentUri, string column, string? selection = null, string[]? selectionArgs = null)
@ -77,29 +85,44 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
return null;
}
public Task<IStorageFolder?> GetParentAsync()
public async Task<IStorageFolder?> GetParentAsync()
{
if (!await EnsureExternalFilesPermission(false))
{
return null;
}
using var javaFile = new JavaFile(Uri.Path!);
// Java file represents files AND directories. Don't be confused.
if (javaFile.ParentFile is {} parentFile
&& AndroidUri.FromFile(parentFile) is {} androidUri)
{
return Task.FromResult<IStorageFolder?>(new AndroidStorageFolder(Context, androidUri));
return new AndroidStorageFolder(Activity, androidUri, false);
}
return Task.FromResult<IStorageFolder?>(null);
return null;
}
protected async Task<bool> EnsureExternalFilesPermission(bool write)
{
if (!_needsExternalFilesPermission)
{
return true;
}
return await _activity.CheckPermission(Manifest.Permission.ReadExternalStorage);
}
public void Dispose()
{
_context = null;
_activity = null;
}
}
internal sealed class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
internal class AndroidStorageFolder : AndroidStorageItem, IStorageBookmarkFolder
{
public AndroidStorageFolder(Context context, AndroidUri uri) : base(context, uri)
public AndroidStorageFolder(Activity activity, AndroidUri uri, bool needsExternalFilesPermission) : base(activity, uri, needsExternalFilesPermission)
{
}
@ -110,6 +133,11 @@ internal sealed class AndroidStorageFolder : AndroidStorageItem, IStorageBookmar
public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
{
if (!await EnsureExternalFilesPermission(false))
{
return Array.Empty<IStorageItem>();
}
using var javaFile = new JavaFile(Uri.Path!);
// Java file represents files AND directories. Don't be confused.
@ -124,8 +152,8 @@ internal sealed class AndroidStorageFolder : AndroidStorageItem, IStorageBookmar
.Where(t => t.uri is not null)
.Select(t => t.file switch
{
{ IsFile: true } => (IStorageItem)new AndroidStorageFile(Context, t.uri!),
{ IsDirectory: true } => new AndroidStorageFolder(Context, t.uri!),
{ IsFile: true } => (IStorageItem)new AndroidStorageFile(Activity, t.uri!),
{ IsDirectory: true } => new AndroidStorageFolder(Activity, t.uri!, false),
_ => null
})
.Where(i => i is not null)
@ -133,9 +161,20 @@ internal sealed class AndroidStorageFolder : AndroidStorageItem, IStorageBookmar
}
}
internal sealed class WellKnownAndroidStorageFolder : AndroidStorageFolder
{
public WellKnownAndroidStorageFolder(Activity activity, string identifier, AndroidUri uri, bool needsExternalFilesPermission)
: base(activity, uri, needsExternalFilesPermission)
{
Name = identifier;
}
public override string Name { get; }
}
internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkFile
{
public AndroidStorageFile(Context context, AndroidUri uri) : base(context, uri)
public AndroidStorageFile(Activity activity, AndroidUri uri) : base(activity, uri, false)
{
}
@ -143,10 +182,10 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
public bool CanOpenWrite => true;
public Task<Stream> OpenReadAsync() => Task.FromResult(OpenContentStream(Context, Uri, false)
public Task<Stream> OpenReadAsync() => Task.FromResult(OpenContentStream(Activity, Uri, false)
?? throw new InvalidOperationException("Failed to open content stream"));
public Task<Stream> OpenWriteAsync() => Task.FromResult(OpenContentStream(Context, Uri, true)
public Task<Stream> OpenWriteAsync() => Task.FromResult(OpenContentStream(Activity, Uri, true)
?? throw new InvalidOperationException("Failed to open content stream"));
private Stream? OpenContentStream(Context context, AndroidUri uri, bool isOutput)
@ -210,7 +249,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
MediaStore.IMediaColumns.Size, MediaStore.IMediaColumns.DateAdded,
MediaStore.IMediaColumns.DateModified
};
using var cursor = Context.ContentResolver!.Query(Uri, projection, null, null, null);
using var cursor = Activity.ContentResolver!.Query(Uri, projection, null, null, null);
if (cursor?.MoveToFirst() == true)
{

124
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs

@ -4,18 +4,21 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Android;
using Android.App;
using Android.Content;
using Android.Provider;
using Avalonia.Platform.Storage;
using Java.Lang;
using AndroidUri = Android.Net.Uri;
using Exception = System.Exception;
using JavaFile = Java.IO.File;
namespace Avalonia.Android.Platform.Storage;
internal class AndroidStorageProvider : IStorageProvider
{
private readonly Activity _activity;
private int _lastRequestCode = 20000;
public AndroidStorageProvider(Activity activity)
{
@ -31,7 +34,108 @@ internal class AndroidStorageProvider : IStorageProvider
public Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark)
{
var uri = AndroidUri.Parse(bookmark) ?? throw new ArgumentException("Couldn't parse Bookmark value", nameof(bookmark));
return Task.FromResult<IStorageBookmarkFolder?>(new AndroidStorageFolder(_activity, uri));
return Task.FromResult<IStorageBookmarkFolder?>(new AndroidStorageFolder(_activity, uri, false));
}
public async Task<IStorageFile?> TryGetFileFromPath(Uri filePath)
{
if (filePath is null)
{
throw new ArgumentNullException(nameof(filePath));
}
if (filePath is not { IsAbsoluteUri: true, Scheme: "file" or "content" })
{
throw new ArgumentException("File path is expected to be an absolute link with \"file\" or \"content\" scheme.");
}
var androidUri = AndroidUri.Parse(filePath.ToString());
if (androidUri?.Path is not {} androidUriPath)
{
return null;
}
var hasPerms = await _activity.CheckPermission(Manifest.Permission.ReadExternalStorage);
if (!hasPerms)
{
throw new SecurityException("Application doesn't have ReadExternalStorage permission. Make sure android manifest has this permission defined and user allowed it.");
}
var javaFile = new JavaFile(androidUriPath);
if (javaFile.Exists() && javaFile.IsFile)
{
return null;
}
return new AndroidStorageFile(_activity, androidUri);
}
public async Task<IStorageFolder?> TryGetFolderFromPath(Uri folderPath)
{
if (folderPath is null)
{
throw new ArgumentNullException(nameof(folderPath));
}
if (folderPath is not { IsAbsoluteUri: true, Scheme: "file" or "content" })
{
throw new ArgumentException("Folder path is expected to be an absolute link with \"file\" or \"content\" scheme.");
}
var androidUri = AndroidUri.Parse(folderPath.ToString());
if (androidUri?.Path is not {} androidUriPath)
{
return null;
}
var hasPerms = await _activity.CheckPermission(Manifest.Permission.ReadExternalStorage);
if (!hasPerms)
{
throw new SecurityException("Application doesn't have ReadExternalStorage permission. Make sure android manifest has this permission defined and user allowed it.");
}
var javaFile = new JavaFile(androidUriPath);
if (javaFile.Exists() && javaFile.IsDirectory)
{
return null;
}
return new AndroidStorageFolder(_activity, androidUri, false);
}
public Task<IStorageFolder?> TryGetWellKnownFolder(WellKnownFolder wellKnownFolder)
{
var dirCode = wellKnownFolder switch
{
WellKnownFolder.Desktop => null,
WellKnownFolder.Documents => global::Android.OS.Environment.DirectoryDocuments,
WellKnownFolder.Downloads => global::Android.OS.Environment.DirectoryDownloads,
WellKnownFolder.Music => global::Android.OS.Environment.DirectoryMusic,
WellKnownFolder.Pictures => global::Android.OS.Environment.DirectoryPictures,
WellKnownFolder.Videos => global::Android.OS.Environment.DirectoryMovies,
_ => throw new ArgumentOutOfRangeException(nameof(wellKnownFolder), wellKnownFolder, null)
};
if (dirCode is null)
{
return Task.FromResult<IStorageFolder?>(null);
}
var dir = _activity.GetExternalFilesDir(dirCode);
if (dir is null || !dir.Exists())
{
return Task.FromResult<IStorageFolder?>(null);
}
var uri = AndroidUri.FromFile(dir);
if (uri is null)
{
return Task.FromResult<IStorageFolder?>(null);
}
// To make TryGetWellKnownFolder API easier to use, we don't check for the permissions.
// It will work with file picker activities, but it will fail on any direct access to the folder, like getting list of children.
// We pass "needsExternalFilesPermission" parameter here, so folder itself can check for permissions on any FS access.
return Task.FromResult<IStorageFolder?>(new WellKnownAndroidStorageFolder(_activity, dirCode, uri, true));
}
public Task<IStorageBookmarkFile?> OpenFileBookmarkAsync(string bookmark)
@ -110,19 +214,21 @@ internal class AndroidStorageProvider : IStorageProvider
var pickerIntent = Intent.CreateChooser(intent, options.Title ?? "Select folder");
var uris = await StartActivity(pickerIntent, false);
return uris.Select(u => new AndroidStorageFolder(_activity, u)).ToArray();
return uris.Select(u => new AndroidStorageFolder(_activity, u, false)).ToArray();
}
private async Task<List<AndroidUri>> StartActivity(Intent? pickerIntent, bool singleResult)
{
var resultList = new List<AndroidUri>(1);
var tcs = new TaskCompletionSource<Intent?>();
var currentRequestCode = _lastRequestCode++;
var currentRequestCode = PlatformSupport.GetNextRequestCode();
if (_activity is IActivityResultHandler mainActivity)
if (!(_activity is IActivityResultHandler mainActivity))
{
mainActivity.ActivityResult += OnActivityResult;
throw new InvalidOperationException("Main activity must implement IActivityResultHandler interface.");
}
mainActivity.ActivityResult += OnActivityResult;
_activity.StartActivityForResult(pickerIntent, currentRequestCode);
var result = await tcs.Task;
@ -161,11 +267,7 @@ internal class AndroidStorageProvider : IStorageProvider
return;
}
if (_activity is IActivityResultHandler mainActivity)
{
mainActivity.ActivityResult -= OnActivityResult;
}
mainActivity.ActivityResult -= OnActivityResult;
_ = tcs.TrySetResult(resultCode == Result.Ok ? data : null);
}

26
src/Avalonia.Base/Animation/Animators/GradientBrushAnimator.cs

@ -4,6 +4,7 @@ using System.Collections.Generic;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Media.Transformation;
#nullable enable
@ -30,7 +31,7 @@ namespace Avalonia.Animation.Animators
return new ImmutableRadialGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.Transform is { } ? new ImmutableTransform(oldValue.Transform.Value) : null,
InterpolateTransform(progress, oldValue.Transform, newValue.Transform),
s_relativePointAnimator.Interpolate(progress, oldValue.TransformOrigin, newValue.TransformOrigin),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center),
@ -41,7 +42,7 @@ namespace Avalonia.Animation.Animators
return new ImmutableConicGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.Transform is { } ? new ImmutableTransform(oldValue.Transform.Value) : null,
InterpolateTransform(progress, oldValue.Transform, newValue.Transform),
s_relativePointAnimator.Interpolate(progress, oldValue.TransformOrigin, newValue.TransformOrigin),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center),
@ -51,7 +52,7 @@ namespace Avalonia.Animation.Animators
return new ImmutableLinearGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.Transform is { } ? new ImmutableTransform(oldValue.Transform.Value) : null,
InterpolateTransform(progress, oldValue.Transform, newValue.Transform),
s_relativePointAnimator.Interpolate(progress, oldValue.TransformOrigin, newValue.TransformOrigin),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint),
@ -72,6 +73,25 @@ namespace Avalonia.Animation.Animators
return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
}
private static ImmutableTransform? InterpolateTransform(double progress,
ITransform? oldTransform, ITransform? newTransform)
{
if (oldTransform is TransformOperations oldTransformOperations
&& newTransform is TransformOperations newTransformOperations)
{
return new ImmutableTransform(TransformOperations
.Interpolate(oldTransformOperations, newTransformOperations, progress).Value);
}
if (oldTransform is not null)
{
return new ImmutableTransform(oldTransform.Value);
}
return null;
}
private static IReadOnlyList<ImmutableGradientStop> InterpolateStops(double progress, IReadOnlyList<IGradientStop> oldValue, IReadOnlyList<IGradientStop> newValue)
{
var resultCount = Math.Max(oldValue.Count, newValue.Count);

2
src/Avalonia.Base/Animation/PageSlide.cs

@ -79,7 +79,6 @@ namespace Avalonia.Animation
var animation = new Animation
{
Easing = SlideOutEasing,
FillMode = FillMode.Forward,
Children =
{
new KeyFrame
@ -110,7 +109,6 @@ namespace Avalonia.Animation
to.IsVisible = true;
var animation = new Animation
{
FillMode = FillMode.Forward,
Easing = SlideInEasing,
Children =
{

2
src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs

@ -71,7 +71,6 @@ public class Rotate3DTransition: PageSlide
{
Easing = SlideOutEasing,
Duration = Duration,
FillMode = FillMode.Forward,
Children =
{
CreateKeyFrame(0d, 0d, 2),
@ -90,7 +89,6 @@ public class Rotate3DTransition: PageSlide
{
Easing = SlideInEasing,
Duration = Duration,
FillMode = FillMode.Forward,
Children =
{
CreateKeyFrame(0d, 90d * (forward ? 1 : -1), 1),

4
src/Avalonia.Base/AttachedProperty.cs

@ -24,11 +24,9 @@ namespace Avalonia
Func<TValue, bool>? validate = null)
: base(name, ownerType, metadata, inherits, validate)
{
IsAttached = true;
}
/// <inheritdoc/>
public override bool IsAttached => true;
/// <summary>
/// Attaches the property as a non-attached property on the specified type.
/// </summary>

8
src/Avalonia.Base/Avalonia.Base.csproj

@ -30,6 +30,7 @@
<InternalsVisibleTo Include="Avalonia.Desktop, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Direct2D1, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup.Xaml, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.OpenGL, PublicKey=$(AvaloniaPublicKey)" />
@ -47,6 +48,9 @@
<InternalsVisibleTo Include="Avalonia.DesignerSupport, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Direct2D1.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.LeakTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup.Xaml, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup.Xaml.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
@ -66,8 +70,4 @@
<ItemGroup Label="Build dependency">
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj" SetTargetFramework="TargetFramework=netstandard2.0" ReferenceOutputAssembly="false" SkipGetTargetFrameworkProperties="true" />
</ItemGroup>
<ItemGroup>
<Folder Include="Rendering\Composition\Utils" />
</ItemGroup>
</Project>

16
src/Avalonia.Base/AvaloniaObject.cs

@ -132,7 +132,7 @@ namespace Avalonia
switch (property)
{
case StyledPropertyBase<T> styled:
case StyledProperty<T> styled:
ClearValue(styled);
break;
case DirectPropertyBase<T> direct:
@ -147,7 +147,7 @@ namespace Avalonia
/// Clears a <see cref="AvaloniaProperty"/>'s local value.
/// </summary>
/// <param name="property">The property.</param>
public void ClearValue<T>(StyledPropertyBase<T> property)
public void ClearValue<T>(StyledProperty<T> property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
@ -220,7 +220,7 @@ namespace Avalonia
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
public T GetValue<T>(StyledPropertyBase<T> property)
public T GetValue<T>(StyledProperty<T> property)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
@ -243,7 +243,7 @@ namespace Avalonia
}
/// <inheritdoc/>
public Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property)
public Optional<T> GetBaseValue<T>(StyledProperty<T> property)
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
@ -309,7 +309,7 @@ namespace Avalonia
/// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
/// </returns>
public IDisposable? SetValue<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
T value,
BindingPriority priority = BindingPriority.LocalValue)
{
@ -373,7 +373,7 @@ namespace Avalonia
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<object?> source,
BindingPriority priority = BindingPriority.LocalValue)
{
@ -396,7 +396,7 @@ namespace Avalonia
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue)
{
@ -419,7 +419,7 @@ namespace Avalonia
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
StyledPropertyBase<T> property,
StyledProperty<T> property,
IObservable<BindingValue<T>> source,
BindingPriority priority = BindingPriority.LocalValue)
{

8
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -146,7 +146,7 @@ namespace Avalonia
return property switch
{
StyledPropertyBase<T> styled => target.Bind(styled, source, priority),
StyledProperty<T> styled => target.Bind(styled, source, priority),
DirectPropertyBase<T> direct => target.Bind(direct, source),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
};
@ -170,7 +170,7 @@ namespace Avalonia
{
return property switch
{
StyledPropertyBase<T> styled => target.Bind(styled, source, priority),
StyledProperty<T> styled => target.Bind(styled, source, priority),
DirectPropertyBase<T> direct => target.Bind(direct, source),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
};
@ -231,7 +231,7 @@ namespace Avalonia
return property switch
{
StyledPropertyBase<T> styled => target.GetValue(styled),
StyledProperty<T> styled => target.GetValue(styled),
DirectPropertyBase<T> direct => target.GetValue(direct),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
};
@ -280,7 +280,7 @@ namespace Avalonia
return property switch
{
StyledPropertyBase<T> styled => target.GetBaseValue(styled),
StyledProperty<T> styled => target.GetBaseValue(styled),
DirectPropertyBase<T> direct => target.GetValue(direct),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
};

8
src/Avalonia.Base/AvaloniaProperty.cs

@ -107,22 +107,22 @@ namespace Avalonia
/// <summary>
/// Gets a value indicating whether the property inherits its value.
/// </summary>
public virtual bool Inherits => false;
public bool Inherits { get; private protected set; }
/// <summary>
/// Gets a value indicating whether this is an attached property.
/// </summary>
public virtual bool IsAttached => false;
public bool IsAttached { get; private protected set; }
/// <summary>
/// Gets a value indicating whether this is a direct property.
/// </summary>
public virtual bool IsDirect => false;
public bool IsDirect { get; private protected set; }
/// <summary>
/// Gets a value indicating whether this is a readonly property.
/// </summary>
public virtual bool IsReadOnly => false;
public bool IsReadOnly { get; private protected set; }
/// <summary>
/// Gets an observable that is fired when this property changes on any

59
src/Avalonia.Base/Data/BindingOperations.cs

@ -41,54 +41,41 @@ namespace Avalonia.Data
{
case BindingMode.Default:
case BindingMode.OneWay:
if (binding.Observable is null)
throw new InvalidOperationException("InstancedBinding does not contain an observable.");
return target.Bind(property, binding.Observable, binding.Priority);
return target.Bind(property, binding.Source, binding.Priority);
case BindingMode.TwoWay:
if (binding.Observable is null)
throw new InvalidOperationException("InstancedBinding does not contain an observable.");
if (binding.Subject is null)
{
if (binding.Source is not IObserver<object?> observer)
throw new InvalidOperationException("InstancedBinding does not contain a subject.");
return new TwoWayBindingDisposable(
target.Bind(property, binding.Observable, binding.Priority),
target.GetObservable(property).Subscribe(binding.Subject));
target.Bind(property, binding.Source, binding.Priority),
target.GetObservable(property).Subscribe(observer));
}
case BindingMode.OneTime:
if (binding.Observable is {} source)
{
// Perf: Avoid allocating closure in the outer scope.
var targetCopy = target;
var propertyCopy = property;
var bindingCopy = binding;
return source
.Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue)
.Take(1)
.Subscribe(x => targetCopy.SetValue(
propertyCopy,
BindingNotification.ExtractValue(x),
bindingCopy.Priority));
}
else
{
target.SetValue(property, binding.Value, binding.Priority);
return Disposable.Empty;
}
{
// Perf: Avoid allocating closure in the outer scope.
var targetCopy = target;
var propertyCopy = property;
var bindingCopy = binding;
return binding.Source
.Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue)
.Take(1)
.Subscribe(x => targetCopy.SetValue(
propertyCopy,
BindingNotification.ExtractValue(x),
bindingCopy.Priority));
}
case BindingMode.OneWayToSource:
{
if (binding.Observable is null)
throw new InvalidOperationException("InstancedBinding does not contain an observable.");
if (binding.Subject is null)
if (binding.Source is not IObserver<object?> observer)
throw new InvalidOperationException("InstancedBinding does not contain a subject.");
// Perf: Avoid allocating closure in the outer scope.
var bindingCopy = binding;
return Observable.CombineLatest(
binding.Observable,
binding.Source,
target.GetObservable(property),
(_, v) => v)
.Subscribe(x => bindingCopy.Subject.OnNext(x));
.Subscribe(x => observer.OnNext(x));
}
default:

2
src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs

@ -3,7 +3,7 @@ using Avalonia.Reactive;
namespace Avalonia.Data.Core
{
public class AvaloniaPropertyAccessorNode : SettableNode
internal class AvaloniaPropertyAccessorNode : SettableNode
{
private IDisposable? _subscription;
private readonly bool _enableValidation;

2
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -13,7 +13,7 @@ namespace Avalonia.Data.Core
/// that are sent and received.
/// </summary>
[RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)]
public class BindingExpression : LightweightObservableBase<object?>, IAvaloniaSubject<object?>, IDescription
internal class BindingExpression : LightweightObservableBase<object?>, IAvaloniaSubject<object?>, IDescription
{
private readonly ExpressionObserver _inner;
private readonly Type _targetType;

2
src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs

@ -1,6 +1,6 @@
namespace Avalonia.Data.Core
{
public class EmptyExpressionNode : ExpressionNode
internal class EmptyExpressionNode : ExpressionNode
{
public override string Description => ".";
}

2
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -2,7 +2,7 @@ using System;
namespace Avalonia.Data.Core
{
public abstract class ExpressionNode
internal abstract class ExpressionNode
{
protected static readonly WeakReference<object?> UnsetReference =
new WeakReference<object?>(AvaloniaProperty.UnsetValue);

2
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -11,7 +11,7 @@ namespace Avalonia.Data.Core
/// <summary>
/// Observes and sets the value of an expression on an object.
/// </summary>
public class ExpressionObserver : LightweightObservableBase<object?>, IDescription
internal class ExpressionObserver : LightweightObservableBase<object?>, IDescription
{
/// <summary>
/// An ordered collection of property accessor plugins that can be used to customize

2
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@ -7,7 +7,7 @@ using Avalonia.Utilities;
namespace Avalonia.Data.Core
{
public abstract class IndexerNodeBase : SettableNode,
internal abstract class IndexerNodeBase : SettableNode,
IWeakEventSubscriber<NotifyCollectionChangedEventArgs>,
IWeakEventSubscriber<PropertyChangedEventArgs>
{

2
src/Avalonia.Base/Data/Core/LogicalNotNode.cs

@ -3,7 +3,7 @@ using System.Globalization;
namespace Avalonia.Data.Core
{
public class LogicalNotNode : ExpressionNode, ITransformNode
internal class LogicalNotNode : ExpressionNode, ITransformNode
{
public override string Description => "!";

2
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -8,7 +8,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Reads a property from a <see cref="AvaloniaObject"/>.
/// </summary>
public class AvaloniaPropertyAccessorPlugin : IPropertyAccessorPlugin
internal class AvaloniaPropertyAccessorPlugin : IPropertyAccessorPlugin
{
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]

28
src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs

@ -0,0 +1,28 @@
using System.Collections.Generic;
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Holds a registry of plugins used for bindings.
/// </summary>
public static class BindingPlugins
{
/// <summary>
/// An ordered collection of property accessor plugins that can be used to customize
/// the reading and subscription of property values on a type.
/// </summary>
public static IList<IPropertyAccessorPlugin> PropertyAccessors => ExpressionObserver.PropertyAccessors;
/// <summary>
/// An ordered collection of validation checker plugins that can be used to customize
/// the validation of view model and model data.
/// </summary>
public static IList<IDataValidationPlugin> DataValidators => ExpressionObserver.DataValidators;
/// <summary>
/// An ordered collection of stream plugins that can be used to customize the behavior
/// of the '^' stream binding operator.
/// </summary>
public static IList<IStreamPlugin> StreamHandlers => ExpressionObserver.StreamHandlers;
}
}

2
src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Validates properties on that have <see cref="ValidationAttribute"/>s.
/// </summary>
public class DataAnnotationsValidationPlugin : IDataValidationPlugin
internal class DataAnnotationsValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]

2
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Validates properties that report errors by throwing exceptions.
/// </summary>
public class ExceptionValidationPlugin : IDataValidationPlugin
internal class ExceptionValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
[RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)]

2
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Validates properties on objects that implement <see cref="INotifyDataErrorInfo"/>.
/// </summary>
public class IndeiValidationPlugin : IDataValidationPlugin
internal class IndeiValidationPlugin : IDataValidationPlugin
{
private static readonly WeakEvent<INotifyDataErrorInfo, DataErrorsChangedEventArgs>
ErrorsChangedWeakEvent = WeakEvent.Register<INotifyDataErrorInfo, DataErrorsChangedEventArgs>(

2
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -11,7 +11,7 @@ namespace Avalonia.Data.Core.Plugins
/// Reads a property from a standard C# object that optionally supports the
/// <see cref="INotifyPropertyChanged"/> interface.
/// </summary>
public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
internal class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
{
private readonly Dictionary<(Type, string), PropertyInfo?> _propertyLookup =
new Dictionary<(Type, string), PropertyInfo?>();

2
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -6,7 +6,7 @@ using System.Reflection;
namespace Avalonia.Data.Core.Plugins
{
public class MethodAccessorPlugin : IPropertyAccessorPlugin
internal class MethodAccessorPlugin : IPropertyAccessorPlugin
{
private readonly Dictionary<(Type, string), MethodInfo?> _methodLookup =
new Dictionary<(Type, string), MethodInfo?>();

4
src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

@ -10,12 +10,12 @@ namespace Avalonia.Data.Core.Plugins
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)]
public class ObservableStreamPlugin : IStreamPlugin
internal class ObservableStreamPlugin : IStreamPlugin
{
private static MethodInfo? s_observableGeneric;
private static MethodInfo? s_observableSelect;
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicProperties, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")]
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")]
public ObservableStreamPlugin()
{

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save