Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup of control code, enable MSAA, improvements to event handling #86

Merged
merged 23 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
28c8e60
Started cleanup effort
NogginBops Jul 21, 2022
1631d01
Updated to .net 5.0. Added error checking for d3d9 calls. Slowly remo…
NogginBops Aug 1, 2022
513f980
Moved design time and unstarted rendering helpers into GLWpfControl.cs
NogginBops Aug 1, 2022
8e9d4f7
Whitespace cleanup
NogginBops Aug 1, 2022
d12260f
Merge branch 'master' of github.com:opentk/GLWpfControl into cleanup
NogginBops Feb 15, 2023
57e58df
Implemented proper COM interop. Switched back to .net core 3.1.
NogginBops Mar 13, 2023
e70ea99
Fixed some style issues and made COM error checking part of DxInterop.
NogginBops Mar 13, 2023
82ad452
Merge branch 'master' of github.com:opentk/GLWpfControl into cleanup
NogginBops Mar 13, 2023
bb42362
Move comments
NogginBops Mar 13, 2023
84f0b73
Added code to check if DX rendertarget is created with multisamples
NogginBops May 1, 2023
c07acf1
Fixed compile error after rename
NogginBops May 1, 2023
920cefd
Bunch of cleaup. Enabled MSAA framebuffer sharing. Removed the one sh…
NogginBops May 29, 2024
db7bb33
Merge branch 'master' of github.com:opentk/GLWpfControl into cleanup
NogginBops May 29, 2024
9c51f6f
Fixed paket file. Cleaned csproj. Other cleanup.
NogginBops May 29, 2024
3bb8d1e
Fixed nullability issue.
NogginBops May 29, 2024
4b0913f
Implemented IDisposable.
NogginBops May 29, 2024
8a539b2
Added context sharing setting.
NogginBops May 30, 2024
ec9d51b
Merge branch 'master' of github.com:opentk/GLWpfControl into cleanup
NogginBops May 30, 2024
8edd8b1
Set Focusable=true by default. Deprecate direct event registering.
NogginBops May 30, 2024
20cecb2
Updated readme to reflect changes to keyboard event handling.
NogginBops May 30, 2024
85cc8ca
Fixed warnings
NogginBops May 30, 2024
3551c5e
Small style cleanup
NogginBops May 30, 2024
d619ff4
Fix Example function order.
NogginBops May 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[*.cs]

# IDE0090: Use 'new(...)'
csharp_style_implicit_object_creation_when_type_is_apparent = false
5 changes: 5 additions & 0 deletions GLWpfControl.sln
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GLWpfControl", "src\GLWpfCo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "src\Example\Example.csproj", "{7E12D07B-0DD6-4288-A4F8-92EB75848ECF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{441E95E4-0A78-4121-A68C-C778C8050489}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
53 changes: 13 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ Supported configurations:
- .Net Framework for OpenTK 3.x (the 3.x series NuGet packages)
- .Net Core for OpenTK 4.x (the 4.x series NuGet packages)

Since version 3.0.0, we're using full OpenGL/DirectX interop via OpenGL extensions - [NV_DX_interop](https://www.khronos.org/registry/OpenGL/extensions/NV/WGL_NV_DX_interop.txt). This should run almost everywhere with **AMAZING PERFORMANCE** and is fully supported on Intel, AMD and Nvidia graphics.
Since version 3.0.0, we're using full OpenGL/DirectX interop via OpenGL extensions - [NV_DX_interop](https://www.khronos.org/registry/OpenGL/extensions/NV/WGL_NV_DX_interop.txt).
This should run almost everywhere with **AMAZING PERFORMANCE** and is fully supported on Intel, AMD and Nvidia graphics.

This offers a way more clean solution than embedding GLControl and totally solves [the airspace problem](https://stackoverflow.com/questions/8006092/controls-dont-show-over-winforms-host). As controls can be layered, nested and structured over your 3D view.

This package is intended to supercede the legacy *GLControl* completely, and we strongly encourage upgrading to this native WPF control instead.
This offers a way more clean solution than embedding GLControl and totally solves [the airspace problem](https://stackoverflow.com/questions/8006092/controls-dont-show-over-winforms-host).
As controls can be layered, nested and structured over your 3D view.

## Getting started:

Expand Down Expand Up @@ -56,44 +56,14 @@ This package is intended to supercede the legacy *GLControl* completely, and we
```
For additional examples, see [MainWindow.xaml](https://github.com/opentk/GLWpfControl/blob/master/src/Example/MainWindow.xaml) and [MainWindow.xaml.cs](https://github.com/opentk/GLWpfControl/blob/master/src/Example/MainWindow.xaml.cs) in the example project.

### I'm having trouble with Keyboard and Mouse Input!?

The current design has some issues based around polling for keyboard and mouse input due to the way the control was initially designed.

If you want to handle keyboard input for the control when it is not focused, this is a feature we're currently looking into fixing. However, if you just want to handle keyboard input when the control has focus there's a little additional logic that must be implemented:

1. Before calling `Start()` add the following to ensure that you can hook onto events via the control:

```csharp
this.glWpfControl.RegisterToEventsDirectly = false;
this.glWpfControl.CanInvokeOnHandledEvents = false;
### I can't receive keyboard input when my control doesn't have keyboard focus!

this.glWpfControl.MouseDown += this.GlWpfControl_MouseDown;
this.glWpfControl.MouseEnter += this.GlWpfControl_MouseEnter;
this.glWpfControl.MouseLeave += this.GlWpfControl_MouseLeave;
```
WPF by design only sends keyboard events to the control that has keybaord focus. To be able to get keyboard focus a control needs to have `Focusable==true` (this is the default for GLWpfControl) and `IsVisible==true`.

2. Next, in the mouse event handlers you want to call `Focus()` for the control:
If you however need to get keyboard events idependent of keyboard focus you will have to use the `Keyboard.AddPreview*` functions.
These functions allow you to register a preview event that is called before the control with keyboard focus gets the keyboard event.

```csharp
private void GlWpfControl_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
// When the mouse is leaving, lose focus so the keyboard cannot invoke events.
var scope = FocusManager.GetFocusScope(this.glWpfControl);
FocusManager.SetFocusedElement(scope, null);
System.Windows.Input.Keyboard.ClearFocus();
}

private void GlWpfControl_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
this.glWpfControl.Focus();
}

private void GlWpfControl_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.glWpfControl.Focus();
}
```
See [Example](./src/Example/TabbedMainWindowTest.xaml.cs) for an example how to set this up.

## Build instructions

Expand All @@ -115,4 +85,7 @@ this.glWpfControl.MouseLeave += this.GlWpfControl_MouseLeave;

#### DX-Hijacking rendering

It's possible to bypass the RTT that takes place in WPF D3dImage by stealing the actual D3d handle from WPF and drawing manually. This is incredibly challenging, but would offer APEX performance as zero indirection is required. Currently more of an idea than a work in progress. Contributions welcome - Drop by the [Discord](https://discord.gg/6HqD48s) server if you want to give this a shot!
It's possible to bypass the RTT that takes place in WPF D3dImage by stealing the actual D3d handle from WPF and drawing manually.
This is incredibly challenging, but would offer APEX performance as zero indirection is required.
Currently more of an idea than a work in progress.
Contributions welcome - Drop by the [Discord](https://discord.gg/6HqD48s) server if you want to give this a shot!
29 changes: 16 additions & 13 deletions src/Example/Example.csproj
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<StartupObject>Example.App</StartupObject>
</PropertyGroup>

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1-windows</TargetFramework>
<UseWPF>true</UseWPF>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<StartupObject>Example.App</StartupObject>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\.editorconfig" Link=".editorconfig" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\GLWpfControl\GLWpfControl.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GLWpfControl\GLWpfControl.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OpenTK" Version="4.7.5" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="OpenTK" Version="4.8.2" />
</ItemGroup>
</Project>
132 changes: 112 additions & 20 deletions src/Example/ExampleScene.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,132 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;

namespace Example {
/// Example class handling the rendering for OpenGL.
public static class ExampleScene {
public class ExampleScene {

private static readonly Stopwatch _stopwatch = Stopwatch.StartNew();

public static void Ready() {
Console.WriteLine("GlWpfControl is now ready");
GL.Enable(EnableCap.Blend);
GL.Enable(EnableCap.DepthTest);
GL.Enable(EnableCap.ScissorTest);
private int Program;

private int VAO;
private int VBO;

struct Vertex
{
public Vector2 Position;
public Color4 Color;

public Vertex(Vector2 position, Color4 color)
{
Position = position;
Color = color;
}
}

private static readonly Vertex[] vertices =
{
new Vertex((0.0f, 0.5f), Color4.Red),
new Vertex((0.58f, -0.5f), Color4.Green),
new Vertex((-0.58f, -0.5f), Color4.Blue),
};

private static readonly string VertexShaderSource =
@"#version 330 core

in vec2 vPosition;
in vec4 vColor;

out vec4 fColor;

void main()
{
gl_Position = vec4(vPosition, 0, 1);
fColor = vColor;
}
";

private static readonly string FragmentShaderSource =
@"#version 330 core

in vec4 fColor;

out vec4 oColor;

void main()
{
oColor = fColor;
}
";

public void Initialize()
{
Program = GL.CreateProgram();

int vertexShader = GL.CreateShader(ShaderType.VertexShader);
GL.ShaderSource(vertexShader, VertexShaderSource);
GL.CompileShader(vertexShader);
GL.GetShader(vertexShader, ShaderParameter.CompileStatus, out int success);
if (success == 0)
{
string log = GL.GetShaderInfoLog(vertexShader);
Debug.WriteLine($"Vertex shader compile error: {log}");
}

int fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(fragmentShader, FragmentShaderSource);
GL.CompileShader(fragmentShader);
GL.GetShader(fragmentShader, ShaderParameter.CompileStatus, out success);
if (success == 0)
{
string log = GL.GetShaderInfoLog(fragmentShader);
Debug.WriteLine($"Fragment shader compile error: {log}");
}

GL.AttachShader(Program, vertexShader);
GL.AttachShader(Program, fragmentShader);
GL.LinkProgram(Program);
GL.GetProgram(Program, GetProgramParameterName.LinkStatus, out success);
if (success == 0)
{
string log = GL.GetProgramInfoLog(Program);
Debug.WriteLine($"Program link error: {log}");
}

GL.DetachShader(Program, vertexShader);
GL.DetachShader(Program, fragmentShader);

GL.DeleteShader(vertexShader);
GL.DeleteShader(fragmentShader);

int positionLocation = GL.GetAttribLocation(Program, "vPosition");
int colorLocation = GL.GetAttribLocation(Program, "vColor");

VAO = GL.GenVertexArray();
GL.BindVertexArray(VAO);

VBO = GL.GenBuffer();
GL.BindBuffer(BufferTarget.ArrayBuffer, VBO);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * Unsafe.SizeOf<Vertex>(), vertices, BufferUsageHint.StaticDraw);

GL.EnableVertexAttribArray(positionLocation);
GL.VertexAttribPointer(positionLocation, 2, VertexAttribPointerType.Float, false, Unsafe.SizeOf<Vertex>(), 0);

GL.EnableVertexAttribArray(colorLocation);
GL.VertexAttribPointer(colorLocation, 4, VertexAttribPointerType.Float, false, Unsafe.SizeOf<Vertex>(), Unsafe.SizeOf<Vector2>());
}

public static void Render(float alpha = 1.0f) {
public void Render(float alpha = 1.0f) {
var hue = (float) _stopwatch.Elapsed.TotalSeconds * 0.15f % 1;
var c = Color4.FromHsv(new Vector4(alpha * hue, alpha * 0.75f, alpha * 0.75f, alpha));
GL.ClearColor(c);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.LoadIdentity();
GL.Begin(PrimitiveType.Triangles);

GL.Color4(Color4.Red);
GL.Vertex2(0.0f, 0.5f);

GL.Color4(Color4.Green);
GL.Vertex2(0.58f, -0.5f);

GL.Color4(Color4.Blue);
GL.Vertex2(-0.58f, -0.5f);

GL.End();
GL.Finish();
GL.UseProgram(Program);
GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
}
}
}
4 changes: 2 additions & 2 deletions src/Example/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
Render="OpenTkControl_OnRender">

<glWpfControl:GLWpfControl.Settings>
<glWpfControl:GLWpfControlSettings MajorVersion="2"
MinorVersion="1"/>
<glWpfControl:GLWpfControlSettings MajorVersion="3"
MinorVersion="3"/>
</glWpfControl:GLWpfControl.Settings>

</glWpfControl:GLWpfControl>
Expand Down
10 changes: 8 additions & 2 deletions src/Example/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ namespace Example {
/// Interaction logic for MainWindow.xaml
/// </summary>
public sealed partial class MainWindow {

ExampleScene mainScene = new ExampleScene();
ExampleScene insetScene = new ExampleScene();

public MainWindow() {
InitializeComponent();

// You can start and rely on the Settings property that may be set in XAML or elsewhere in the codebase.
OpenTkControl.Start();
mainScene.Initialize();

// Or, you can suppy a settings object directly.
InsetControl.Start(new GLWpfControlSettings()
Expand All @@ -20,14 +25,15 @@ public MainWindow() {
MinorVersion = 1,
RenderContinuously = false,
});
insetScene.Initialize();
}

private void OpenTkControl_OnRender(TimeSpan delta) {
ExampleScene.Render();
mainScene.Render();
}

private void InsetControl_OnRender(TimeSpan delta) {
ExampleScene.Render();
insetScene.Render();
}

private void RedrawButton_OnClick(object sender, RoutedEventArgs e) {
Expand Down
Loading