Search code examples
c++windowsgraphicssfml

Draw on top of the screen (Windows) with SFML


on Windows you can draw on top of everything with GDI, taking the draw context of null:

HDC hdc = GetDC(NULL);

I whish to do the same with SFML, but if i try something equivalent (creating render window with NULL as argument, after casting it to hwnd) nothing is drawn anywhere. Is what i'm trying even possible with sfml?


Solution

  • well if you want OpenGL you need a window period. But the window does not need to be visible on the screen. You can combine GDI and OpenGL together to achieve your goal.

    1. Render stuff off screen into bitmap by OpenGL

      using invisible window with the same resolution as your desktop. If the window is invisible it will not react to mouse or keyboard events ...

    2. Copy GL image into CPU side memory

      simple glReadPixels will do.

    3. Copy the image into Desktop (using GDI Bitmap)

      simply convert/copy the raw image data into GDI compatible bitmap and then simply draw it onto desktop canvas. So no SwapBuffers(hdc); as in standard GL app anymore.

    I am coding in C++/VCL environment so I do not have pure WinAPI/GDI knwoledge (VCL do it for me but the code should be very similar the names and operands passed might vary a bit but not too much).

    This is what I come with:

    //---------------------------------------------------------------------------
    #include <vcl.h>
    #include <gl\gl.h>
    #pragma hdrstop
    #include "Unit1.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1  *Form1;                 // VCL Application window object
    TCanvas *scr=NULL;              // Desktop
    DWORD   *txr=NULL;              // memory for GPU->CPU image transfer
    Graphics::TBitmap *bmp=NULL;    // bitmap for CPU->Desktop image transfer
    int     xs,ys;                  // desktop resolution
    HDC     hdc=NULL;               // device context for GL
    HGLRC   hrc=NULL;               // rendering context for GL
    //---------------------------------------------------------------------------
    void gl_draw()
        {
        if (scr==NULL) return;
        if (bmp==NULL) return;
        if (txr==NULL) return;
    
        glClearColor(0.0,0.0,0.0,0.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_CULL_FACE);
    
        // desktop pixel units
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glTranslatef(-1.0,+1.0,0.0);
        glScalef(2.0/float(xs),-2.0/float(ys),1.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
    
        // render rectangle
        GLfloat fx=xs/2,fy=ys/2,fz=0.0,fa=(xs/2)-10,fb=(ys/2)-10;
        glColor3f(1.0,1.0,1.0);
        glBegin(GL_LINE_LOOP);
        glVertex3f(fx-fa,fy-fb,fz);
        glVertex3f(fx-fa,fy+fb,fz);
        glVertex3f(fx+fa,fy+fb,fz);
        glVertex3f(fx+fa,fy-fb,fz);
        glEnd();
    
        if (Form1->Visible)     // normal window GL render
            {
            glFlush();
            SwapBuffers(hdc);
            }
        else{                   // copy GL image directly to desktop
            // copy GL image to CPU side memory
            glFlush();
            glReadPixels(0,0,xs,ys,GL_RGBA,GL_UNSIGNED_BYTE,txr);
            // copy it to bitmap
            int x,y,a; DWORD *p;
            for (a=0,y=0;y<ys;y++)
             for (p=(DWORD*)bmp->ScanLine[y],x=0;x<xs;x++,a++)
              p[x]=txr[a];
            // render it to desktop
            scr->Draw(0,0,bmp);
            }
    
        }
    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
        {
        // desktop
        scr=new TCanvas();
        scr->Handle=GetDC(NULL);
        xs=scr->ClipRect.Width();
        ys=scr->ClipRect.Height()-31;           // leave taskbar out of it
        // BMP
        bmp=new Graphics::TBitmap;
        bmp->HandleType=bmDIB;
        bmp->PixelFormat=pf32bit;
        bmp->SetSize(xs,ys);
        // txr buffer
        txr=new DWORD[xs*ys];
        // window
        BorderStyle=bsNone;
        SetBounds(0,0,xs,ys);
        // GL init
        hdc = GetDC(Handle);                    // get device context for this App window
        PIXELFORMATDESCRIPTOR pfd;
        ZeroMemory( &pfd, sizeof( pfd ) );      // set the pixel format for the DC
        pfd.nSize = sizeof( pfd );
        pfd.nVersion = 1;
        pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
        pfd.iPixelType = PFD_TYPE_RGBA;
        pfd.cColorBits = 24;
        pfd.cDepthBits = 24;
        pfd.iLayerType = PFD_MAIN_PLANE;
        SetPixelFormat(hdc,ChoosePixelFormat(hdc, &pfd),&pfd);
        hrc = wglCreateContext(hdc);            // create current rendering context
        if(hrc == NULL)
            {
            ShowMessage("Could not initialize OpenGL Rendering context !!!");
            Application->Terminate();
            }
        if(wglMakeCurrent(hdc, hrc) == false)
            {
            ShowMessage("Could not make current OpenGL Rendering context !!!");
            wglDeleteContext(hrc);          // destroy rendering context
            Application->Terminate();
            }
        glViewport(0,0,xs,ys);
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormDestroy(TObject *Sender)
        {
        // GL exit
        wglMakeCurrent(NULL, NULL);     // release current rendering context
        wglDeleteContext(hrc);          // destroy rendering context
        // release buffers
        if (scr){ delete scr; scr=NULL; }
        if (bmp){ delete bmp; bmp=NULL; }
        if (txr){ delete[] txr; txr=NULL; }
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormPaint(TObject *Sender)
        {
        gl_draw();
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Timer1Timer(TObject *Sender)
        {
        if (Visible) Visible=false; // hide
        gl_draw();
        }
    //---------------------------------------------------------------------------
    

    Its single Form VCL application with single timer on it. It creates GL context and on first opportunity become invisible. Then it periodically overwrites desktop with black background and white rectangle border ...

    So you need to port the VCL stuff (Form1 and events) into your environment. Also you might want to add transparency to the line:

    scr->Draw(0,0,bmp);
    

    Or read the desktop image and use it as a background texture for the GL rendering.

    PS. pure GDI rendering is much simpler and probably faster than this but your have required GL so ...