POV-Ray : Newsgroups : povray.off-topic : Tell me it isn't so! : Re: Tell me it isn't so! Server Time
10 Oct 2024 05:20:28 EDT (-0400)
  Re: Tell me it isn't so!  
From: clipka
Date: 28 Jul 2009 00:50:04
Message: <web.4a6e826aac52dfd4813466d60@news.povray.org>
"David H. Burns" <dhb### [at] cherokeetelnet> wrote:
> >   What makes it ironic is that a large part of C/C++ programs out there are
> > heavily graphical (most prominently the computer games), and seems like
> > everybody just somehow manages to get the graphics done, but when you ask
> > for a simple way of getting graphics, they will usually shrug and say that
> > it's a bit complicated... (Because it *is* a bit complicated.)
>
> If it is (and I can't believe that it's all that complicated), why is it??

Legacy.

While nowadays computers would most likely have the memory to provide a frame
buffer for each application to write to, and display that as necessary - with
plenty of support from the graphics card - that wasn't the case when Windows
was initially designed. (It even wasn't the case when Windows 3.1 came out.)

Back then, in order to be able to display multiple overlapping application
windows at once, the solution was that whenever the position of a window would
change, all windows previously hidden below it but now visible would have to be
(partially) re-drawn by their respective applications.

This made it impossible to apply the classic straightforward approach of drawing
the window contents when the application saw fit. Applications to still use this
approach would now have to allocate a buffer to draw in, and upon request of the
operating system blit the appropriate portions of the buffer to the screen.

Things were made even more complicated by the application no longer having
control over which video mode to choose: That would be fixed for all
applications. And it could be anything, from 2-color 720x348 over 16-color
640x480 to, say, 256-color 1024x768 (which was an awful lot back then).
Limited, by the way, to a standard palette.

How was such data to be handled when an application would use its own buffer and
just copy portions of it? Memory and computing power were still valuable, and
all those potential candidates for a standard format were rather arbitrary
anyway (with non-palette-based 15/16-bit High- and 24-bit TrueColor modes
nowhere to be seen yet), so from that point of view it seems logical to me to
go for what would be known as "device-dependent bitmaps".

Some of those modes a user might have chosen posed another problem, in that they
were highly anisotropic (e.g. that 720x348 Hercules format intended to be
displayed on a 4:3 monitor), so it was also logical to compute the dimensions
and positions of applications' dialog elements not based on pixels, but some
other - necessarily somewhat arbitrary - distance unit.

I'm not sure if I get all these details right, but stuff along these lines added
up to make the Windows graphics interface somewhat complicated to use, and the
bane of backward compatibility has kept these complications alive to this day.


> Maybe, but why are the graphics for Windows itself so complicated? It's a
> single operating system running mainly on similar hardware.

Oh, if only it had always been that simple!

Yes, nowadays everyone is using 24-bit RGB graphics, and typically at 1:1 pixel
aspect ratio, but that's something PC-users could only dream of in the early
days of Windows.


> And again Pov-Ray does
> it for images at lease. How does it do it? So it's complicated, even
> *hard*, surely
> someone knows how it is done and can tell me, or tell me where to find
> out.

Let's see if we can shed *some* light on this, at least to the point where you
can start asking "stupid" questions about it; i'm not really familiar with
using the Windows API though, and typically relying on Microsoft frameworks
(MFC or the .NOT framework [duh, what a typo! I guess it deserves to be left in
here just for laughs :P]), but maybe... ah, we'll see.

I'm taking the POV-Ray 3.7 (beta 33) source for reference, because I have that
right at my fingertips; the file to look at there is "pvdisplay.cpp" in the
"windows" subdirectory, which defines a WinLegacyDisplay object (well, an
object class, to be precisely).

  WinLegacyDisplay::WinLegacyDisplay(...)
  WinLegacyDisplay::~WinLegacyDisplay()

These are a constructor and destructor, respectively, which don't do anything
exciting. Consider it some of the C++ overhead for object classes.

  bool WinLegacyDisplay::TakeOver(...)

This is just there to handle a special case where a window can be re-used from
one render to the next, so I leave that to you for later study.

  bool WinLegacyDisplay::CreateRenderWindow (void)

This is meat; I'll quote some of it:

  rect.right = GetWidth() ;
  rect.bottom = GetHeight() ;
  flags = WS_OVERLAPPEDWINDOW ;
  AdjustWindowRectEx (&rect, flags, false, 0) ;

This picks the desired "net" size (insert anything you like for GetWidth() and
GetHeight() respectively), chooses some window style (whether it has a title
bar, which title bar buttons can be seen, how thick the border is, etc.) and
has Windows calculate the "gross" size that would be required for such a
window.

What follows are computations to make sure the window is not larger than the
total desktop, and to decide where to place the window.

The m_Bitmap stuff sets up the rules for the drawing space to be used; you'll
want to pretend m_Depth8Bit=false. m_Bitmap.biBitCount = 24 will give you the
3x8 bits RGB you want. The m_Bitmap.m_BytesPerLine formula is designed to round
up the number of bytes required per line to a multiple of 4; I'm not sure
whether Windows actually needs that, so just keep it.

m_BitmapSurface is then set to point to newly allocated memory (in pure C, you'd
use malloc() here), and will serve as the actual drawing space, with bytes laid
out as RGBRGBRGB... per line; don't forget that the lines are padded to
multiples of 4.

Clear() just fills the drawing space with that grey-and-white pattern each
render starts with.

CreateWindowEx (...) is one of the uglier beasts. This actually requests Windows
to create a new window with the following properties:

- 0: matches parameter #4 in AdjustWindowRectEx
- PovLegacyRenderWinClass: a value allowing Windows to identify where to send
"events" pertaining to the window (e.g. user interaction, or requests to redraw
itself); we'll have to come back to this later.
- "POV-Ray"; the title text; choose anything you like.
- flags | renderwin_flags: matches parameter #2 in AdjustWindowRectEx, plus
whether the window is initially minimized, maximized or neither.
- renderwin_left, renderwin_top: position of the window
- width, height: "gross" size of the window
- RenderwinIsChild ? main_window : NULL: If you just want to pop up a window
from a command line tool, set this to NULL.
- NULL: this is for defining a menu bar; set this to NULL for simplicity.
- hInstance: You can leave this to NULL, at the cost of Win95/98/ME
compatibility.
- NULL: Looks like this last parameter is not needed either.

The result value will be NULL in case of failure, or a code uniquely identifying
"your" window.

SetWindowLongPtr (m_Handle, 0, LONG_PTR (this)); stores a reference to the
WinLegacyDisplay object; if you're not using C++, skip this one.

What follows looks like code to insert scroll bars if necessary, in some cases
give focus to the new window, and to request a non-standard cursor to be used
when over the window (the crosshairs); I guess you don't need these for
starters.

InvalidateRect (m_Handle, NULL, false) ; informs Windows that the whole window
needs to be redrawn (we haven't displayed the contents of our drawing area in
the window yet); Windows will subsequently request us to actually redraw the
window (or portions thereof) as soon as it gets visible.

  void WinLegacyDisplay::Initialise()

This seems to be some POV-Ray internal thing, and I guess you don't need any of
this.

  void WinLegacyDisplay::Close()

This rolls back all the stuff from WinLegacyDisplay::CreateRenderWindow.

SendMessage (main_window, WM_COMMAND, CM_RENDERDESTROY, (LPARAM) m_Handle); is
some POV-Ray internal stuff again, ultimately leading to a call to
"DestroyWindow ((HWND) lParam)"; you want to substitute "DestroyWindow
(m_Handle)" here, which will discard the window.

m_BitmapSurface memory is released (in pure C you'd use free() instead of
delete)

Not sure what the m_ErrorBitmap is about. I guess some "can't display this, it's
too large" fallback image.

Never mind the PVEnableMenuItem stuff.

  void WinLegacyDisplay::Show()
  void WinLegacyDisplay::Hide()

These demonstrate how to show or hide a window without really killing it. The
meat is in the "ShowWindow (m_Handle, ...) ;", never mind the rest.

  inline void WinLegacyDisplay::SetPixel (...)

This is meat that demonstrates how to actually use the drawing area. Note
however that it doesn't actually draw to the window, it just draws to the
buffer. It seems that for performance reason Windows is not informed that part
of the image is now in need of being redrawn; that's done somewhere else.

  void WinLegacyDisplay::DrawPixel(...)
  void WinLegacyDisplay::DrawRectangleFrame(...)
  void WinLegacyDisplay::DrawFilledRectangle(...)
  void WinLegacyDisplay::DrawPixelBlock(...)

These are some helper functions; not any particular meat in there (there is some
in DrawPixelBlock(), but that's for some specialty here).

  void WinLegacyDisplay::InvalidatePixelBlock(...)

This code is called whenever a significant portion of our drawing area has been
updated, and tells Windows via InvalidateRect (m_Handle, &rect, false) ; that
the particular region needs re-drawing; the code before that deals with
converting picture coordinates to the coordinate system Windows expects here -
which may be a bit complicated if the window is maximized (IsZoomed
(m_Handle)), but that's application choice; for starters you may want to always
display your drawings at 100% size.

  void WinLegacyDisplay::Clear()

This just draws the already mentioned grey-white checkerboard pattern to our
drawing area.

  void WinLegacyDisplay::SetRenderState(bool IsRendering)

This just picks a different cursor, depending on whether POV-Ray is currently
rendering or has finished.

  LRESULT WinLegacyDisplay::WindowProc (...)
  LRESULT CALLBACK WinLegacyDisplay::StaticWindowProc (...)

Pure Meat! This is called by Windows when something special happens. We've yet
to see how Windows knows that this is the thing to call.

You definitely want to implement some reactions for the cases WM_CLOSE (which is
passed to this function when the user requests to close the window) and WM_PAINT
(which is called when Windows requests to re-draw portions of the window; this
is where we actually make the drawing area visible. You should also hndle
WM_DESTROY, as it will tell you when for some reason the window is destroyed.

return (DefWindowProc (m_Handle, message, wParam, lParam)) ; should be called
for all other events, telling Windows that we don't consider it any of our
bloody business.

In a pure C environment, you can combine the two functions into a single one;
WinLegacyDisplay::StaticWindowProc() is just there to call the other one.

  HPALETTE WinLegacyDisplay::CreatePalette (...)

This seems to be irrelevant for 24-bit RGB operation.


The pvdisplay.cpp leaves us with a few loose ends; if I'm not mistaken, the
following code from the register_classes function in pvengine.cpp should tie
them up:

  WNDCLASSEX  wc ;
  ...
  wc.cbSize        = sizeof (wc) ;
  wc.hIconSm       = NULL ;
  wc.cbClsExtra    = 0 ;
  wc.cbWndExtra    = 0 ;
  wc.hInstance     = hInstance ;
  wc.lpszMenuName  = NULL ;
  wc.hbrBackground = NULL ;
  ...
  wc.style         = CS_BYTEALIGNCLIENT ;
  wc.lpfnWndProc   = WinLegacyDisplay::StaticWindowProc ;
  wc.hIcon         = renderIcon ;
  wc.hCursor       = LoadCursor (NULL, IDC_CROSS) ;
  wc.hbrBackground = NULL ;
  wc.lpszClassName = PovLegacyRenderWinClass ;
  wc.cbWndExtra    = 8 ;
  if (RegisterClassEx (&wc) == false)
    return (false) ;

This must be executed prior to the stuff in pvdisplay.cpp.

We haven't tackled renderIcon yet, but you can leave that to NULL for starters.

I'm not sure whether you can leave hInstance set to NULL as well, but I guess
not; we'll most likely need yet another function defined in pvengine.cpp, which
is actually the official entry point for a full-fledged Windows application
(instead of main()):

  int PASCAL WinMain (...)

There's a lot in there though that we'll not need, and I guess I'll leave this
up to later to sift through this. Suffice it to say here that the only stuff I
expect to be of relevance is in the while (!exit_loop) very near the end - you
need to run this after you have set up everything as outlined above; it's
basically the code that "does everything", including an occasional check for
inbound events (the while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) loop).

For comparison, here's some sample code from the Windows documentation from
Visual Studio:

------------------
HINSTANCE hinst;
HWND hwndMain;

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    MSG msg;
    BOOL bRet;
    WNDCLASS wc;
    UNREFERENCED_PARAMETER(lpszCmdLine);

    // Register the window class for the main window.

    if (!hPrevInstance)
    {
        wc.style = 0;
        wc.lpfnWndProc = (WNDPROC) WndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon((HINSTANCE) NULL,
            IDI_APPLICATION);
        wc.hCursor = LoadCursor((HINSTANCE) NULL,
            IDC_ARROW);
        wc.hbrBackground = GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName =  "MainMenu";
        wc.lpszClassName = "MainWndClass";

        if (!RegisterClass(&wc))
            return FALSE;
    }

    hinst = hInstance;  // save instance handle

    // Create the main window.

    hwndMain = CreateWindow("MainWndClass", "Sample",
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL,
        (HMENU) NULL, hinst, (LPVOID) NULL);

    // If the main window cannot be created, terminate
    // the application.

    if (!hwndMain)
        return FALSE;

    // Show the window and paint its contents.

    ShowWindow(hwndMain, nCmdShow);
    UpdateWindow(hwndMain);

    // Start the message loop.

    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    {
        if (bRet == -1)
        {
            // handle the error and possibly exit
        }
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    // Return the exit code to the system.

    return msg.wParam;
}
------------------


Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.