Endlich Pixelkontrolle Visual Studio 22



  • @ralfsobe sagte in Endlich Pixelkontrolle Visual Studio 22:

    Im höchsten Maße nützlich wäre natürlich ein fertiges DEMO-Programm, was man einfach mal als Projekt in dieses Visual Studio laden, und modifizieren kann !

    Mal sehen, viellecht stoppel' ich morgen mal was kleines zusammen, wenn ich zeit finde. Ohne Garantie 😉



  • @Finnegan sagte in Endlich Pixelkontrolle Visual Studio 22:

    @ralfsobe sagte in Endlich Pixelkontrolle Visual Studio 22:

    @Finnegan Danke für die Mühe ! Ich brauch keine Supergeschwindigkeit, aber es müsste schnell genug sein, für eine flüssige Interaktion mit einer großflächig veränderlichen Graphik. SetPixel ist da eh zu langsam, das muß gepuffert werden. Momentan zeigt es sogar erste Anzeichen einer Reaktion auf externe Signale !

    Dann recherchier mal was was mit BitBlt und die Pixel direkt in das Bitmap Schreiben so geht. Das ist machbar, ist aber bei mir schon ewig her. Im Zweifel bekommt man denke ich mit auch SFML schnell etwas in die Richtung hin und spart sich jede Menge Win32-API-Gefummel.

    ... Womit man sich SFML-Gefummel einhandelt, keine Ahnung, was weniger schlimm ist. Momentan will ich
    SendMessage( ... WM_PRINT ... ) probieren, der muß irgendwie meine Eingabe verdauen, nur leider kommt die Nachricht nicht an.



  • @ralfsobe sagte in Endlich Pixelkontrolle Visual Studio 22:

    ... Womit man sich SFML-Gefummel einhandelt, keine Ahnung, was weniger schlimm ist. Momentan will ich
    SendMessage( ... WM_PAINT ... ) probieren, der muß irgendwie meine Eingabe verdauen, nur leider kommt die Nachricht nicht an.

    Ich arbeite damit zwar nicht regelmässig, aber ich bin verdammt sicher, dass das "SFML-Gefummel" deutlich angenehmer ist - wenn die Bibliothek einmal läuft. Win32-API ist echt übel.



  • @ralfsobe Kleine Quick-and-Dirty GDI-Demo mit Bitmap als Buffer und BitBlt um den Buffer zu zeichnen. Pixel im direkten Speicherzugriff lesbar/modifizierbar. Zeichnen mit gedrückter Maustaste, Buffer löschen mit rechter Maustaste:

    #if !defined(UNICODE)
        #define UNICODE
    #endif
    #include <cstdint>
    #include <cmath>
    #include <algorithm>
    #include <random>
    #define NOMINMAX
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    #include <windowsx.h>
    
    static constexpr int pixel_buffer_width = 500;
    static constexpr int pixel_buffer_height = 500;
    
    std::mt19937 random{ std::random_device{}() };
    
    HDC pixel_buffer_dc;
    HBITMAP pixel_buffer_bitmap;
    std::uint32_t* pixel_buffer;
    
    constexpr
    auto rgba_to_color(float r, float g, float b, float a = 1.0f) -> std::uint32_t
    {
        return
            static_cast<std::uint8_t>(std::clamp(b, 0.0f, 1.0f) * 255.0f)
            | (static_cast<std::uint8_t>(std::clamp(g, 0.0f, 1.0f) * 255.0f) << 8)
            | (static_cast<std::uint8_t>(std::clamp(r, 0.0f, 1.0f) * 255.0f) << 16)
            | (static_cast<std::uint8_t>(std::clamp(a, 0.0f, 1.0f) * 255.0f) << 24);
    }
    
    void clear(std::uint32_t color = rgba_to_color(0.0f, 0.0f, 0.0f))
    {
        std::fill(
            pixel_buffer,
            pixel_buffer + pixel_buffer_width * pixel_buffer_height,
            color
        );
    }
    
    void draw_box(int x0, int y0, int x1, int y1, std::uint32_t color)
    {
        if (x0 > x1)
            std::swap(x0, x1);
        if (y0 > y1)
            std::swap(y0, y1);
    
        x0 = std::clamp(x0, 0, pixel_buffer_width);
        y0 = std::clamp(y0, 0, pixel_buffer_height);
        x1 = std::clamp(x1, 0, pixel_buffer_width);
        y1 = std::clamp(y1, 0, pixel_buffer_height);
    
        auto begin = pixel_buffer + y0 * pixel_buffer_width + x0;
        auto end = begin + x1 - x0;
    
        for (int y = y0; y < y1; y++)
        {
            std::fill(begin, end, color);
            begin += pixel_buffer_width;
            end += pixel_buffer_width;
        }
    }
    
    auto next_color(bool reset = false) -> std::uint32_t
    {
        static std::uniform_real_distribution<float> random_color_component(0.0f, 1.0f);
        static float r0, g0, b0;
        static float r1 = random_color_component(random);
        static float g1 = random_color_component(random);
        static float b1 = random_color_component(random);
        static float t = 1.0f;
        if (reset || t >= 1.0f)
        {
            r0 = r1;
            g0 = g1;
            b0 = b1;
            r1 = random_color_component(random);
            g1 = random_color_component(random);
            b1 = random_color_component(random);
            t = 0.0f;
        }
        auto color = rgba_to_color(
            std::lerp(r0, r1, t),
            std::lerp(g0, g1, t),
            std::lerp(b0, b1, t)
        );
        t += 0.01f;
        return color;
    }
    
    auto CALLBACK WndProc(
        HWND window_handle,
        UINT message,
        WPARAM wparam,
        LPARAM lparam
    ) -> LRESULT
    {
        switch (message)
        {
            case WM_RBUTTONDOWN:
            {
                clear();
                InvalidateRect(window_handle, nullptr, false);
            }
            case WM_LBUTTONUP:
            {
                next_color(true);
                break;
            }
            case WM_MOUSEMOVE:
            {
                int x = GET_X_LPARAM(lparam);
                int y = GET_Y_LPARAM(lparam);
                if (wparam & MK_LBUTTON)
                {
                    draw_box(x - 5, y - 5, x + 5, y + 5, next_color());
                    InvalidateRect(window_handle, nullptr, false);
                }
                break;
            }
            case WM_PAINT:
            {
                PAINTSTRUCT ps;
                auto paint_dc = BeginPaint(window_handle, &ps);
                BitBlt(
                    paint_dc,
                    ps.rcPaint.left,
                    ps.rcPaint.top,
                    ps.rcPaint.right - ps.rcPaint.left,
                    ps.rcPaint.bottom - ps.rcPaint.top,
                    pixel_buffer_dc,
                    ps.rcPaint.left,
                    ps.rcPaint.top,
                    SRCCOPY
                );
                EndPaint(window_handle, &ps);
                break;
            }
            case WM_DESTROY:
            {
                PostQuitMessage(0);
                break;
            }
            default:
                return DefWindowProc(window_handle, message, wparam, lparam);
        }
        return 0;
    }
    
    auto APIENTRY WinMain(
        HINSTANCE instance_handle,
        HINSTANCE previous_instance_handle,
        LPSTR command_line,
        int show_window_mode
    ) -> int
    {
        auto gdi_demo_wndclass = WNDCLASSEXW{
            .cbSize = sizeof(WNDCLASSEX),
            .style = CS_HREDRAW | CS_VREDRAW,
            .lpfnWndProc = WndProc,
            .hInstance = instance_handle,
            .lpszClassName = L"gdi-demo"
        };
        RegisterClassExW(&gdi_demo_wndclass);
    
        DWORD window_style = WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX;
        // This calculates the correct window dimensions for given buffer size.
        RECT window_rect = {
            .left = 0,
            .top = 0,
            .right = pixel_buffer_width,
            .bottom = pixel_buffer_height
        };
        AdjustWindowRect(&window_rect, window_style, false);
    
        HWND window_handle = CreateWindowW(
            L"gdi-demo", 
            L"GDI Demo",
            window_style,
            CW_USEDEFAULT,
            0,
            window_rect.right - window_rect.left,
            window_rect.bottom - window_rect.top,
            nullptr,
            nullptr,
            instance_handle,
            nullptr
        );
    
        auto pixel_buffer_bitmap_info = BITMAPINFO{
            .bmiHeader = {
                .biSize = sizeof(BITMAPINFOHEADER),
                .biWidth = pixel_buffer_width,
                // Negative height, so bitmap is not upside down (Windows default).
                .biHeight = -pixel_buffer_height,
                .biPlanes = 1,
                .biBitCount = 32,
                .biCompression = BI_RGB,
                .biSizeImage = pixel_buffer_width * pixel_buffer_height * 4
            }
        };
    
        pixel_buffer_dc = CreateCompatibleDC(GetDC(window_handle));
        pixel_buffer_bitmap = CreateDIBSection(
            pixel_buffer_dc,
            &pixel_buffer_bitmap_info,
            DIB_RGB_COLORS,
            reinterpret_cast<void**>(&pixel_buffer),
            nullptr,
            0
        );
        SelectObject(pixel_buffer_dc, pixel_buffer_bitmap);
    
        clear();
        ShowWindow(window_handle, show_window_mode);
        UpdateWindow(window_handle);
    
        MSG message;
        while (GetMessage(&message, nullptr, 0, 0))
        {
            TranslateMessage(&message);
            DispatchMessage(&message);
        }
    
        DeleteObject(pixel_buffer_bitmap);
        DeleteDC(pixel_buffer_dc);
        DestroyWindow(window_handle);
        UnregisterClassW(L"gdi-demo", instance_handle);
    
        return 0;
    }
    

    Code ist nur ein Prototyp und sehr simpel gehalten - z.B. keinerlei Fehlerbehandlung und Buffer hat feste Größe (Fenster nicht skalierbar).

    Gestestet mit VS22, GCC11.2/MingW und Clang13.0.1/MingW. Code verwendet C++20-Features (in VS aktivieren bzw. GCC/Clang -std=c++20 - und natürlich -lgdi32 mitgeben).



  • @Finnegan Danke ich versuche es mal, zum Leben zu erwecken !



  • Wenn ich mich recht erinnere sollte DrawDibDraw eine der schnellsten Möglichkeiten (wenn nicht die schnellste) sein rohe Pixel aus einem eigenen RAM Puffer in einem "device independent" Format auf den Schirm zu bekommen. Zumindest wenn man Direct2D/Direct3D mal aussen vor lässt.

    Hab' jetzt keinen Beispielcode zur Hand, aber mit etwas Geduld sollte sich das ergoogeln lassen.

    ps: Mit "schnell" meine ich die Laufzeit, nicht wie lange man braucht den Code zu schreiben 🙂



  • @hustbaer sagte in Endlich Pixelkontrolle Visual Studio 22:

    Wenn ich mich recht erinnere sollte DrawDibDraw eine der schnellsten Möglichkeiten (wenn nicht die schnellste) sein rohe Pixel aus einem eigenen RAM Puffer in einem "device independent" Format auf den Schirm zu bekommen. Zumindest wenn man Direct2D/Direct3D mal aussen vor lässt.

    Interessant. Sieht sehr ähnlich aus wie BitBlt und scheint irgendwie zu Video-Rendering-Funktionen zu gehören. Ich kann mir aber nur schwer vorstellen, dass BitBlt nicht ausreichen sollte. Das dürfte eigentlich letztendlich durchs selbe Silizium laufen. Zwischen Arbeitsspeicher und VRAM hin- und her zu kopieren ist eigentlich eine ziemlich grundlegende Funktion von Grafikchips.

    Edit: Ah, aber man scheint direkt einen selbst-reservierten Speicherbereich damit zeichnen zu können. Ohne den Umweg über ein Bitmap-Objekt, das einem den Speicher reserviert. Da ginge dann auch ein z.B. std::vector::data(). Das macht es natürlich etwas interessanter. Das würde einige Zeilen Code bei meinem Beispiel einsparen.

    ps: Mit "schnell" meine ich die Laufzeit, nicht wie lange man braucht den Code zu schreiben 🙂

    Ja, wenn man nicht genau wissen will, wie die Win32 API funktioniert, halte ich auch immer noch sowas wie SFML für die wahrscheinlich simpelste Lösung vom "Code schreiben" her. Würd mich nicht wundern wenn das da grad mal um die 5 Zeilen wären 😉



  • Ich hab' das vor langer, langer Zeit mal probiert, und ich meine eben mich zu erinnern dass DrawDibDraw schneller war als alles andere was ich probiert habe. Wobei es da wirklich um Videos ging die noch dazu im YUV (bzw. YUY2) Format reinbekommen sind, und auch noch mit Interpolation skaliert werden sollten. Und ich kann mich auch nicht mehr erinnern welche anderen APIs ich noch alle probiert habe.

    Ich hab die Videos Frames allerdings auch immer als volle Frames bekommen, direkt unkomprimiert von einer Capture-Karte (bzw. mehreren Capture-Karten). D.h. rein technisch hätte ich genau so gut mit StretchDIBits o.ä. arbeiten können. War aber eben langsamer bzw. eine der Möglichkeiten die ich probiert habe ist rausgefallen weil sie beim Skalieren ganz komische Artefakte erzeugt hat (seltsame Farbränder).

    Das dürfte eigentlich letztendlich durchs selbe Silizium laufen.

    Möglich. Aber muss nicht sein. Und vor allem: selbst wenn es durch's selbe Silizium läuft ist nicht gesagt dass die selben Algorithmen zum Einsatz kommen.

    Und DrawDibDraw hat noch einen Vorteil: es hat den DrawDib DC in dem es alle Möglichen Dinge cachen kann die es eventuell benötigt um die Frames zu malen. Also sowas wie DirectX Surfaces, Shader etc. Theoretisch könnte man die Implementierung da sogar soweit treiben dass man dynamisch optimierten Code generiert für genau die Konvertierung die nötig ist. Wobei ich nicht davon ausgehe dass das gemacht wird 🙂

    Bei den anderen Funktionen hat man natürlich auch den DC - da könnte man auch Dinge drin cachen. Nur sind die anderen Funktionen nicht darauf ausgelegt einen Video-Stream zu rendern. Und das heisst man kann da nicht bei jedem GDI Call davon ausgehen dass genau dieser der primär wichtige Aufruf ist auf den alles optimiert werden soll. Der DrawDib DC kann allerdings sehrwohl davon ausgehen, da er halt dafür da ist ein Video zu rendern. In der Doku wird sogar explizit erwähnt dass man pro Video-Stream einen eigenen DrawDib DC machen soll. (Und bei komprimierten Streams ist das auch nötig, sofern man sie nicht selbst vorher dekomprimiert.)

    Das alles heisst natürlich nicht dass DrawDibDraw schneller sein muss. Aber es deutet schon darauf hin dass die Idee es könnte schneller sein nicht ganz so irre ist 😉



  • @hustbaer sagte in Endlich Pixelkontrolle Visual Studio 22:

    Das alles heisst natürlich nicht dass DrawDibDraw schneller sein muss. Aber es deutet schon darauf hin dass die Idee es könnte schneller sein nicht ganz so irre ist 😉

    Vielleicht. Ich kannte die Funktion halt noch nicht und bin bisher immer davon ausgegangen dass BitBlt die Funktion schlechthin dafür ist.

    Es stimmt allerdings, dass gerade Video-Rendering im Grafiktreiber teilweise speziell implementiert ist, wenn man die entsprechenden Funktionen verwendet. Da zeichnet dann soweit ich weiss der Treiber den Frame direkt auf den Bildschirm, anstatt dass das ganze erst noch durch den Desktop-Compositor gejagt wird.

    Das würde aber m.E. eher Sinn machen, wenn der Grafikchip auch das Video decodiert. Der lpBits-Parameter (RAM-Pointer auf Pixeldaten) von DrawDibDraw spricht aber eher dagegen, denke ich. Da würde ich mehr vertrauen haben, wenn das z.B. ein Handle eines DXVA-Surface oder sowas wäre.

    VfW ist allerdings auch schon steinalt - wie auch BitBlt, es kann gut sein, das all unsere Spekulationen hier alle nicht mehr wirklich relevant sind 😉 ... das Demo-Programm wirkt jedenfalls flott genug, auch wenn es bei jedem MouseMove-Event den Buffer einmal komplett kopiert. Sieht nach 60fps aus bei grad mal um die 3% CPU-Last. Für CPU-Zeichnen ganz okay (bis auf Buffer kopieren, das macht wohl definitiv die GPU). Vielleicht unterschätze ich aber auch wie schnell Computer über die Jahre mittlerweile geworden sind. CPU-getriebene Grafik hab ich sogar noch mit DOS gemacht 😁



  • ... Womit man sich SFML-Gefummel einhandelt, keine Ahnung, was weniger schlimm ist. Momentan will ich
    SendMessage( ... WM_PRINT ... ) probieren, der muß irgendwie meine Eingabe verdauen, nur leider kommt die Nachricht nicht an.

    ich hab nicht alles gelesen aber dein Versuch SendMessage mit WM_PRINT, was soll das bringen? Oder sollte das WM-PAINT heißen was genau so sinnlos ist, denn zum neuzeichnen kannst du nicht einfach WM_PAINT aufrufen, das macht die GDI du kannst nur deinen Zeichenbereich für ungültig erklären (Repaint) und dann wird WM_PAINT vom System angeschoben.


Anmelden zum Antworten