jdaniel7's picture

DrawToBitmap() method for OpenTK.GLControl returns a blank image

Project:The Open Toolkit library
Version:all versions
Component:Code
Category:support request
Priority:normal
Assigned:Unassigned
Status:closed
Description

I am using the GLControl in an application that needs to support printing. The third party printing tool I am using uses the DrawToBitmap() method on all the controls in the application and displays a Print Preview window. The GLControl comes up blank for its image in the Preview window. I have manually tried to call the DrawToBitmap() method on the control and all I get is a blank image. Is there a setting I am missing?

Thanks,

Jason


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

#1

You cannot use GDI/GDI+ methods, like DrawToBitmap(), to interact with OpenGL. This is a limitation of the operating system.

The solution is to use GL.ReadPixels() to make a copy the OpenGL framebuffer when necessary.

jdaniel7's picture

#2

When I use SharpGL control it works fine. However, the rendering speed is slow and I have my own camera I control with the mouse - SharpGL has its own camera with mouse controls that I do not like. Are you sure there is no way to do this with OpenTK? The third party print control has to use DrawToBitmap().

Thanks.

the Fiddler's picture

#3

SharpGL may be falling back to software acceleration, which would explain both the slowness and the fact that DrawToBitmap() works. To the best of my knowledge, it really is impossible to make hardware-accelerated OpenGL work with GDI/GDI+.

OpenTK does not support software-accelerated modes by design. It might be possible to modify WinGraphicsMode.cs to disable hardware acceleration but this is an unsupported (and untested) configuration.

jdaniel7's picture

#4

I have located the WinGraphicsMode.cs class but I am not sure what to modify in order to disable harware acceleration and force software acceleration. Can you point me in the right direction?

Thanks for all the help.

the Fiddler's picture

#5

According to http://www.opengl.org/resources/faq/technical/mswindows.htm:

Quote:

To force software rendering from your application, choose a pixel format that is not hardware accelerated. To do this, you can not use ChoosePixelFormat(), which always selects a hardware accelerated pixel format when one is available. Instead, use DescribePixelFormat() to iterate through the list of available pixel formats. Any format with the PFD_GENERIC_FORMAT attribute bit set will not be hardware accelerated.

You will need to download opentk-trunk (instructions) and make two modifications:

  1. Comment out GetModesARB (line 55 of the constructor)
  2. Modify GetModesPFD, line 148, to consider only software-accelerated formats:
                    // Ignore accelerated formats.
                    if ((pfd.Flags & PixelFormatDescriptorFlags.GENERIC_FORMAT) == 0)
                        continue;

    In other words, simply toggle the existing conditional.

Compile, and add the new OpenTK.dll to your project references (make sure you remove the existing reference first, otherwise Visual Studio will ignore the new one).

jdaniel7's picture

#6

Found it. I will do some testing. Thanks.

jdaniel7's picture

#7

I made the changes as suggested. The fps dropped from 60+ fps to 8 fps but still no image from DrawToBitmap(). Anything else you can think of? Thanks again for all the help.

the Fiddler's picture

#8

Sorry, I have no idea why this doesn't work. Reading the documentation of DrawToBitmap, it seems that this method is quite limited. The fact that it fails to print RichTextControl suggests that it only supports pure GDI+ controls, not GDI ones (like software-accelerated OpenGL) and certainly not hardware-accelerated OpenGL.

Another idea: given your current architecture, is it possible to run a custom piece of code *just before* executing the 3rd party printing tool? In that case, you might be able to swap a "fake" UserControl in place of the actual GLControl, which should then print correctly.

The following piece of code constructs System.Drawing.Bitmap from the contents of a GLControl:

        public Bitmap GrabScreenshot()
        {
            Bitmap bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
            System.Drawing.Imaging.BitmapData data =
                bmp.LockBits(this.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly,
                             System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            GL.ReadPixels(0, 0, this.ClientSize.Width, this.ClientSize.Height, PixelFormat.Bgr, PixelType.UnsignedByte,
                          data.Scan0);
            bmp.UnlockBits(data);
            bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
            return bmp;
        }

At the last instant before printing, call this function, hide the GLControl, enable the fake control and display this Bitmap. Afterwards, hide the UserControl and re-enable the GLControl (this can be done in glControl.Paint, if necessary). Don't forget to Dispose() the bitmap (or a GDI+ leak will occur).

This approach is slightly hacky, but should allow you to keep hardware acceleration enabled and still print correctly.

Edit: of course, the hack would have been completely unnecessary if Control.DrawToBitmap was a virtual method. Unfortunately, it isn't...

jdaniel7's picture

#9

Great idea - that worked.

I was able to use your code to place an image in a PictureBox and overlay the OpenGL control. The code that shows the PrintPreview has a CreateDocument() method that captures the controls when called. I swap the OGL control with the PictureBox before the CreateDocument() call and then delete the PictureBox after the CreateDocument() call.

Thanks for all the help with this issue.

Jason

the Fiddler's picture

#10

Status:open» closed

Glad to hear it worked!