Hardwarebeschleunigung

  • C#
  • .NET (FX) 4.0

Es gibt 25 Antworten in diesem Thema. Der letzte Beitrag () ist von xd-franky-5.

    Hardwarebeschleunigung

    Hallo Leute,
    ich beschäftige mich mit rechenintensiver Grafik, sprich verschiedene Filter auf Bildern etc. (läuft alles auf Objekterkennung hinaus). Ich arbeite zurzeit auf WinForms mit C#, Bitmaps, LockBits und Unsafe Code. Ich möchte noch Parallel For implementieren, aber bisher habe ich noch keine Möglichkeit gefunden Convolutuion Filter in Threads aufzuteilen, aber das sei mal dahingestellt. Ich würde gerne meine Berechnungen auf der Grafikkarte laufen lassen. Managed DirectX und XNA sollte das ja können, aber geht glaube ich nicht mehr. Ich glaube etwas mit WPF gelesen zu haben, aber inwiefern mir das weiter hilft weiß ich auch nicht, da ich nicht mit GDI arbeite. Deshalb meine Frage:
    Wie kann ich meine bisherige Methode durch die GPU noch schneller machen?

    Mit freundlichen Grüßen
    Frank
    Hi
    zuerst benötigst du eine parallelisierte Variante deines Algorithmus. Kleine Anmerkung zum selber machen: Parallelisierbar sind die Berechnungen von Ergebnissen dann, wenn sie nicht voneinander abhängen. Tun sie dies, gilt es, erst ein Zwischenergebnis zu bestimmen, von dem aus alle nachfolgenden parallelisierbaren Schritte ausgeführt werden können.
    Anschließend hast du die Wahl zwischen CUDA, OpenCL oder Compute Shader (wie in OpenGL und DirectX) (falls ich welche vergessen habe, sei mir verziehen). Sobald du dich für eines entschieden hast, arbeite dich ein und schau' dir vielleicht mal ähnliche Ansätze von Anderen an (GitHub, Google).

    Für DirectX hast du hier eine Dokumentation: msdn.microsoft.com/en-us/library/ff476331(v=VS.85).aspx

    Über RenderToTexture hast du zwar in XNA bzw. Managed DirectX die Möglichkeit, zurück in einen Puffer zu zeichnen, aber du hast afaik keine Garantie, dass die Ergebnisse 100% akkurat sind.

    Gleich mal vorweg als Hinweis: Das ist definitiv nicht trivial. Mein Rat wäre, sich erst mal in Shaderprogrammierung in DirectX oder OpenGL/Vulkan einzulesen und zu verstehen, wie Compute-Shader arbeiten (es schadet aber nicht, sich auch die Renderpipeline anzusehen) und die Intuition hinter dieser Sache zu entwickeln. Versuche, sämtliche Datenstrukturen, die du zur Verfügung hast, zu verstehen und zu verstehen, wann man diese einsetzt und wann andere besser geeignet sind.
    Wichtig ist außerdem auch die Einbindung in der gewählten Schnittstelle. In DirectX hast du bspw. Usage, CpuAccess, usw. die entsprechend gewählt werden müssen (OpenGL habe ich bisher nur gelesen, nicht verwendet, daher kann ich dazu keine Aussage treffen, aber ggf. kann dir @jvbsl da mehr dazu erzählen, falls es dich interessiert. Es sollte nicht allzu anders laufen).

    CUDA habe ich selbst noch nicht angefasst, aber ich kenne ein paar Leute, die damit schon mal etwas gemacht haben und meinten, sie würden damit glücklich.

    Ach ja: Du kommst um die Shader kaum herum, außer du findest eine Anwendung, die genau das macht, was du suchst. Daher würde auch WPF keinen Vorteil bringen.

    Viele Grüße
    ~blaze~
    Sogenannte ShaderEffects sind in WPF ohne größere Umwege machbar: codeproject.com/Articles/71617…ith-Shader-Effects-in-WPF
    Hab ich mal probiert und funktioniert auch, ist aber sehr beschränkt, wenn es um Features geht. Speziell wird eigentlich immer UI gerendert. Man kann mithilfe von RenderTargetBitmap UI auf eine Bitmap rendern, aber ich weiß nicht, wie man die dann wiederum in einem Shader-Effekt weiterverwendet.
    Eventuell wäre es also mal einen Versuch wert.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Okay, danke für eure Rückmeldungen und tut mir leid, dass ich mich erst jetzt melde, aber ich hatte leider keine Zeit.
    Machen wir das mal an einem Beispiel fest: Convolution Filter.
    Heißt ein "Zeiger"(ich meine jetzt keinen Pointer) geht jeden Pixel in einem Bild durch und legt einen Kernel(Matrix z.B. 3x3) darauf. Der Farbwert des Pixels auf den gezeigt wird wird berechnet in der Abhängigkeit der Pixel, welche durch die Matrix erfasst werden. Ein Bild:


    1. Wie teile ich das in Threads auf? Ich habe es mal mit Parallel-For probiert, theoretisch sollte das funktionieren, da die Operationen nicht voneinander abhängen.
    "Code"

    C#-Quellcode

    1. public static Bitmap ConvolutionFilter(Bitmap img, double[] kernel)
    2. {
    3. LBitmap lbmp = new LBitmap(img);
    4. Bitmap img2 = GetBlackBitmap(img.Size);
    5. LBitmap lbmp2 = new LBitmap(img2);
    6. lbmp.Lock();
    7. lbmp2.Lock();
    8. int k = Convert.ToInt32(Math.Sqrt(kernel.Count()));
    9. Parallel.For(0, lbmp.Height, y =>
    10. {
    11. for (int x = 0; x <= lbmp.Width - 1; x++)
    12. {
    13. int r = 0;
    14. int g = 0;
    15. int b = 0;
    16. int p = 0;
    17. for (int ky = y - Convert.ToInt32(Math.Floor(k / 2.0)); ky <= y + Math.Floor(k / 2.0); ky++)
    18. {
    19. for (int kx = x - Convert.ToInt32(Math.Floor(k / 2.0)); kx <= x + Math.Floor(k / 2.0); kx++)
    20. {
    21. Color col = lbmp.GetPixel(Convert.ToInt32(kx), Convert.ToInt32(ky));
    22. r += Convert.ToInt32(col.R * kernel[p]);
    23. g += Convert.ToInt32(col.G * kernel[p]);
    24. b += Convert.ToInt32(col.B * kernel[p]);
    25. p++;
    26. }
    27. }
    28. b = (b > 255 ? 255 : (b < 0 ? 0 : b));
    29. g = (g > 255 ? 255 : (g < 0 ? 0 : g));
    30. r = (r > 255 ? 255 : (r < 0 ? 0 : r));
    31. lbmp2.SetPixel(x, y, Color.FromArgb(r, g, b));
    32. }
    33. });
    34. lbmp.Unlock();
    35. lbmp2.Unlock();
    36. return img2;
    37. }



    Wenn ich das aber auf ein Bild anwende bekomme ich Störungen, also irgendwas ist da nicht ganz Threadsicher :(

    2. Was für ein Methode bietet sich nun an von den genannten? OpenGL(habe ich bis jetzt am meisten gelesen)?

    Bin ein Fan von Schritt für Schritt Anleitungen mit eventuellen Beispielen :thumbsup:

    Mit freundlichen Grüßen
    Frank
    Da ich noch nie von einer LBitmap-Klasse gehört habe, kann ich nicht sagen, was die macht. SetPixel in Zeile 34 wird wohl nicht threadsicher sein. Mit mehreren Threads gleichzeitg an einem Objekt rumzuschrauben ist generell gefährlich.
    Könntest Du eventuell ein Beispiel von den Störungen zeigen?

    Ansonsten sollte es so funktionieren (außer Du hast sonst noch einen Berechnungsfehler, den ich jetzt nicht erkennen kann).
    Nebenbei: Convert.ToInt32(Math.Floor(k / 2.0))
    k ist bereits ein Integer. In C# gilt Integer / Integer = Integer Du kannst also direkt k / 2 (beachte das fehlende .0, das macht nämlich einen Double draus) verwenden und es wird "automatisch" gegen 0 gerundet, was für positive Zahlen dem Abrunden entspricht.

    Für Convolution-Filter sind PixelShader geradezu maßgeschneidert. Ich hab mal auf die Schnelle was in HLSL zusammengewürfelt (ist bei mir Teil eines C++-Projektes):

    Quellcode

    1. SamplerState PointSampler
    2. {
    3. Filter = MIN_MAG_MIP_POINT;
    4. AddressU = Mirror;
    5. AddressV = Mirror
    6. };
    7. Texture2D SourceTexture;
    8. float TextureWidth;
    9. float TextureHeight;
    10. float3 SampleAtKernelSubPixel(float2 CenterUV, float SubPixelOffsetX, float SubPixelOffsetY)
    11. {
    12. return SourceTexture.Sample(PointSampler,
    13. float2(CenterUV.x + SubPixelOffsetX / (TextureWidth - 1),
    14. CenterUV.y + SubPixelOffsetY / (TextureHeight - 1))).rgb;
    15. }
    16. float4 KernelPixelShader(PixelShaderInput Input) : SV_Target
    17. {
    18. // Da ich nicht mehr weiß, ob und wie man ein Array von Floats in WPF hier rein bekommt (und weil ich faul war) habe ich die Kernel-Werte fix hier reingeschrieben.
    19. float3 KernelSum =
    20. SampleAtKernelSubPixel(Input.UV, -1, -1) * 2 +
    21. SampleAtKernelSubPixel(Input.UV, 0, -1) * 1 +
    22. SampleAtKernelSubPixel(Input.UV, 1, -1) * 0 +
    23. SampleAtKernelSubPixel(Input.UV, -1, 0) * 1 +
    24. SampleAtKernelSubPixel(Input.UV, 0, 0) * 0 +
    25. SampleAtKernelSubPixel(Input.UV, 1, 0) * -1 +
    26. SampleAtKernelSubPixel(Input.UV, -1, 1) * 0 +
    27. SampleAtKernelSubPixel(Input.UV, 0, 1) * -1 +
    28. SampleAtKernelSubPixel(Input.UV, 1, 1) * -2;
    29. return float4(saturate(KernelSum / 2 + 0.5), 1);
    30. }

    Sowas bekommt man auch relativ einfach in einen WPF-Effekt rein. Da gibt's ein paar Sachen, die man spezifisch für WPF beachten muss, aber an die Details kann ich mich nicht mehr erinnern.
    Generell gilt aber:
    float ist ein einziger Float. float2, float3 und float4 sind Zusammengehängte Floats (2, 3 und 4 Floats zusammen). Die einzelnen Komponenten sind immer (in dieser Reihenfolge) x, y, z, w oder r, g, b, a. x ist das selbe wie r, y das selbe wie g, etc. weil sie manchmal Farben und manchmal Positionen verwendet werden.
    Bei HLSL geht alles von 0 bis 1. Farbwerte, Texturkoordinaten, etc.
    Bei Texturkoordinaten (das, was als zweites Argument an SourceTexture.Sample geht) ist 0 das linke bzw. obere Pixel und 1 das rechte bzw. untere Pixel. Das ist unabhängig von der Größe der eigentlichen Textur. Hier tritt das "fencepost problem" auf. Die Textur ist X Pixel ("Zaunpfosten") breit, aber zwischen dem linken und den rechten Pixel liegen nur X - 1 Pixel ("Zaunabschnitte"). Deshalb muss durch TextureHeight - 1 dividiert werden.
    saturate klemmt den angegebenen Wert einfach zwischen 0 und 1 ein. Wie Zeilen 30 bis 32 bei dir, nur wird das automatisch für jede Komponente des float3 gemacht.
    Der PointSampler sagt einfach nur, dass beim Raussuchen des Pixels aus der Textur NearestNeighbour (heißt "pointsampling") verwendet wird. Die Angaben von Wrap beziehen sich auf UVs außerhalb von 0 bis 1 (hier nachzulesen).

    Jo, ich würde sagen, einfach mal probieren.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    @Niko Ortner LBitmap ist ne Klasse mit der ich Unsafe Code, also mit Pointer und LockBits vereinfacht hab:
    Spoiler anzeigen

    C#-Quellcode

    1. public unsafe class LBitmap
    2. {
    3. private struct PixelData
    4. {
    5. public byte blue;
    6. public byte green;
    7. public byte red;
    8. public byte alpha;
    9. }
    10. Bitmap bmp;
    11. int pwidth;
    12. BitmapData bmpd;
    13. Byte* ptr;
    14. public int Width;
    15. public int Height;
    16. public LBitmap(Bitmap img)
    17. {
    18. bmp = img;
    19. Width = bmp.Width;
    20. Height = bmp.Height;
    21. }
    22. public void Lock()
    23. {
    24. pwidth = (int)(bmp.Width * sizeof(PixelData));
    25. if (pwidth % 4 != 0) pwidth = 4 * (pwidth / 4 + 1);
    26. bmpd = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
    27. ptr = (Byte*)bmpd.Scan0.ToPointer();
    28. }
    29. private PixelData* pixelData;
    30. public Color GetPixel(int x, int y)
    31. {
    32. if (x >= 0 && x < Width && y >= 0 && y < Height)
    33. {
    34. pixelData = (PixelData*)(ptr + y * pwidth + x * sizeof(PixelData));
    35. return Color.FromArgb(pixelData->alpha, pixelData->red, pixelData->green, pixelData->blue);
    36. }
    37. return Color.Empty;
    38. }
    39. public Color GetPixel(Point point)
    40. {
    41. if (point.X >= 0 && point.X < Width && point.Y >= 0 && point.Y < Height)
    42. {
    43. pixelData = (PixelData*)(ptr + point.Y * pwidth + point.X * sizeof(PixelData));
    44. return Color.FromArgb(pixelData->alpha, pixelData->red, pixelData->green, pixelData->blue);
    45. }
    46. return Color.Empty;
    47. }
    48. public void SetPixel(int x, int y, Color color)
    49. {
    50. if (x >= 0 && x < Width && y >= 0 && y < Height)
    51. {
    52. PixelData* data = (PixelData*)(ptr + y * pwidth + x * sizeof(PixelData));
    53. data->alpha = color.A;
    54. data->red = color.R;
    55. data->green = color.G;
    56. data->blue = color.B;
    57. }
    58. }
    59. public void SetPixel(Point p, Color color)
    60. {
    61. if (p.X >= 0 && p.X < Width && p.Y >= 0 && p.Y < Height)
    62. {
    63. PixelData* data = (PixelData*)(ptr + p.Y * pwidth + p.X * sizeof(PixelData));
    64. data->alpha = color.A;
    65. data->red = color.R;
    66. data->green = color.G;
    67. data->blue = color.B;
    68. }
    69. }
    70. public void Unlock()
    71. {
    72. bmp.UnlockBits(bmpd);
    73. }
    74. }



    Ich weiß nicht ob die Klasse einwandfrei ist, aber ich denke schon. Hier mal ein Bild mit Störpixel:

    (Ohne Parallel-For und mit normalen For habe ich keine Störpixel, ist dafür aber deutlich langsamer)

    Dein Beispiel verwendet HLSL, inwiefern kann ich so einen Code in mein C# Projekt einbinden? Ist OpenGL eventuell neuer und mehr verbreitet?
    Was ist bei deiner Erklärung eine "Textur"?

    @~blaze~ Ja habe mehrere Filter und Methoden die ich hardwarebeschleunigen möchte, der Convolution-Filter ist nur die einfachste Funktion, mit der ich auch anfangen möchte.
    Beginne am besten damit, dich in DirectX einzulesen, dann werden sich deine Fragen erübrigen. Nach kürzester Zeit kommst du bei Shadern an. Was du im Prinzip tun wirst, ist, zwei Dreiecke durch den Screen zu legen und ausgehend von diesem dann einen Pixelshader bzw. Fragmentshader in OpenGL ausführen. Texturen sind jene Objekte, die deine Bilddaten bereitstellen. Sampler states werden in Shadern dann dazu verwendet, die Bilddaten zu extrahieren.
    Ich halte weiterhin Pixelshader nicht unbedingt für das Mittel der Wahl, sondern würde eher das Zurückgreifen auf Compute Shader für sinnvoll. Btw. weiß ich nicht mal, ob Rendertargets mit beliebiger Größe funktionieren. Ich vermute es zwar stark, aber müsste ich erst nachlesen oder testen.

    OpenGL ist meiner Einschätzung nach ein wenig komplizierter, als DirectX, aber dafür einigermaßen plattformunabhängig. DirectX ist auf jedem Windows über Windows XP vorinstalliert, ich glaube sogar DirectX 11.

    Viele Grüße
    ~blaze~
    Hab vergessen, dass es um OCR geht :D Ja, die komplexeren Filter werden in WPF nicht so einfach umzusetzen sein. Vollständigkeitshalber beantworte ich die Fragen trotzdem:
    Das Problem ist, dass pixelData ein Feld ist (Zeile 35): Hardwarebeschleunigung
    Ein Thread schreibt da was rein, danach schreibt ein anderer Thread was rein, und dann arbeiten beide mit dem letzten Wert weiter. Das Feld sollte garnicht existieren. Deklariere pixelData in jeder Methode separat als lokale Variable.
    Ansonsten ist der Code OK, aber: Das, was bei Dir pwidth ist, ergibt sich automatisch aus bmpd.Stride. Wenn Stride negativ ist, liegt das Bild von unten nach oben im Speicher. ptr zeigt dann also auf das linke Pixel in der untersten Zeile, ptr + 1 auf das Pixel rechts daneben, ptr + Stride auf das linke Pixel in der Zeile darüber, etc. Dieser Fall ist zwar selten, wird aber so wies aussieht von Deinem Code auch nicht behandelt. (Eine vertikale Kantenerkennung würde dann "verkehrte" Werte liefern.)

    xd-franky-5 schrieb:

    Dein Beispiel verwendet HLSL, inwiefern kann ich so einen Code in mein C# Projekt einbinden?

    Füge im Projektmappenexplorer eine neue Datei mit der Endung .fx hinzu. Visual Studio kann mit der Datei erst mal nicht viel anfangen. Abhängig von der Version wird aber Syntax-Highlighting unterstützt (z.B. 2015 kann das, 2010 nicht). Dann musst Du die Datei durch einen speziellen Compiler schieben, der eine .ps-Datei daraus macht. Diese Datei fügst Du dann auch dem Projekt hinzu und dann musst Du im Einstellungs-Fensterchen bei "Buildvorgang" auf "Effekt" stellen. Jetzt bekommst Du die Datei über eine Pack-URI in den Effekt hinein. Die Details stehen auch auf der verlinkten CodeProject-Seite.

    xd-franky-5 schrieb:

    Ist OpenGL eventuell neuer und mehr verbreitet?

    Das weiß ich leider nicht. Auf der Uni hatten wir einfach Grafikprogrammierung mit DirectX, also eben HLSL. Ich hörte aber von einem renomierten Spieleentwickler, dass OpenGL um einiges komplizierter als DirectX sei. Mehr kann ich leider nicht sagen.

    xd-franky-5 schrieb:

    Was ist bei deiner Erklärung eine "Textur"?

    Äh, einfach ein Bild :D
    In Deinem Fall also das Bild, das Du verarbeiten möchtest.
    Bei Grafikprogrammierung wird sowas immer als "Textur" bezeichnet.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    @Niko Ortner also ich hab‘ mich ein wenig eingelesen in DirectX und ComputeShadern, aber konnte jetzt noch nicht arg viel lernen da sich kaum was nützliches finden lässt außer ein paar MSDN-Beiträge. Lassen wir das mit dem pixelData mal weg, ich möchte mit HLSL arbeiten. Außerdem geht es nicht zwingend um OCR sondern erstmal um Bildverarbeitung :)
    Also nehmen wir mal an ich konnte so eine ps-Datei kompilieren und habe nun den Effekt. Wie wende ich den auf eine Bitmap in einem WinForms Projekt in C# an.
    Wie gehe ich nun vor? Die Datei ist ja leer, ich finde leider kaum gut erklärende Beispiele. Hier hat ja bereits wer einen Shader geschrieben, aber das war ja kein Compute-Shader. Ich hoffe ihr könnt mir hier weiterhelfen :)

    Edit: Würde das funktionieren coding-experiments.blogspot.de/2010/07/convolution.html?m=1 ? Bin im Moment nicht zu Hause und kann es nicht testen, ich kann auch nicht erkennen ob das HLSL ist und ob das ein Compute-Shader ist

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „xd-franky-5“ ()

    @xd-franky-5
    Das Beispiel, das Du gefunden hast, ist GLSL:
    Now, GLSL pixel shader code which performs convolution with all these kernels =>


    xd-franky-5 schrieb:

    WinForms Projekt

    Beachte, dass sich diese ShaderEffects auf WPF beziehen. Du kannst aber höchst wahrscheinlich trotzdem die WPF-Klassen in einem WinForms-Projekt verwenden, indem Du die nötigen Verweise hinzufügst.

    xd-franky-5 schrieb:

    Compute-Shader

    Also ich weiß nur, dass PixelShader in WPF funktionieren. Ich weiß auch, dass VertexShader definitiv nicht funktionieren. Deshalb vermute ich stark, dass ComputeShader auch nicht funktionieren.
    Effekte in WPF sind ja eigentlich dazu gedacht, sowas zu machen:

    Ich habe einfach ein bestehendes Programm genommen, meine Effekt-Klasse mit der ps-Datei mit dem Effekt von Post #7 (mit ein paar Änderungen) eingefügt, Window.Effect auf den Effekt gesetzt und gestartet. Den Effekt kann man auf beliebige UI-Elemente anwenden.

    Aber zur besseren Demonstration habe ich ein Testprojekt aufgesetzt. Es ist eigentlich super simpel:

    VB.NET-Quellcode

    1. 'MyEffect1.vb
    2. Public Class MyEffect1
    3. Inherits Effects.ShaderEffect
    4. 'Wird immer benötigt. Das ist das das UI-Element, bevor der Effekt darauf angewendet wurde.
    5. Public Shared ReadOnly InputProperty As DependencyProperty = Effects.ShaderEffect.RegisterPixelShaderSamplerProperty("Input", GetType(MyEffect1), 0)
    6. 'Wird für den Convolution-Filter benötigt.
    7. Public Shared ReadOnly SourceWidthProperty As DependencyProperty = DependencyProperty.Register("SourceWidth", GetType(Double), GetType(MyEffect1), New UIPropertyMetadata(0.0, PixelShaderConstantCallback(0)))
    8. Public Shared ReadOnly SourceHeightProperty As DependencyProperty = DependencyProperty.Register("SourceHeight", GetType(Double), GetType(MyEffect1), New UIPropertyMetadata(0.0, PixelShaderConstantCallback(0)))
    9. 'Ein Test, um zu demonstrieren, wie man beliebige andere Texturen (sprich Bilder) in den Shader bekommt.
    10. Public Shared ReadOnly SecondImageProperty As DependencyProperty = Effects.ShaderEffect.RegisterPixelShaderSamplerProperty("SecondImage", GetType(MyEffect1), 1)
    11. Public Property Input As Brush
    12. Get
    13. Return DirectCast(GetValue(InputProperty), Brush)
    14. End Get
    15. Set(value As Brush)
    16. SetValue(InputProperty, value)
    17. End Set
    18. End Property
    19. Public Property SourceWidth As Double
    20. Get
    21. Return DirectCast(GetValue(SourceWidthProperty), Double)
    22. End Get
    23. Set(value As Double)
    24. SetValue(SourceWidthProperty, value)
    25. End Set
    26. End Property
    27. Public Property SourceHeight As Double
    28. Get
    29. Return DirectCast(GetValue(SourceHeightProperty), Double)
    30. End Get
    31. Set(value As Double)
    32. SetValue(SourceHeightProperty, value)
    33. End Set
    34. End Property
    35. Public Property SecondImage As Brush
    36. Get
    37. Return DirectCast(GetValue(SecondImageProperty), Brush)
    38. End Get
    39. Set(value As Brush)
    40. SetValue(SecondImageProperty, value)
    41. End Set
    42. End Property
    43. Public Sub New()
    44. PixelShader = New Effects.PixelShader
    45. 'Das hier lädt die .ps-Datei.
    46. 'Es gibt zwei Möglichkeiten:
    47. 'Entweder man gibt einen Dateistream an, aus dem der Bytecode gelesen wird:
    48. PixelShader.SetStreamSource(System.IO.File.OpenRead("Pfad\Zur\Effekt-Datei.ps"))
    49. 'Oder man lädt eine eingebettete Ressource:
    50. PixelShader.UriSource = New Uri("pack://application:,,,/Pfad/Zur/Effekt-Datei.ps")
    51. 'Für jede DependencyProperty hier ein Aufruf.
    52. UpdateShaderValue(InputProperty)
    53. UpdateShaderValue(SourceWidthProperty)
    54. UpdateShaderValue(SourceHeightProperty)
    55. UpdateShaderValue(SecondImageProperty)
    56. End Sub
    57. End Class

    Wenn Du eine eingebettete Ressource verwenden willst, dann musst Du die zu den Ressourcen hinzufügen. Das, was ich in Post #11 erklärt habe, mit "Buildvorgang" auf "Effekt" stellen, trifft nur zu, wenn Du so ein komisches Plugin installiert hast. Das ist dann auch auf CodeProject erklärt. Ich habe das bei mir aber so gelöst:
    Die .fx-Datei habe ich ganz normal zum Projekt hinzugefügt, um sie in Visual Studio editieren zu können. Mit fsc.exe habe ich die .fx-Datei zu einer .ps-Datei kompiliert: "C:\Program Files (x86)\DirectX10\Utilities\bin\x64\fxc.exe" /O0 /Fo "Effekt-Datei.ps" /Fe "Errors.txt" /Zi /T ps_2_0 "Effekt-Datei.fx"
    Diese .ps-Datei habe ich dann dem Projekt hinzugefügt (Shift+Alt+A) und dort dann bei "Buildvorgang" auf "Ressource" gestellt.
    Beachte: "/Pfad/Zur/Effekt-Datei.ps" in der URI ist relativ zum Projekt-Ordner. Also da, wo auch die .csproj-Datei liegt.
    Du musst aber keine Ressource verwenden. Du kannst, wie oben im Code-Kommentar erklärt, einfach einen Stream auf eine Datei öffnen, die irgendwo liegt.

    Jetzt kann man den Effekt verwenden:

    Quellcode

    1. <Window
    2. x:Class="MainWindow"
    3. xmlns:l="clr-namespace:WpfEffectTest01"
    4. ...
    5. >
    6. <Grid>
    7. <Image x:Name="OutputImage" Source="https://www.google.at/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png">
    8. <Image.Effect>
    9. <l:MyEffect1
    10. SourceWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}"
    11. SourceHeight="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}"
    12. >
    13. <l:MyEffect1.SecondImage>
    14. <ImageBrush ImageSource="https://i.pinimg.com/736x/8d/97/cf/8d97cf8e8d8ba30aca3bf4571eb74a4c--apple--free-icon.jpg"/>
    15. </l:MyEffect1.SecondImage>
    16. </l:MyEffect1>
    17. </Image.Effect>
    18. </Image>
    19. </Grid>
    20. </Window>

    Das Binding bei SourceWidth und SourceHeight sorgt dafür, dass für diese zwei Werte immer die Größe verwendet wird, die das UIElement hat, auf das der Effekt angewendet wird. Dadurch funktioniert der Convolution-Filter korrekt.
    Das ist das Ergebnis:

    Das Google-Logo mit Edge-Detection und irgend ein Bild von einem Apfel, einfach ohne Effekt.
    Das ist der HLSL-Code, den ich dafür verwendet habe:

    Quellcode

    1. //Beachte, wie 0 und 1 hier mit den Dependency-Properties übereinstimmt.
    2. sampler2D ControlTexture : register(S0);
    3. sampler2D SecondImage : register(S1);
    4. float SourceWidth : register(C0);
    5. float SourceHeight : register(C1);
    6. float3 SampleAtKernelSubPixel(float2 CenterUV, float SubPixelOffsetX, float SubPixelOffsetY)
    7. {
    8. return tex2D(ControlTexture,
    9. float2(CenterUV.x + SubPixelOffsetX / (SourceWidth - 1),
    10. CenterUV.y + SubPixelOffsetY / (SourceHeight - 1))).rgb;
    11. }
    12. float4 main(float2 UV : TEXCOORD) : COLOR
    13. {
    14. float3 KernelSum =
    15. SampleAtKernelSubPixel(UV, -1, -1) * 2 +
    16. SampleAtKernelSubPixel(UV, 0, -1) * 1 +
    17. SampleAtKernelSubPixel(UV, 1, -1) * 0 +
    18. SampleAtKernelSubPixel(UV, -1, 0) * 1 +
    19. SampleAtKernelSubPixel(UV, 0, 0) * 0 +
    20. SampleAtKernelSubPixel(UV, 1, 0) * -1 +
    21. SampleAtKernelSubPixel(UV, -1, 1) * 0 +
    22. SampleAtKernelSubPixel(UV, 0, 1) * -1 +
    23. SampleAtKernelSubPixel(UV, 1, 1) * -2;
    24. float3 Color1 = saturate(KernelSum / 2 + 0.5);
    25. float3 Color2 = tex2D(SecondImage, UV).rgb;
    26. return float4((Color1 + Color2) / 2, 1);
    27. }


    Jo. Mehr fällt mir im Moment gerade nicht ein. Wenn irgendwas unklar ist, einfach fragen.


    Edit: Ach ja! Das ganze wieder in eine Bitmap bekommen.
    Zum Teil hier abgeguckt: blogs.msdn.microsoft.com/jaime…/rendertargetbitmap-tips/

    VB.NET-Quellcode

    1. Dim Bounds = VisualTreeHelper.GetDescendantBounds(EffectImage)
    2. Dim TempVisual As New DrawingVisual
    3. Using Context = TempVisual.RenderOpen
    4. Dim Brush As New VisualBrush(EffectImage)
    5. Context.DrawRectangle(Brush, Nothing, New Rect(0, 0, Bounds.Width, Bounds.Height))
    6. End Using
    7. Dim Bipm As New RenderTargetBitmap(CInt(Bounds.Width), CInt(Bounds.Height), 96, 96, PixelFormats.Pbgra32)
    8. Bipm.Render(TempVisual)
    9. 'Bipm kann dann wieder z.B. in einem ImageBrush verwendet werden und somit in einen weiteren ShaderEffect verwendet werden.
    10. 'Alternativ auch als Datei abspeichern:
    11. Dim Encoder As New PngBitmapEncoder
    12. Encoder.Interlace = PngInterlaceOption.Off
    13. Encoder.Frames.Add(BitmapFrame.Create(Bipm))
    14. Using OutputStream = System.IO.File.OpenWrite("RenderedImage.png")
    15. Encoder.Save(OutputStream)
    16. End Using

    Aber soweit ich das verstanden habe verwendet RenderTargetBitmap nur Software-Rendering. Das ist natürlich doof. Es scheint, dass WPF für so komplexe Sachen einfach nicht geeignet ist.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Niko Ortner“ ()

    @Niko Ortner Okay, danke erst mal. Mal paar Fragen:
    1. Zieht das Nachteile mit sich, wenn ich Shader auf WinForms benutze anstatt mit WPF?

    2. @~blaze~ meinte: "Ich halte weiterhin Pixelshader nicht unbedingt für das Mittel der Wahl, sondern würde eher das Zurückgreifen auf Compute Shader für sinnvoll." Meine Vorhaben sind grob:
    1. resize and gray level
    2. gaussian blur (Convolution)
    3. Histogram Equalization and Otsu
    6. Segmentation: k-means or SLIC-Superpixel
    Die sollten alle parallelisierbar sein außer Histogram Equalziation glaub' ich.
    Und später eventuell noch ein neuronales Netzwerk, wobei ich noch nicht weiß, ob das mit der Grafikkarte funktioniert. Was wäre denn nun besser?

    3. Der erste Code scheint Standard zu sein, der dritte ist HLSL, wobei ich aber fand' dass GLSL einfacher aussieht. Dazu fällt mir jetzt keine Frage ein, einfach eine Feststellung.

    4. "Aber soweit ich das verstanden habe verwendet RenderTargetBitmap nur Software-Rendering. Das ist natürlich doof. Es scheint, dass WPF für so komplexe Sachen einfach nicht geeignet ist."
    Heißt, was sollte ich denn nun am besten verwenden für mein genaues Vorhaben? Am liebsten wäre mir ja, dass ich auf C# und WinForms bleiben kann und die Shader dann in HLSL oder anderweitiges schreibe und dort einbinde.

    Mit freundlichen Grüßen
    Frank


    EDIT: Sorry, habe aus Versehen vorhin abgeschickt, ist mir auch noch nie passiert ?(
    EDIT2: Wurde bereits genannt: WPF Eigene Effekte leicht gemacht Würde das von der Performance her nicht auch reichen?

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „xd-franky-5“ ()

    Das habe ich in meinem derzeitigen Projekt aber das bringt irgendwann nichts mehr. Ich möchte zwischen 6 und 8 Filter auf ein Bild anwenden und das 30 mal die Sekunde (Kamerabild). Irgendwann kommt die CPU da nicht mehr hinterher, auch bei mehreren Threads nicht, da ein Prozessor nur wenige Kerne hat. Die GPU hat jedoch sehr viele kleine Kerne und kann deshalb Arbeite, welche parallel laufen können (also sowas wie Grafiken) sehr schnell verarbeiten. Und deshalb möchte ich mit der Grafikkarte arbeiten.

    Mit freundlichen Grüßen
    Frank

    EDIT: Außerdem glaube ich, dass ich dem jetzigen Code, der auch oben steht irgendwo ein Memoryleak ist, da die FPS sehr schnell sinken.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „xd-franky-5“ ()

    1. Ich denke nicht. Es ist einfach nur Code, der ausgeführt wird. Aber beachte: WinForms wird ganz anders gerendert, als WPF. Du kannst also nicht einen WPF-Effekt auf ein WinForms-Fenster/Control anwenden. Da WinForms ausschließlich von der CPU gerendert wird, würde das auch keinen Sinn machen.
    Du kannst aber ganz normale WPF-Fenster öffnen und damit arbeiten, wie in einem normalen WPF-Projekt. Dazu musst Du Verweise auf die WPF-Assemblies hinzufügen. PresentationCore, PresentationFramework, WindowsBase und System.Xaml sind die wichtigsten, aber es kann sein, dass Du für Shader-Effekte noch andere Verweise brauchst. Einfach in einem normalen WPF-Projekt nachgucken, welche Verweise dort gesetzt sind und diese dann beim WinForms-Projekt nachtragen.
    Ein WPF-Fenster kannst Du dann ganz normal wie ein WinForms-Fenster öffnen:

    VB.NET-Quellcode

    1. 'Entweder ein komplett neues, leeres Fenster:
    2. Dim WpfWindow As New System.Windows.Window
    3. 'Oder ein über den Projektmappenexplorer eingefügtes Fenster, für das man auch den Designer verwenden kann:
    4. Dim WpfWindow As New Window1
    5. 'Und anzeigen:
    6. WpfWindow.Show()

    Nebenbei:
    Damit Du auch wie in einem WPF-Projekt die ItemTemplates bekommst, musst Du die betroffenen ItemTemplates abändern. Die werden von VisualStudio nämlich ausgeblendet. (Macht normalerweise wenig Sinn, ein WPF-Fenster in ein WinForms-Projekt einzufügen.) Siehe dazu bei Visual Studio - Empfohlene Einstellungen den Punkt "Microsoft.VisualBasic-Namespace bei neuen Projekten". Aber was man genau ändern muss, damit die ItemTemplates dann auch sieht, weiß ich gerade nicht. Ich vermute aber, dass es in der .vstemplate-Datei eine Einstellung gibt, die das steuert.

    2.1. Technisch gesehen kann man in WPF auch nicht mit Pixel-Shadern skalieren. Man müsste das UI-Element (z.B. ein <Image/> auf die gewünschte Größe bringen, WPF skaliert dann den Inhalt selbst runter, und das wird dann in den Pixel-Shader-Effekt reingeschoben. Graustufen ist aber kein Problem.
    2.2. Auch kein Problem. Wobei es in WPF BlurEffect schon aufs Haus gibt. Musst Du nicht mal selber machen ;)
    2.3. Mit Histogram Equalization kenne ich mich zu wenig aus. Es könnte sein, dass es sich teilweise paralellisieren lässt. Und ich vermute auch stark, dass sich das nicht mit einem PixelShader lösen lässt. Otsu (Schwellwert) ist wieder kein Problem.
    2.6. Mit Clustering habe ich ebenfalls wenig Erfahrun. Aber auch hier vermute ich, dass man zumindest teilweise parallelisieren kann. Aber auch hier kommst Du mit PixelShadern wohl nicht weit.

    4. Da ich mit HLSL und DirectX nur in C++ gearbeitet habe (bis auf WPF eben), wäre mein Vorschlag, C++ zu verwenden und von dort aus mit der Grafikkarte zu arbeiten. Eventuell könntest Du sogar C++/CLI verwenden, um die Kamerabilder von .NET zu C++ rüberzuschieben und die Ergebnisse wieder zu .NET zurückzuschieben. Es kann auch sein, dass Du hier mit bestehenden DirectX-Wrappern (XNA, SlimDX, oder was es da so gibt) besser dran bist. Was genau für Dich Sinn macht weiß ich nicht, weil ich nicht weiß, wo die Bilder herkommen und was Du mit den Ergebnissen machen willst.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Gut dann würde 1. keinen Sinn machen für mich, da ich WPF-Effekte auf Bitmaps anwenden wollte und diese dann in z.B. einer PictureBox anzeige wollte.

    Zu 2. und 4. also bis jetzt habe ich Kamera-Bilder direkt per Zeilenweise Internetcode oder mit aforge bekommen, diese werden dann bearbeitet und dann angezeigt. Dabei wollte ich eine Lib machen mit allen Funktionen, so dass ich immer nur mein Bild rein gebe und das Bearbeitete wieder raus bekomme, so wie bei EmguCV, der OpenCV Wrapper für .NET. In der Schule programmiere ich mit C++, auch OOP also sollte das kein Problem sein die Algorithmen dort zu schreiben, da könntest du mir wohl auch besser helfen. Ich muss mich dann wohl später noch einlesen wie ich 'ne C++ DLL in C# benutzen kann.

    Danke bis hier her mal!
    Mit freundlichen Grüßen
    Frank
    Du kannst Pixelshader natürlich schon in Windows Forms einbinden. Du brauchst dazu halt eine Form-Instanz, auf der du das DirectX-Device erzeugen kannst. Der allgemeine Ablauf sollte irgendwie so aussehen (ich habe es bisher noch nicht so oft machen müssen):
    - Form instanziieren
    - DXGI verwenden, um Adapter und Output zu spezifizieren
    - Swapchain-Beschreibung erzeugen (erst mal einlesen, was das ist)
    - Swapchain und Direct3D-Device erzeugen
    - RenderTarget erzeugen und festlegen
    - Ggf. Depth- und Stencil-Buffer erzeugen und festlegen

    An irgendeiner Stelle musst du auch noch den Viewport näher spezifizieren, wenn ich mich recht erinnere.
    Danach kannst du halbwegs rendern und Shader verwenden. Dazu benötigst du erst den Shader-Code und musst ihn auf dem DeviceContext (bei einfachen Anwendungen ist das der immediate context des Devices) festlegen, nachdem du ihn kompiliert und geladen hast.
    Die Konstanten des Shaders werden in constant buffer abgelegt, d.h. du musst halt erst noch die Puffer erzeugen und entsprechend mit Daten befüllen. Setzen kannst du die Konstanten dann auch auf dem jeweiligen device context (d.h. in deinem Fall wohl der immediate context).

    Für einen Render-Vorgang brauchst du Daten und eine Beschreibung dieser Daten für die input assembler stage, damit der weiß, wie er die Semantiken im Shader den Daten im Eingabedatenstrom zuordnen muss. Der einfachste Verlauf wäre bei pixel shadern entsprechend:
    - Shader kompilieren (kann man auch in Bytecode mitliefern, sodass man ihn nicht jedes mal kompilieren muss, hat aber glaube ich auch Nachteile bzgl. Macros)
    - Shader laden (als Pixelshader, Vertexshader, usw.) und auf immediate device setzen
    - Constant buffer für Shader erzeugen, befüllen und
    - Daten (Vertices, Indices, usw.) und Format (Position, Normal, usw., was in den Daten halt drinsteht) für den input assembler bereitstellen
    - Rendern

    Die einfachste Rendering-Pipeline wäre Pixel shader -> Vertex shader und die nicht programmierbaren stages dazwischen (d.h. halt Rasterizer, input assembler, usw.).
    Im Vertexshader wirst du vmtl. auch die Transformation der Vertices über eine Projektionsmatrix oder dergleichen durchführen.

    Wichtig: Constant buffer werden als Buffer modelliert, die entsprechende Usage-Flags und CPU-Access haben, da würde ich dir empfehlen, die Spezifikation in die Hand zu nehmen. Das ist zwar logisch aufgebaut, aber es ist wichtig, zu wissen, was man macht.

    Btw. ist das hier ganz okay, soweit ich weiß: rastertek.com/tutdx11.html
    Ist zwar für DirectX11, aber z.B. SharpDX ist extrem ähnlich implementiert.

    PS: Hier im Forum gab's mal ein Posting, das besagt hat, dass GDI+ inzwischen auch hardwarebeschleunigt sei, aber bisher habe ich das selbst nicht überprüft.

    Viele Grüße
    ~blaze~
    @~blaze~ Danke für deine Antwort, aber ist das nicht etwas zu überkompliziert? Habe mich jetzt in die anderen Techniken noch nicht eingelesen aber deine beschriebene Methode scheint mir etwas über das Ziel hinauszuschießen und ich hab' eingesehen dass das Alles etwas zu viel Umwege hat. Ich habe mal bei OpenCV nachgeschaut, der Ersteller arbeitet auch mit C++ und OpenCL. Wäre das nicht eine bessere Alternative? Also mit C++ und HLSL zu arbeiten und am Ende eventuell einen Wrapper zu basteln (also wenn meine besagten Algorithmen so überhaupt möglich sind). Ich suche im Moment also noch die beste und einfachste (oder ein Kompromiss aus beidem) Möglichkeit Filter und Anderes hardwarebeschleunigt darzustellen. Und mit Pixelshadern komm' ich ja wie @Niko Ortner sagte nicht weit:
    ​Aber auch hier kommst Du mit PixelShadern wohl nicht weit.

    ​wäre mein Vorschlag, C++ zu verwenden und von dort aus mit der Grafikkarte zu arbeiten.


    Mit freundlichen Grüßen
    Frank