• Welcome to Powerbasic Museum 2020-B.
 

News:

Forum in repository mode. No new members allowed.

Main Menu

Layered or DWM Composited Spinner

Started by Patrice Terrier, January 07, 2014, 11:02:08 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Patrice Terrier

This is a two projects in one, to show the difference between using a layered window, and a DWM composited window.

Both are able to play a PNG animation in TOP_MOST mode using variable opacity.

The purpose of a Spinner control is to inform the user that a lengthy process or critical task is running.
But it could also be used to display a nag screen or any transparent PNG file in overlay mode.

SPINNER_LAYERED

Is using the WS_EX_LAYERED extended style, in this type of window the mouse could be used to click on another window overlayed by the spinner as long as you click on a transparent part (empty region).

The layered version uses in SpinnerInit a timer to perform the animation:
void SpinnerInit (IN HWND hParent, IN HWND hWnd, IN WCHAR* szFileName, IN long nSpeedDelay) {

    if (IsWindow(hParent) == 0) { hParent = GetDesktopWindow(); }

    gn_FrameCount = 0;
    if (ghImg) { GdipDisposeImage(ghImg); ghImg = 0; }

    if (GdipLoadImageFromFile(szFileName, ghImg) == 0) {
        GdipGetImageWidth(ghImg, gn_W);
        GdipGetImageHeight(ghImg, gn_H);
        if (gn_W > gn_H) {
            gn_FrameCount = gn_W / gn_H;
            gn_IsHorizontal = -1; }
        else {
            gn_FrameCount = gn_H / gn_W;
            gn_IsHorizontal = 0;
        }

        RECT rw = {0}; GetWindowRect(hParent, &rw);
        MoveWindow(hWnd, rw.left + ((rw.right - rw.left - gn_H) / 2), rw.top + ((rw.bottom - rw.top - gn_H) / 2), gn_H, gn_H, 0);
       
        if (nSpeedDelay == 0) {
            if (gn_FrameCount < 4) {
                gn_DELAY = 75; }
            else if (gn_FrameCount < 8) {
                gn_DELAY = 50; }
            else if (gn_FrameCount < 12) {
                gn_DELAY = 37; }
            else {
                gn_DELAY = TIMER_DELAY;
            }
        }

        SetTimer(hWnd, TIMER_SPINNER, 0, 0); // 66 FPS
    }
}


SPINNER_DWM

Is using a DWM composited window, in this type of window the mouse couldn't be used to click on another window overlayed by the spinner even if the region is fully transparent.

This version uses a child thread rather than a timer to perform the animation, this is the best solution when used on modern hardware built on multi-core I7 (with other config better to use a timer):
long StartAnimation (IN long Delay) {
    long nRet = LB_ERR;
    gn_DELAY = Delay;
    DWORD dwThreadId = 0;
    HANDLE hThread = CreateThread(NULL,                                  // default security attributes
                                  0,                                     // use default stack size 
                                  (LPTHREAD_START_ROUTINE ) Animate,     // thread function name
                                  (LPVOID) Delay,                        // argument to thread function
                                  0,                                     // use default creation flags
                                  &dwThreadId);                          // returns the thread identifier
    if (hThread) { nRet = 0; Sleep(100); }
    CloseHandle(hThread);
    return nRet;
}


HOW TO USE IT

Both window can be dragged at any location, and you have to press the Escape key to close the Spinner.

This code is provided altogether with a small subset of the GDIPLUS Flat API to keep the code as small as possible, and also because it is a mandatory to work in full 32-bit with alpha channel to render correctly the variable opacity.

Several PNG files designed specifically for the purpose of Spinner animation are provided into the attached SPINNER_PNG.zip file. All of them have been reworked with Photoshop to produce the best visual output, and credit must also be given to the original artists who created the original animated GIF files that were transposed into PNG.

The projects have been set to create VS2010+ 64-bit EXE by default, the bin EXE files are not provided inside of the ZIP file, you have to recompile the code first.

In order to play a spinner (with either version):

Unzip first the PNG files into a dedicated folder, and use Explorer to drag and drop any of the PNG file on the Spinner.exe or DwmSpinner.exe.

Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Patrice Terrier

#1
Here is the translation of the DWM Spinner code into PowerBASIC.

The code must be used altogether with the PNG files attached to the first post of this tread.

I have translated only the DWM composited version to PB, because it is the most advanced one.

  • Using a thread animation, instead of a timer
  • Using DWM to deal with transparency and variable opacity.

Note: This code has been moved from the PowerBASIC forum here, because i couldn't post anymore ZIP or picture attachments on PB. And also because message notifications is broken.

About the source code:
I have used exactly the same code for C++ and PB, using UNICODE and SDK coding style, and a small subset of the GDIPLUS Flat API.

In order to create the transparent composited window, DWM must be first enabled with zSetCrystalBehindMode and we fool the OS by using a fake rectangular region with CreateRectRgn(-1, -1, 0, 0). This is what allows us to render the composition directly onto the double buffer of the DirectDraw surface.

However in order to render the transparency correctly, we need first to clear the background of our composited window, just like what we do in OpenGL when using glClear, this is the reason of the creation of a NULL brush to paint the permanent bitmap background, before redrawing each frame of the animation like this:
Quote'// Create a null brush, to clear the background of our permanent bitmap.
hBrush = CreateSolidBrush(%NULL)
SelectObject(g_hDC, hBrush)
FillRect(g_hDC, r, hBrush)
DeleteObject(hBrush)

Once our permanent bitmap has been composited then we BitBlt it onto the DD surface.
Note: BitBlt is able to deal with alpha channel as long as we use it in full 32-bit mode, it does a direct copy of our memory DIB block into the graphic card GPU (at least this what years of practice teached me).

If you have questions about this code, feel free to ask here.
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Theo Gottwald


Patrice Terrier

#3
Because i like to produce the smallest and fastest code for my projects.

I was worried to see that i was able to compile the PowerBASIC "DWM spinner" code in a little 26 Kb, while the C++ 32-bit version was 35 Kb, and the 64-bit version 41 Kb.

Thus i challenged myself to learn more about the arcane of C++ optimization, and finally i was able to produce a resulting C++ 32-bit as small as 10 Kb, and a C++ 64-bit as small as 12 Kb.

I never thought i could produce a 64-bit EXE as small as 12 Kb for this DWM spinner utility,
and like the {Droopy dog} and {Frederick J. Harris} would say:
Quote"you know what... i am happy".

See below the serie of screen shots, showing the parameters i am using to reach this result, and the new C++ DwmSpinner.zip attached to this post (both 32-bit and 64-bit binary EXE are into the ZIP file).

64-bit EXE half the size of PB's 32-bit, could you believe this!

...
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Patrice Terrier

#4
I wanted to understand why there was such a huge difference of size between my 12 Kb C++ code and the {bloated} 26Kb PowerBASIC version.

After doing further optimization without significant code size reduction.

I took the decision to get rid of the #INCLUDE "WIN32API.INC" creating a small declaration subset, with only those needed by the application.

And doing that, BINGO, i was able to reduce the size down, from 26 Kb to 16 Kb, that is only 4 Kb larger than the C++ 64-bit version, thus i am attaching the new optimized PB version to this post :)

...
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Patrice Terrier

#5
I have been able to further reduce the size of the 64-bit C++ EXE down to 11 Kb with Visual Studio 2013.
That was not obvious, because the linked .LIB files are not the same than those used in VS 2010, and there is almost nothing in the whole MSDN, showing how to get rid of the CRT. I had to fight whith the compiler and the linker for a couple of hours, just to figure how to do it.  >:(

If you use VS2013, and want to see the settings i have used in the project, then let me know, and i shall post the VS2013 project there.

Note: one of the main optimization is to use only WCHAR (wchar_t) and get rid of any string/wstring engine (usually a saving of 12 Kb), and avoid any OOP or GDIPLUS class encapsulation.

...

Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Patrice Terrier

#6
I have been able to perform the ultimate optimization of the DWMspinner.

And the result is:

PB32:  DWMspinnerPB.exe = 11260 bytes, and C++64: DWMspinner = 11264 bytes

A difference of only 4 bytes, could you believe this  ???

To achieve this result, i have removed all the extra encapsulations done by the languages, using my own set of functions based on direct call to the core API.

nothing could beat SDK coding style  ;D

See the attached ZIP files

Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Frederick J. Harris

Interesting and nice work Patrice!  When I have time I'm going to look at what you did in more detail and see if I can't shave a few bytes off my recent work.  I hadn't done anything real detailed in my work - just Os and O1 I believe, so maybe with a few more arcane settings I can make it a little smaller. 

Patrice Terrier

#8
Frederick

Thanks for the feedback.

QuoteI'm going to look at what you did in more detail and see if I can't shave a few bytes off my recent work.

  • Always use direct call to the core Flat API (everything is already there).
  • Use only native data type, especially char and wchar_t (this alone saves 13 Kb).
  • Avoid the use of classes and OOP, the extra level of encapsulations requires much more dependencies.
  • Always link with the core API LIBs like: user32.lib;kernel32.lib;gdi32; etc.,
  • and ignore all default LIBs with  Yes(/NODEFAULTLIB).
  • Do not include a MANIFEST.
  • When using a WS_EX_TOOLWINDOW, no need to embed ICON (or any other bloated resources).

Indeed, try to write C++, just like plain C.  :)


Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Frederick J. Harris

I bet I could slim that grid control down by translating that String::Parse() back to a simple C function, converting the whole thing to C (build VTables by hand like some of my other earlier C examples), and use Microsoft's versions of the string primitives rather than the C Runtime versions, i.e., lstrcpy() instead of strcpy(), etc.  That way I could eliminate linking with the C Runtime.  Actually, that was the trick Microsoft uses in ATL, I believe.  They even have a #define _NO_CRT_xxx or something to that effect.  I guess its a matter of how much effort one wants to make and how many K one will get out of it.  If one wouldn't value one's time much, one could just use assembler.  If one wouldn't need floating point math one could save a whooping 3 or 4K or so by not compiling in floating point math! :)

José Roca

If you're going to avoid any C++ features, why don't use a plain ansi C compiler instead?

Patrice Terrier

#11
José--

QuoteIf you're going to avoid any C++ features, why don't use a plain ansi C compiler instead?
It is all a matter of the kind of application you have to write.
When writing utilities and DLLs, i think they should be as fast and as small as possible.

When writing complex CRM or large multimedia projects, like what i am doing with WinDev, then the main factor is efficiency and how long it would take to produce the final EXE. This is the reason why i am using several tools each one having its own set of merits.

For years Bob Zale told us, that 64-bit produces larger code than 32-bit, and based on my own experimentation i would say that this is only a matter of the coding style being used. The PowerBASIC language encapsulation is also producing largest code than a direct call to the core API (aka: 26 Kb down to 11 Kb).

I like the VISUAL STUDIO editor, and especially the new features of VS2013, but what i like the most when using C++ is that i do not have to worry anymore about using the right include files to use the latest and newest API.

The main reason why i have selected C++, was to convert my DLLs to 64-bit, and the use of the vector and the wstring engine helped me much to convert my PowerBASIC code, and allows me to maintain all sources in parallel with a minimum of effort.
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

James C. Fuller

Patrice,
  Outstanding size reduction.
It compiled and ran on my system with Visual Studio 2013 Express but I wanted to take it a step forward (backward :)) and compile from the command line.
I looked up all the command line options listed in VS and came up with this:

from a command prompt to set the paths and machine type:
"%VS120COMNTOOLS%..\..\VC\vcvarsall.bat" x86_amd64



compile:
cl -c dwmspinner.cpp /GL /W3 /Gy /Zc:wchar_t /Gm- /O1 /sdl-  /fp:precise /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /GF /WX- /Zc:forScope /GR- /Gz /Oy /Oi /MT /EHsc  /nologo /Os

compiler Notes:
/GL -> Whole Program Optimization
/W3 -> set warning level 3
/Gy -> Enable Function-Level Linking
/Zc:wchar_t -> a bit confusing to me
/Zc:forScope -> needed? it is the default
/Gm- -> needed ?
/O1 -> small code
/sdl -> Enables additional security features and warnings
/fp:precise -> needed? it is the default
/GF -> Eliminate Duplicate Strings
/WX- -> needed? witout "-" Treats all compiler warnings as errors
/GR- -> disables run-time type information
/Gz -> specifies the __stdcall calling convention
/Oy -> Suppresses creation of frame pointers on the call stack.
/Oi -> Generate Intrinsic Functions (may make app larger)
/MT -> Creates a multithreaded executable file using LIBCMT.lib.
/EHsc -> Exception Handling Model
/nologo -> Suppresses display of sign-on banner.
/Os -> Favors small code.




link:
LINK dwmspinner.obj /OUT:"DWMspinner.exe" /MANIFEST:NO "Dwmapi.lib" "libcpmt.lib" "msvcrt.lib" "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" /MACHINE:X64 /OPT:REF /INCREMENTAL:NO /SUBSYSTEM:WINDOWS /NOLOGO /NODEFAULTLIB


linker notes:
/MANIFEST:NO do not create a manifest
/LTCG is implied with /GL so is it needed?
/NXCOMPAT -> Compatible with Data Execution Prevention. by default it is on
/DYNAMICBASE -> By default, /DYNAMICBASE is on.
/OPT:ICF is on if /OPT:REF is on
/INCREMENTAL:NO not sure on it's use with above /OPT


Would you please address the needed? items
also how about /Oi. msdn says it may create larger exe.


James

Frederick J. Harris

Regarding your question Jose about 'why not just us a C compiler', there are some advantages to mostly using C isms, but compiling as C++, i.e., with a *.cpp extension, as opposed to just a *.c extension.  Some of the stuff is almost silly, such as this ...

struct SomeStruct
{
  ....
};

To use that in C you would have to do this ..

struct SomeStruct ss;

Or, you would have to put the typedef keyword in front of the struct declaration to just be able to declare it like so ...

SomeStruct ss;

That's why you see the typedef specifier so much in headers; to help make declarations cleaner.  In C++ though one can just  be more natural in creating an instance like so ...

SomeStruct ss;

No need for typedefs.

More significant is the issue of passing references.  Strictly speaking, C doesn't have reference passing.  Parameters have to be all passed as pointers if one want the procedure to be able to modify the arguement.

And of course, C++ has classes.  I for one don't use that capability a lot, but I do use it.  In fact, the only way one can deal with strings in any major application is through a string class. 

So I think what actually happened among folks in the C world once C++ became popular, is that some folks just started compiling their C code as C++ and that mostly works with oftentimes only minor modifications.  Sometimes none at all in trivial instances.  I suppose that's how a C coder becomes an instant C++ coder! :)

Then I guess folks start teaching themselves the various parts of C++ such as classes and templates, and they either use them or not, depending on their personal inclinations. 

In my particular case I started in C a long time ago, and actually learned GUI SDK with C.  After that I learned C++ but essentially hated it when I saw how it was used to create class frameworks, which I disliked immensely.  I thought they were horrendous.  So I threw the whole works out and went back to C and later PowerBASIC.  So essentially, back in the early 2000s, I gave up C++.  I just figurred it was horrible and not worth using.

But I suffered tremendously writing everything I had to do in C with only the low level string buffer manipulation functions.  I worked with Windows CE a lot, and used C there because PowerBASIC as you know doesn't work on Windows CE.  Its really, really, really miserable writing major applications that involve substantial string manipulation in C.  So while I was suffering through a lot of this the thought kept nagging at me that if I used C++ and had a string class perhaps the going wouldn't be so hard.  But on the other hand, I hated C++!  And the thing was there that in VC6 times the string class that C++ had was part of MFC, and couldn't be seperated from it.  And at that time - just through my personal ignorance, I didn't know about the string class that was part of the C++ Standard Library.  So my choices seemed to me to be link with MFC to get a string class, and start with 1.5 mb "Hello, World!" programs, or suffer through C with no string handling.

I began to wonder if I hadn't "Thrown The Baby Out With The Bathwater", when I abandoned C++.  Sure, I hated bloated class frameworks that required massive runtimes to be attached to binaries, but I saw no inherent evil in the class keyword.  I saw no inherent evil in C++'s Byref parameter passing, and a number of other improvements to C. 

So it occurred to me that if I could create my own String Class to alleviate the miserableness of C's lack of string handling, I'd have solved the main issue I had with C.  Really, that was the only difficulty I had with the language of C - having to constantly copy bytes around in memory for the silliest things.

So essentially, I'm perfectly happy with the outcome I've now achieved.  I have a completely eclectic approach to the C++ language.  I use only what I want and ignore the rest.  Like you, programming is an enjoyable hobby for me, and I don't have anyone enforcing coding standards on me telling me, for example, that I have to use vectors instead of arrays.  I don't like vectors, I'm under no compunction to use them, so I ignore them.  Actually, I ignore most of what's considered modern C++.  And I feel no obligation to use a C++ ism over a C ism if I like the C ism better.  And I don't even care if the C++ ism is considered by most superior to the C ism.  I don't require total rationality in my choices, because, as I said, I do this for pleasure in many instances. 

So I believe eclectic fairly well describes my use of C++, in that I took from it what I wanted, and left the rest.  And basically what I took was the class keyword which allowed me to solve C's miserable string buffer handling, and that's about it.  Otherwise, I'm basically a C coder.  I really suspect though that I'm not alone.  In one exchange I had on a C++ forum one time a fellow told me that its a natural progression C coders go through in their slow adoption of C++.  He said that the advantages of C++ usually become clear after a time and C coders generally adopt most or all of C and just do things the C++ way.  But I suppose its possible to suffer from 'arrested development', and fail to move on to the next stage.  That's me. 

In my case though I've analyzed the situation back and forth so many times I just can't see what folks see in so much of the stuff I see in C++.  So, as I said, I don't use a lot of it.  While Patrice and I work in quite different areas, we are in total agreement on a lot of it.  He uses vectors, and by association templates, and I don't, but other than that I agree with all he said.  So basically, there aren't really any advantages to compiling in C as opposed to C++.  It isn't the .cpp extension that bloats code - its what gets compiled and linked in. 


Patrice Terrier

#14
James--

Here is my VS2013 compiler command line:
/GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O1 /sdl- /Fd"x64\Release\vc120.pdb" /fp:precise /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /GF /WX- /Zc:forScope /GR- /Gz /Oy /Oi /MT /Fa"x64\Release\" /EHsc /nologo /Fo"x64\Release\" /Os /Fp"x64\Release\DWMspinner.pch"

And the linker one:
/OUT:"D:\VS2013\DWMspinner\x64\Release\DWMspinner.exe" /MANIFEST:NO /LTCG /NXCOMPAT /PDB:"D:\VS2013\DWMspinner\x64\Release\DWMspinner.pdb" /DYNAMICBASE "Dwmapi.lib" "libcpmt.lib" "msvcrt.lib" "kernel32.lib" "user32.lib" "gdi32.lib" /MACHINE:X64 /OPT:REF /INCREMENTAL:NO /PGD:"D:\VS2013\DWMspinner\x64\Release\DWMspinner.pgd" /SUBSYSTEM:WINDOWS /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /ManifestFile:"x64\Release\DWMspinner.exe.intermediate.manifest" /OPT:ICF /ERRORREPORT:PROMPT /NOLOGO /NODEFAULTLIB /TLBID:1
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com