Bug in V3d_View background image

Hello,

I use V3d_View::SetBackgroundImage method to implement gradient background in 3D viewer, and found 100% - reproducable bug here.
First, let's slightly modify Viewer3D sample. In the file Viewer3dView.cpp add line

myView->SetBackgroundImage("Background.bmp", Aspect_FM_STRETCH, true);

to the CViewer3dView::OnInitialUpdate() method, it will be the last line in this method.
Then, you need to create file Background.bmp. It can be any picture, I use 1 pixel width, 256 pixel height gradient fill.

How to reproduce bug:
- Launch modified Viewer3D sample, you will see 3D viewer with your picture as background.
- Call File/Close command from menu, viewer will be closed. Without this step bug will not be reproduced.
- Call File/New command.
- Once more call File/New command. The second viewer will be opened over the first one.
- Call File/Close command. The second viewer will be closed, the first one will be shown, but already without background picture!

If you create some shape in remaining viewer, its behaviour will be quite interesting: the background will be changed from white to black and back to the white depending on selection state of the shape in viewer.

I hope this bug will be fixed soon or later, but before I need some workaround.Can anybody suggest it?

Thanks in advance,
Victor

Pawel's picture

Hi Victor,

I can reproduce the behavior you described. This seems to be a bug indeed...

If you want to take control over the background rendering yourself you could take a look at:

http://www.opencascade.org/org/forum/thread_11428/

Pawel

P Dolbey's picture

And I can reproduce it too. It looks very much like the OpenCascade code that removes views is deleting the background texture before it has established the correct OpenGL rendering context for the view and the scenario you found discovers this flaw.

I have found a solution that seems to work. I've altered the "call_subr_close_ws(..)" function in OpenGL\OpenGl_subrvis.c to read

void
call_subr_close_ws( CALL_DEF_VIEW * aview )
{
CMN_KEY_DATA key;

// PCD 28/06/07 ------ Starts
#ifdef OCC1188
tsm_bg_texture texture;
#endif
TsmGetWSAttri( aview->WsId, WSWindow, &key );
TxglWinset( call_thedisplay, (Window) key.ldata );
// PCD 26/06/07 ------ Ends

#ifdef OCC1188
// tsm_bg_texture texture; PCD 26/06/07

TsmGetWSAttri( aview->WsId, WSBgTexture, &key );
texture = key.pdata;

if ( texture->texId != 0 )
glDeleteTextures( 1,(GLuint *)&(texture->texId) );
#endif

TsmGetWSAttri( aview->WsId, WSWindow, &key );
#ifdef RIC120302
if( !aview->GContext )
#endif
TxglDestroyWindow( call_thedisplay, (Window) key.ldata );

TglDeleteFiltersForWS( aview->WsId );
RemoveWksLight( aview->WsId );
call_triedron_erase (aview); /* BGN 09-12-98 */
TelDeleteViewsForWS( aview->WsId );
TelDeleteHighlightsForWS( aview->WsId );
TelDeleteDepthCuesForWS( aview->WsId );

TsmUnregisterWsid( aview->WsId );
return;
}

and it seems to fix the problem - my edits are marked with PCD. Changing this file requires a recompilation of the TKOpenGl library.

As usual, I have no documentation of the inner working of OCC but the TxglWinset function appears to be a platform independent was of setting the current context to the OCC view. However I am not able to prove this on non-Wintel platforms.

Another one for the bug hunters

Cheers

Pete

vmarkin's picture

Pete and Pawel,
thank you very much!

I use your solution from another discussion thread and it works well.

Pawel's picture

Hi Pete,

the patch seems to work fine.

Cheers
Pawel

P Dolbey's picture

Pawel, I'll add it into my list of hacks, currently referenced on

http://www.opencascade.org/org/forum/thread_11478/

Pete

vmarkin's picture

Hi Pete and Pawel!

Let me add something to think about.
It seems that solution with callback which was discussed in http://www.opencascade.org/org/forum/thread_11428/
works only in neutral point context. In opened local context the preselection (detection highlighting) does not work if such callback is defined.
Now I've switched to solution with patched version of TKOpenGL and it works well, but solution which has no need to install patched version of any OCC DLL would be much more better for my application.

P Dolbey's picture

Hi Victor,

I think you've hit on an issue which has been close to my heart recently. Should we edit the core code, and how sows this affect out ability to absorb future ugrades. My response to this was mostly driven by the impact discussed in http://www.opencascade.org/org/forum/thread_11016/. Basically this describes an issue, and a simple solution to a major issues with OCC leaking GDI handles on Windows. Unless you put in the three simple Deletes, your OCC application will die after a few hundred repaints. Having chosen to apply this, and the flicker improvements in http://www.opencascade.org/org/forum/thread_8291/ my basic premise to change the code to suit myself, publish any changes for the good of the community and hope that the OCC team monitor these threads - which to be fair in the case of the GDI handles they did raise it as an official bug. However in my opinion you MUST make this change at least, especially if you are delivering applications to your own customers.

I have published a file containing my changes (Pawel has pointed out that it would be nice to have them documented - now that's just like being at work ). One of the changes I made was to move the position of the callback to move it higher in the rendering loop, so that it happens after the main layer but before the overlayer. This removed any of the need for blending the ColorScale and although I haven't tested it but would probaly fix the local contexts as well - Pawel's tested it with his graphics card and it does seem to work.

In a perfect C++ world, I should be able to make changes via inheritance. But not for the C code. However, if you want to make changes to the TKOpenGl lib, you can always it deploy a renamed version side-by-side with the orginal and select it via environment variables. Alternatively you may need to make the descision to deploy your own set of DLLs with your application.

Other things that irritated me.
- why don't the crosshairs of points appear where they should.
- why does the z-buffer trihedron not appear where I tell it.
- and why does it keep changing colour when I select solmething.
- why is the y-coodinate of a point-in-plane one pixel out.

The beauty of open source is that you can make these type of changes - and that there is a world community of developers and testers to support you. However in the case of OCC, there are now no formal ways for the open source community to have their changes validated back into the core product. Having looked and some of the NavisWorks mods described in http://www.opencascade.org/org/forum/thread_11478/ I found many of them were 5.1 issues that have already been solved in 6.2.

I could write a very long essay on this topic (you might think I aleady have) but OpenCascade is a commercial organisation and I suspect that the only way to really influence them is to become a paying customer with contrcated support. Otherwise, and especially if your developing commercially, you have to make the best choice you can for your own applications and customers.

My own involvement is purely as a hobbiest - I've actually started to enjoy taking the code apart. Of which I may publish one or two further changes soon (i.e. last bullet point above).

Cheers

Pete

P Dolbey's picture

...found many of them were 5.1 issues ...
should have read 6.1

Pete

Pawel's picture

Hi Victor,

I can't confirm this behavior on my machine. Opening local context with the callback active works fine.

Can you give some details, code or example?

Pawel

P Dolbey's picture

Pawel,

I suspect that Victor has applied the patch I gave earlier in this thread, and I also suspect you still have the patch we developed for OpenGl_togl_redraw.c that alter the main rendering loop. Without this I found that that the callback messed up grid echo, as well as forcing you to blend the colorscale. For the benefit of others, this is a listing of the patches to OpenGl_togl_redraw.c. Basically it moved the code defined with the RIC120302 definition to earlier in the rendering path, before any immediate mode drawing takes place. Is suspect as usual that all the indentations will be removed when its posted!

Pete

void EXPORT
call_togl_redraw
(
CALL_DEF_VIEW * aview,
CALL_DEF_LAYER * anunderlayer,
CALL_DEF_LAYER * anoverlayer
)
{
CMN_KEY_DATA data;
Tint swap = 1; /* swap buffers ? yes */

if ( TsmGetWSAttri (aview->WsId, WSWindow, &data) != TSuccess ) return;
#ifdef G004
if ( !aview->DefBitmap.bitmap ) { /* redrawing view to the window */
#endif
if (TxglWinset (call_thedisplay, (Window) data.ldata) == TSuccess) {
call_func_redraw_all_structs_begin (aview->WsId);
if (anunderlayer->ptrLayer)
call_togl_redraw_layer2d (aview, anunderlayer);
call_func_redraw_all_structs_proc (aview->WsId);
#ifdef RIC120302
call_subr_displayCB(aview,OCC_REDRAW_WINDOW);
#endif
if (anoverlayer->ptrLayer)
call_togl_redraw_layer2d (aview, anoverlayer);
//#ifdef RIC120302
// call_subr_displayCB(aview,OCC_REDRAW_WINDOW);
//#endif
call_func_redraw_all_structs_end (aview->WsId, swap);
call_togl_redraw_immediat_mode (aview);
}
#ifdef G004
} else {
CMN_KEY_DATA pixdata;
GLenum errorcode = 0;
#ifndef WNT
int n,sdesc[11];
XVisualInfo* XVInfo = NULL;
XWindowAttributes wattr;
GLXPixmap theGLXPixmap = 0;
GLXContext theContext = NULL;

XGetWindowAttributes ( call_thedisplay, (Window)data.ldata , &wattr );
#ifdef IMP100701
if( aview->DefBitmap.depth > 0 )
wattr.depth = aview->DefBitmap.depth;
#endif
n = 0;
sdesc[n] = GLX_RGBA; n++;
sdesc[n] = GLX_DEPTH_SIZE; n++;
#ifdef IMP100701
sdesc[n] = wattr.depth; n++;
#else
sdesc[n] = 1; n++;
#endif
sdesc[n] = GLX_RED_SIZE; n++;
sdesc[n] = ( wattr.depth <= 8 ) ? 0 : 1; n++;
sdesc[n] = GLX_GREEN_SIZE; n++;
sdesc[n] = ( wattr.depth <= 8 ) ? 0 : 1; n++;
sdesc[n] = GLX_BLUE_SIZE; n++;
sdesc[n] = ( wattr.depth <= 8 ) ? 0 : 1; n++;
#ifdef BUG /* Redraw always in single buffer mode and don't swap ! */
char string[CALL_DEF_STRING_LENGTH];
if ( !call_util_osd_getenv ("CALL_OPENGL_NO_DBF", string, CALL_DEF_STRING_LENGTH) )
{ sdesc[n] = GLX_DOUBLEBUFFER; n++; }
#endif
sdesc[n] = None; n++;

XVInfo = glXChooseVisual ( call_thedisplay, DefaultScreen(call_thedisplay), sdesc );
if ( !XVInfo ) {
fprintf ( stderr, "Visual not available\n" );
return;
}

theContext = glXCreateContext ( call_thedisplay, XVInfo, NULL, GL_FALSE );

theGLXPixmap = glXCreateGLXPixmap ( call_thedisplay, XVInfo, aview->DefBitmap.bitmap );

if ( ! glXMakeCurrent (call_thedisplay, theGLXPixmap, theContext) )
{
errorcode = glGetError ();
fprintf ( stderr, "glXMakeCurrent failed: %d %s\n", errorcode, gluErrorString(errorcode) );
return;
}
#else /* WindowsXX code here */
HGLRC hglrc_old = wglGetCurrentContext ();
HDC hdc_old = wglGetCurrentDC ();
HDC hdc = (HDC) aview->DefBitmap.bitmap;
HGLRC hglrc = wglCreateContext (hdc);

if ( !hglrc || !wglMakeCurrent (hdc, hglrc) )
{
errorcode = glGetError ();
printf ( "wglMakeCurrent failed: %d %s\n", errorcode, gluErrorString(errorcode) );
return;
}
#endif
pixdata.ldata = aview->DefBitmap.width;
if ( TsmSetWSAttri (aview->WsId, WSWidth, &pixdata) != TSuccess ) return;
pixdata.ldata = aview->DefBitmap.height;
if ( TsmSetWSAttri (aview->WsId, WSHeight, &pixdata) != TSuccess ) return;

/* generate new display lists */
TsmInitAttributes();

glLightModeli((GLenum)GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

glMatrixMode ( GL_MODELVIEW );
glViewport ( 0, 0, aview->DefBitmap.width, aview->DefBitmap.height );

glDrawBuffer ( GL_FRONT );

/* redrawing ... */
g_fBitmap = GL_TRUE;
call_func_redraw_all_structs_begin (aview->WsId);
if (anunderlayer->ptrLayer)
call_togl_redraw_layer2d (aview, anunderlayer);
call_func_redraw_all_structs_proc (aview->WsId);
#ifdef RIC120302
call_subr_displayCB(aview,OCC_REDRAW_BITMAP);
#endif
if (anoverlayer->ptrLayer)
call_togl_redraw_layer2d (aview, anoverlayer);
//#ifdef RIC120302
// call_subr_displayCB(aview,OCC_REDRAW_BITMAP);
//#endif
call_func_redraw_all_structs_end (aview->WsId, 0);

call_togl_redraw_immediat_mode (aview);
g_fBitmap = GL_FALSE;

glFinish();

/* cleaning up ... */
#ifndef WNT
glXMakeCurrent ( call_thedisplay, None, NULL );
glXDestroyContext ( call_thedisplay, theContext );
glXDestroyGLXPixmap ( call_thedisplay, theGLXPixmap );
#else
wglMakeCurrent ( hdc_old, hglrc_old );
wglDeleteContext ( hglrc );
#endif
}
#endif /*G004*/

return;
}

void EXPORT
call_togl_redraw_area
(
CALL_DEF_VIEW * aview,
CALL_DEF_LAYER * anunderlayer,
CALL_DEF_LAYER * anoverlayer,
int x, int y, int width, int height
)
{
CMN_KEY_DATA data;

/*
When the exposure area size is > window size / 2 do a full redraw.
*/
if( width*height >
(int)(aview->DefWindow.dx*aview->DefWindow.dy)/2 ) {
call_togl_redraw(aview,anunderlayer,anoverlayer);
return;
}
/*
Or redraw only the area in the front buffer
*/
TsmGetWSAttri (aview->WsId, WSWindow, &data);
if (TxglWinset (call_thedisplay, (Window) data.ldata) == TSuccess) {
GLint buffer;
glGetIntegerv(GL_DRAW_BUFFER,&buffer);
if( buffer != GL_FRONT ) glDrawBuffer (GL_FRONT);
glEnable( GL_SCISSOR_TEST );
glScissor( (GLint)x,
(GLint)((int)aview->DefWindow.dy - (y+height)),
(GLsizei)width, (GLsizei)height);
call_func_redraw_all_structs_begin (aview->WsId);
if (anunderlayer->ptrLayer)
call_togl_redraw_layer2d (aview, anunderlayer);
call_func_redraw_all_structs_proc (aview->WsId);
#ifdef RIC120302
call_subr_displayCB(aview,OCC_REDRAW_WINDOWAREA);
#endif
if (anoverlayer->ptrLayer)
call_togl_redraw_layer2d (aview, anoverlayer);
//#ifdef RIC120302
// call_subr_displayCB(aview,OCC_REDRAW_WINDOWAREA);
//#endif
call_func_redraw_all_structs_end (aview->WsId, 0);
call_togl_redraw_immediat_mode (aview);
glFlush();
glDisable( GL_SCISSOR_TEST );
if( buffer != GL_FRONT ) glDrawBuffer (buffer);
}
}

vmarkin's picture

Hi Pete and Pawel,

after only two monthes ;-) of using your solution I found some incorrectness. Unfortunately I'm not specialist in OpenGl at all, so my attempts to fix it had no success.
The problem is that when we try to switch OpenGL context during call_subr_close_ws, the call to wglMakeCurrent fails and returns error 1282 "Invalid operation".
May be it is not very serious, but to be absolutely correct, it would be nice to fix it.

Best regards,
Victor

P Dolbey's picture

Can you post me a sample application that reproduces the problem (peter at dolbey dot freeserve dot co dot uk).

Pete

P Dolbey's picture

Viktor,

I saw your email via web interface. I think you've trying to call Remove() in the class destructor, but this is called after the window has been already destroyed. I think you should try overriding the window's OnDestroy() function instead. For the viewer3d demo you should call your Remove() function here then call the base class window OnDestroy( e.g. CView::OnDestroy() ) - and remove the code from the class destructor. I'll try to reproduce problem and mail back the changes tonight.

Pete

vmarkin's picture

Hi Pete!

It was the first I've tried. But without success.
And yes, in any case, you right - it has no sence to do something with window after the window has been destroyed.

Best regards,
Victor

P Dolbey's picture

Hi Viktor,

I had a quick look at your problem last night. First I had to re-write the "console" code to work with the version of STL used with VS2005, and thats now working. I tried applying my suggestion for OnDestroy, but the documentation indicates this is called after the window is destroyed. I then tried over-ridding DestroyWindow with no sucess.

When I stuck a breakpoint on the new code I added to the TKOpenGL lib I discovered from the call stack that the wglMakeCurrent is actually being called not from the destruction of the view, but the destruction of the document as it iterates the document's attached windows - actually in the OnClose event of the CFrameWnd class. Oh how I hate MDI apps. I'll take another look tonight to see if there's a logical fix, but I'm trying to build an OpenSUSE virtual machine for debugging OCC/Qt4 at the same time.

Pete