ObjReader Community
WIP => WIP => Topic started by: Patrice Terrier on March 16, 2018, 11:44:58 am
-
Mike
Ok, here are the latest changes i did to the project, altogether with the new rotate.png (to increase its visibility on all kind of background).
Does your sending me this mesh mean you don't want to experiment any further with your quasi alpha shadow's displacement relative to the model and would rather try true stencil shadowing solutions?
About the provided mesh it was just a first shot to see if this kind of decimation was enough to be used as a stencil shadow pass, obviously not ;)
For now, the quasi alpha shadow's displacement is all what we need, for compatibity with the existing models, and because we do not have {yet} any extra environment around the model.
-
DL'ed OK, thanks!
Should the quasi shadow get split when the model is lit by more than 1 light? If yes, then should the split sub-shadows get tinted depending on the color of the other lights the shadowed areas still see as unobscured by the model?
-
Should the quasi shadow get split when the model is lit by more than 1 light
The closer to real shadow, the better.
If we are outdoor, nothing could beat the sun brightness, but in a photo studio things could be different, i leave this to your appreciation...
About tinted shadow, i never thought of that 8)
-
Probably this picture will give you a better idea of what it means in real world to be lit simultaneously with three differently colored light sources that have different space positions but are all directed to the center of the model exactly like our directional lights are.
In fact, we have no directional light falling from above on top of our cars, so our quasi shadow isn't realistic. OTOH we could somehow determine which of the lights is currently the brightest and displace our singular quasi shadow somewhat in the opposite direction. Needs testing to see if this would look convincing, especially if we decide to auto rotate our lights in real time... :-\
(https://upload.wikimedia.org/wikipedia/commons/3/38/Female_in_red_pullover_and_blue_jeans_-coloured_lights-2Nov2003.jpg)
-
Thank you for the head's up
OTOH we could somehow determine which of the lights is currently the brightest and displace our singular quasi shadow somewhat in the opposite direction
Or perhaps always use the Front ambient (indeed the leading lighting), except if it is being turned off...
PS: I am working on a glossy Bugatti Veyron...
-
Or perhaps always use the Front ambient ...
NO!
The ambient component is an OpenGL/Direct3D fake to emulate real world light scatter due to multiple reflections from the nearby objects' surfaces. No real world surface is completely black to absorb all the light that falls on it, so the effect of multiple reflections and resultant scatter is pretty strong. Look out of your window on a winter night and see how bright it is even when it's moonless. The light you're seeing is actually multiple reflections of Grenòble's (?) street lights between the low cloudy skies and the snow that covers the ground.
The main visual effect of ambient lighting is that it seems to come from nowhere in particular but rather from everywhere all around you, and as such, it cannot cast a distinctly directional shadow. :)
-
Hi Patrice,
Below please find my patch to allow for arbitrary GL_CLAMP_TO_EDGE mipmapping. It pertains to ObjReader v2.5 though the code already uses some changes that are going to be introduced in v3.0 WIP.
The logic is as follows:- To clamp an arbitrary material's texture(s) to edge, use the keyword clamp immediately following map_Kd but before the texture name proper, e.g. map_Kd clamp tex00.png. Do not use the keyword with other texture types because it won't be recognized there. Yet when specified for map_Kd, it will automatically apply to the other textures in that material, if any.
- Clamped textures are mipmapped unless Mipmap textures is unchecked in the menu.
- Billboards are clamped on default and do not require explicit clamp. They are also mipmapped unless Mipmap textures is unchecked in the menu.
- Wallpapers are always clamped but never mipmapped, so do not use clamp for wallpapers at all.
Use your carousel model in both mipmapped and unmipmapped modes to see the difference clamping makes on the borders of textured quads.
-
Mike
Thank you for this patch, i shall merge it with what i have done already (worked also on clamp, but will gladly switch to what you have done).
I twisted my right foot, thus i can't stay for too long in front on my computer (have to keep my leg horizontaly) ...
I shall come back to you once merged.
-
Here is the merged patch with my own changes.
Thank you.
-
Thank you Patrice,
Everything works flawlessly for me here.
Why does the control panel just pop up when F1-ed but slide left out of view when closed with the panel button? Can it also slide into view from the left when F1-ed? I'd like it better that way. :)
If it can slide both in and out, then have you ever considered exponential sliding motion so that the panel would first start fast but would then decelerate exponentially as it moves to its finish position, either in to or out of view?
-
Mike
Try playing with this in ProcessMenu
case MENU_LIGHTSETTING: // PAT: 01-30-2018
if (IsWindow(gP.hOverlay)) {
long OverLayVisible = IsWindowVisible(gP.hOverlay);
GetWindowRect(gP.hGL, &lpr);
if (OverLayVisible) {
for (long w = OVERLAY_WIDTH; w >= 0; w-=1) {
MoveWindow(gP.hOverlay, lpr.left, lpr.top, w, Height(lpr), true);
}
ShowWindow(gP.hOverlay, SW_HIDE);
SetWindowPos(gP.hOverlay, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // PAT: 02-12-2018
} else {
//MoveWindow(gP.hOverlay, lpr.left, lpr.top, OVERLAY_WIDTH, Height(lpr), false);
ShowWindow(gP.hOverlay, SW_SHOW);
if (gP.nIsFullScreen) { SetWindowPos(gP.hOverlay, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } // PAT: 02-12-2018
for (long w = 0; w <= OVERLAY_WIDTH; w++) {
if (w % 20 == 0) {
MoveWindow(gP.hOverlay, lpr.left, lpr.top, w, Height(lpr), false);
WindowRedraw(gP.hOverlay);
gP.redraw = -1; gl_DrawScene();
}
}
MoveWindow(gP.hOverlay, lpr.left, lpr.top, OVERLAY_WIDTH, Height(lpr), true);
}
SetFocus(gP.hGL);
gP.redraw = -1; // Redraw the OpenGL scene
}
break;
-
Hehe Patrice,
Thank you! This is what I ended up with in v3.0 WIP:
case MENU_LIGHTSETTING: // PAT: 01-30-2018
if (IsWindow(gP.hOverlay)) {
GetWindowRect(gP.hGL, &lpr);
if (IsWindowVisible(gP.hOverlay)) {
for (long w = OVERLAY_WIDTH; w > 0; w--) {
MoveWindow(gP.hOverlay, lpr.left, lpr.top, w, Height(lpr), false);
}
SetWindowPos(gP.hOverlay, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_DEFERERASE); // PAT: 02-12-2018
} else {
ShowWindow(gP.hOverlay, SW_SHOWNOACTIVATE);
if (gP.nIsFullScreen) { SetWindowPos(gP.hOverlay, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_DEFERERASE); } // PAT: 02-12-2018
for (long w = 0; w <= OVERLAY_WIDTH; w++) {
if (w % 30 == 0) {
SetWindowPos(gP.hOverlay, 0, 0, 0, w, Height(lpr), SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE | SWP_DEFERERASE);
WindowRedraw(gP.hOverlay);
}
}
MoveWindow(gP.hOverlay, lpr.left, lpr.top, OVERLAY_WIDTH, Height(lpr), true);
}
SetFocus(gP.hGL);
}
break;
The code seems to run both ways with more or less equal speed, and it doesn't shift the focus off the main window so vividly. Of course it blocks model rotation for a split second while sliding in or out but that's not so annoying after all. Also, sometimes the tooltips appear behind the control panel buttons while in full screen mode, but this may be due to my monitors having several macOS-ish sliding dockbars where I store a number of my everyday tools. Those also fight for being at HWND_TOP from time to time so the occasional glitches may be attributed to them but anyway these are minor artifacts because fullscreen isn't used so very often IMO.
-
Mike
I did put your "ended up" into mine.
The go.hTip gives you the real handle of the tooltip window, you can use it to force its topmost order.
I am doing this already into the ToggleFullScreen subroutine into Main.cpp.
if (IsWindow(go.hTip)) { SetWindowPos(go.hTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } // PAT: 02-12-2018
-
Ah yes,
Adding your line like this did the trick of keeping the tooltip in front of the control panel buttons while in the fullscreen mode! :)
........
}
MoveWindow(gP.hOverlay, lpr.left, lpr.top, OVERLAY_WIDTH, Height(lpr), true);
if (IsWindow(go.hTip)) { SetWindowPos(go.hTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } // PAT: 02-12-2018
}
SetFocus(gP.hGL);
}
break;
-
Ok, i have updated the source code.
-
Patrice,
When in full screen, there are two glitches that I think should be corrected in v2.5.- There is still one wallpaper image line missing at the screen bottom. (see image 1 below) To fix this, goto ToggleFullScreen() in Main.cpp and add as follows:
........
// PAT: 02-12-2018
SetWindowLongPtr(gP.hGL, GWL_STYLE, WS_POPUP | WS_VISIBLE); // PAT: 02-12-2018
MoveWindow(gP.hGL, 0, 0, screenWidth, screenHeight + 1, FALSE); // PAT: 02-12-2018
SetWindowPos(gP.hOverlay, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // PAT: 02-12-2018
if (IsWindow(go.hTip)) { SetWindowPos(go.hTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } // PAT: 02-12-2018
........
- When trying to save a screenshot, centering the SaveFile dialog on the screen causes hGL displacement that can't be corrected other than by exiting the full screen mode. (see image 2 below) To fix this, goto gl_TimerView() in Main.cpp and add as follows:
void gl_TimerView() {
static DWORD AnimDelay, ReportDelay;
//DWORD AniCount = GetTickCount();
gP.AniTick = GetTickCount();
if (gP.openDialog) { // Center OpenDialog
HWND hWnd = GetForegroundWindow();
if ((hWnd != gP.hMain) && (hWnd != gP.hGL) && IsWindowVisible(hWnd)) {
RECT r; GetWindowRect(hWnd, &r);
........
-
Patrice,
Can your skinned controls like e.g. list views change their visible appearance depending on whether they are enabled or disabled?
-
There is still one wallpaper image line missing at the screen bottom.
This seems to occure only with some specific wallpapers.
For example, if you select a white background into Windows, and the Black.png for wallpaper in OR, then in fullscreen mode, you won't see any white line at the bottom, at least by me. Also try using a full HD wallpaper rather than 1024x512.
Can your skinned controls like e.g. list views change their visible appearance depending on whether they are enabled or disabled?
In that case, the best solution would be to paint a disabled window hover the control, this is what i am doing to simulate a popup modal dialog (to see the effect in action, just display the About dialog box, then you won't be able to click outside of the dialog).
-
This seems to occure only with some specific wallpapers.
For example, if you select a white background into Windows, and the Black.png for wallpaper in OR, then in fullscreen mode, you won't see any white line at the bottom, at least by me. Also try using a full HD wallpaper rather than 1024x512.
Please look at the image below. Anyway, why not just fix that glitch if we can? I'm not seeing any adverse effects of that extra pixel added to the height on my monitors. Do you see any on yours?
In that case, the best solution would be to paint a disabled window hover the control, this is what i am doing to simulate a popup modal dialog (to see the effect in action, just display the About dialog box, then you won't be able to click outside of the dialog).
Got it, thanks for the tip.
-
2.When trying to save a screenshot, centering the SaveFile dialog on the screen causes hGL displacement that can't be corrected other than by exiting the full screen mode. (see image 2 below) To fix this, goto gl_TimerView() in Main.cpp and add as follows:
I am unable to reproduce this behavior on W10.
Does this occures only when you are in full screen mode?
In full screen mode we should consider the size of the gP.hGL window rather than the size of gP.hMain.
Here is what to do:
void gl_TimerView() {
static DWORD AnimDelay, ReportDelay;
//DWORD AniCount = GetTickCount();
gP.AniTick = GetTickCount();
if (gP.openDialog) { // Center OpenDialog
HWND hWnd = GetForegroundWindow();
if ((hWnd != gP.hMain) && IsWindowVisible(hWnd)) {
RECT r; GetWindowRect(hWnd, &r);
long w = r.right - r.left;
long h = r.bottom - r.top;
if (gP.nIsFullScreen) { // PAT: 04-11-2018
GetWindowRect(gP.hGL, &r);
} else {
GetWindowRect(gP.hMain, &r);
}
r.right = (r.right - r.left - w) / 2;
r.bottom = (r.bottom - r.top - h) / 2;
long x = r.left + r.right;
long y = r.top + r.bottom;
MoveWindow(hWnd, x, y, w, h, TRUE);
gP.openDialog = 0;
}
}
To create a disabled window use this couple of API:
HWND hGFI = skCreateDW (hWndToDisable);
skDestroyDW (hGFI);
To learn more about it, read this
https://forum.powerbasic.com/forum/user-to-user-discussions/sdk-programming/59770-dim-the-disabled-windows
BTW, i have added the extra pixel for the full screen mode ;)
-
Patrice,
I am unable to reproduce this behavior on W10.
That's correct, the glitch isn't observed under Win 10 but it is seen Under Win 7 on both nVidia and ATi.
Does this occures only when you are in full screen mode?
Yes, only when in full screen.
Here is what to do:
Sorry but your fix doesn't help under Win 7: although the hGL window doesn't drift away any more but the SaveAs dialog stays off-center in the top left corner of the screen. My fix works under both OSes and doesn't seem to care whether the mode is windowed or full screen, as the full screen size is large enough to make the differences in hMain and hGL, and hence the dialog displacement error relative to the screen center, imperceptible.
Occam's razor, my friend; please follow my earlier suggestion.
To create a disabled window use this couple of API:
Thank you very much for those! The About dialog example didn't help because the actual APIs were hidden in the info box implementation, and re-writing the entire blob of PB code just for one tiny disabled window was too much pain in the back.
BTW, i have added the extra pixel for the full screen mode ;)
Excellent! :)
-
OK, i followed your advice, because i have no way to check it anymore on W7.
if (gP.openDialog) { // Center OpenDialog
HWND hWnd = GetForegroundWindow();
//if ((hWnd != gP.hMain) && IsWindowVisible(hWnd)) {
if ((hWnd != gP.hMain) && (hWnd != gP.hGL) && IsWindowVisible(hWnd)) { // MLL: 04-11-2018 (for W7 compatibility)
RECT r; GetWindowRect(hWnd, &r);
long w = r.right - r.left;
long h = r.bottom - r.top;
if (gP.nIsFullScreen) { // PAT: 04-11-2018
GetWindowRect(gP.hGL, &r);
} else {
GetWindowRect(gP.hMain, &r);
}
r.right = (r.right - r.left - w) / 2;
r.bottom = (r.bottom - r.top - h) / 2;
long x = r.left + r.right;
long y = r.top + r.bottom;
MoveWindow(hWnd, x, y, w, h, TRUE);
gP.openDialog = 0;
}
}
Note: On W10 because of the border width used by the Windows theme, in full screen mode the displacement error is much more visible.
Here is an example to disable the wallpaper listbox :
HWND hGFI = skCreateDW(GetDlgItem(gP.hMain, IDC_LISTBOX));
it must be done AFTER the skin theme has been applied to the main window
-
... i have no way to check it anymore on W7.
I have both 64-bit Win 7 and 10 on two different boxes with nVidia geForce video adapters. You can rely on my reports in this regard. :)
Note: On W10 because of the border width used by the Windows theme, in full screen mode the displacement error is much more visible.
OK OK, your name stays in the list of fixes. 8)
Here is an example to disable the wallpaper listbox :
How did you know it's going to be the wallpaper list box?? :o :D
-
How did you know it's going to be the wallpaper list box??
For the purpose of post-processing, we must provide specific materials, and of course avoid the changing of the wallpaper background, just like for my BassBox/MediaBox visual plugins :)
I am stamped with a few new models, but because of my foot i have to spend several hours in my bed, far away from my keyboard :'(
About the disabled window, we will probably need a specific one, to follow the moving of the main window, let me know when you need it, and i shall write it or perhaps make a change into WinLIFT (have to see the easiest one).
-
I'm sorry for your bad times ... How's your foot doing, my friend?
-
Going slowly better, i can now put again my right foot on the floor, thank you...
-
Mike--
Ok, i am finally asking about Jessica ;)
What FX did you put into FX1 to FX9, and the 3 horizontal sliders ?
...
-
Oh Patrice, you must be definitely getting better now that you've started to take interest in the opposite sex! :D
Those are just handlers I might need when experimenting with various post-processing effects and their parameters in real time in any combination. They have no particular bindings yet, hence no meaningful labels. I also hid the topmost mouse label for now to allow for more space on the panel and add yet more controls if needed. Finally, I readjusted the main window's min sizes to be able to see the CPU/GPU meters instantly whenever I open the lighting control panel, and to narrow the viewport horizontally for easier framing depending on the scene content.
Thus, ObjReader with the yellow orb is the experimental sandbox I intend to use for transitioning from the current direct screen buffer writes to the frame buffer object. When tested and proven functional in the sandbox, the individual PP effects may go into standard ObjReader where you'll decide where and how to implement the associated controls. :)
-
Mike
I plan to work back on ObjReader64, and i would like to add individual mesh rotation along the X, Y, Z axes.
First thing, i want to know if you have worked already on this in the FBO version ?
Indeed it is not related to post processing, but moreover an enhancement of the existing demo mode.
I am thinking also of a new global #animate (with speed parameter for CW or CWW rotation).
Your thought ?
-
I plan to work back on ObjReader64 ...
That's great news, Patrice! It indicates your foot's finally gotten much much better, n'est-ce pas? :)
... and i would like to add individual mesh rotation along the X, Y, Z axes.
........
I am thinking also of a new global #animate (with speed parameter for CW or CWW rotation).
Have a look at this article (https://www.techradar.com/news/acer-announces-nitro-vg0-and-rg0-gaming-monitors) svp. These mass-production low-cost monitors (and competitive notebooks too!) are designed to operate at refresh frequencies of 75 to 144Hz and don't have a 60Hz option at all. Note that ObjReader's current Windows timer-based refresh engine will not be able to deliver such speeds because its fastest theoretical rate is ca. 64FPS (ca. 15,6 msec) only. VSYNC=2 will work regardless as it doesn't care what the refresh rate actually is as long as the renderer is still able to catch up with half the monitor rate, but VSYNC=1 ("turbo") won't because of the timer's too slow pace.
We could use straight-forward timer-less continuous VSYNC'ed rendering but it would be considerably more stressful for the CPU/DWM than the current Windows timer implementation is. ???
We need another adjustable render clock, probably RDTSC-/high precision Sleep()-based one, which means we'll have to go down to the 64-bit inline assembly level. It is available in GCC and Intel C(++) but is missing entirely in 64-bit MS VC(++). ::)
First thing, i want to know if you have worked already on this in the FBO version ?
No, and now you know why. I imagined you defending your timer implementation and your beloved MS VC too, and felt kinda frustrated... :-\ :-[
-
Yes, my foot is going better, but causes extra troubles to my right leg (because of walk compensation).
See what Microsoft says about using TSC and/or QPC
https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx
Anyway i couldn't see why keeping using GetTickCount could cause any havoc for the purpose of animation, because on Windows 10, DWM always renders everything ultimatly onto the hidden DirectDraw surface, and that is the only way to work in composited mode. Remember, ObjReader64 has been designed to be a windowed cooperative application, and the use of a GDImage transparent window is something that alows us to mix easily 2D and 3D altogether.
I am slowly working on the REX FU's McLaren car, but i am getting lazy to complete the whole car, because of the huge amount of details.
-
See what Microsoft says about using TSC and/or QPC
Alas, this is irrelevant to the problem in question, Patrice.
First, we need a self-calibrating clock (in fact, a stopwatch, not just a QPC counter!) that should trigger system events regardless of CPU clocks, based on Mother Nature's absolute time intervals (not CPU clocks per second!) at a rate slightly higher than the monitor refresh rate (144Hz, or 144 events per second, at the "worst" case). Believe me, that's a non-trivial task at all under any of Windows OS'es which in fact aren't realtime systems. Continuous rendering based on InvalidateRect() in every WM_PAINT message handler is too crude for this task because WM_PAINT has higher "priority" than WM_TIMER, and it overloads the CPU message pump even if it doesn't actually paint anything due to WM_PAINT's own low priority relative to other window messages.
The slightly extra frequency rate of such a clock will then be normalized by OpenGL VSYNC down to the monitor's actual frequency (144Hz) to avoid image tear -- with minimum overload on the window message pump, pretty much like ObjReader's current Windows timer-based "heart pacemaker" works.
Second, we will have to somehow mimic WM_TIMER's behavior in the message pump where it has a "synthetic priority" lower than any other window message except user-interactive "hardware interrupted" messages such as mouse moves and mouse/keyboard button clicks or wheel rotations. Our own custom message will then trigger OpenGL canvas renders in its own handler exactly like the original WM_TIMER did but at a considerably higher yet stabilized "turbo" rate/speed/pace.
Do you understand the nature of the problem better now, my friend?
-
My friend
Many things have changed into Windows 10 compared to Seven, for the purpose of compositing.
Do you know the DwmGetCompositionTimingInfo.
https://msdn.microsoft.com/en-us/library/windows/desktop/aa969503(v=vs.85).aspx
Note: in case of multiple monitors, DWM uses of course the lowest refresh rate, with the exception of running in full screen mode.
On Windows 10, trying to bypass the DWM compositing refresh rate is a NoNo.
To work around the low priority of the WM_TIMER message, the solution is to use a distinct thread to perform the sync, but this also means probably to change the ObjReader SetProcessorAffinity.
-
Patrice,
I'm feeling somewhat nonplussed because we definitely seem to be speaking different languages. Thank you of course for the interesting info on Windows DWM but this isn't my point of discussion. Leave DWM alone to do its job: it will compose its frames as it sees fit accommodating them to the monitor's actual refresh rate (not vice versa!) that's independent of Windows own timers and performance counters. If the monitor refreshes occur at 144Hz rather than the current 60Hz, then DWM will adjust itself accordingly; that's what monitor drivers are for.
What I am talking about is as follows. As long as the monitor is running at 60Hz (my main monitor can switch between 60Hz and 75Hz), the Windows timer interval of 15.6 msec (= 64 Hz/FPS) is sufficient to clock our gP.hGL refreshes at 60FPS when VSYNC'ed to 1 ("turbo" mode). But when dealing with my 75Hz setting, ObjReader fails to turbo-VSYNC to 75FPS because the Windows timer events (WM_TIMER messages) are triggered too rarely and cannot occur any faster. As a result, the renderer rather continues to run un-VSYNC'ed at 64FPS as shown by the ObjReader FPS counter.
So, what we need is an analog to the Windows timer that can run at a pace of at least 144Hz + ca. 5Hz = ca. 150Hz to be able to VSYNC our redraws to the monitor actual refresh rate whatever it happens to be. And this analog should work as smooth and unstressful for the message pump as the current genuine Windows timer does. It will be a plus if that analog is also automatically switchable to run at the monitor exact refresh rate plus some 5 extra Hertz.
Do I sound clearer this time?
-
Do I sound clearer this time?
Yes, thank you :)
Then perhaps we could use the EnumDisplaySettings API to get the smallest refresh rate, and a slave thread running a game loop and sending a private message to mimic the WM_TIMER, based on the real VSYNC frequency ?
-
Fine! :)
Now Patrice, can you investigate the following tactics:- Disable setting process affinity (I don't understand what you're setting it now for, anyway? Are you measuring performances or profiling anything? Why would you stress your CPU so indiscriminately at all?)
- On pressing the Show FPS, or Y Rotation Mode, or Turbo buttons, create a separate worker thread if it isn't already running.
- Set up a loop in the worker thread with the following pseudo code:
while (1) {
SetMutexOrSemaphoreOrCriticalSection(); // avoid gP.doneRender access conflicts
if (gP.doneRender) {
gP.doneRender = FALSE;
// experiment with Post/Send/BroadcastMessage whichever would work across threads
PostMessage(gP.hMain, WM_TIMER, 0x1234, 0); // synthetic timer ID; use same in gP.hMain message loop
}
UnsetMutexOrSemaphoreOrCriticalSection();
timeBeginPeriod(1); // set wait resolution to 1 msec, else Sleep() will be too rough!
Sleep(12); // let loop run at ca. 83 FPS
timeEndPeriod(1); // restore default resolution
MaybeDoEventsHere(); // let gP.hMain message loop do its work if messages are pending
} - gP.doneRender is a global flag to indicate if the renderer actually redraws our gP.hGL. At the end of gl_DrawScene(), use Set/UnsetMutex...(hehe) and assign gP.doneRender = TRUE. This flag should prevent sending/posting/broadcasting excessive synthetic WM_TIMER messages that can clog the gP.hMain message loop if they remain unprocessed and accrue there for whatever reason. I think this can help bring down our synthetic WM_TIMER's "priority" to an acceptable level comparable with the genuine one.
- Do not activate our existing Windows timer but instead, use the existing code with these synthetic WM_TIMER messages. Check if the CPU usage stays as low (or low enough) as with the original Windows timer.
It would be great if the tactics works one way or another...
-
Here is a quick change using thread (mobj.h + main.cpp).
New: StartSyncThread + Animate
Don't forget to link with Winmm.lib (for timeBeginPeriod/timeEndPeriod)
Tell me if it makes any difference by you ...
-
Thank you very much, Patrice, it looks very elegant! :)
IMO the initial results do seem very promising, usage-wise. I'll need a little time during the day to analyze the FPS counter behavior (wink-wink) and probably add a couple more touches but overall, I think we've nailed this whole issue on the head.
I will come back to you later to ask for some more creative work regarding your earlier EnumDisplaySettings/DwmGetCompositionTimingInfo input to add automatic detection of the monitor actual retrace rate and to fine-tune the Sleep(n) delay accordingly. This might help us bring down the CPU load yet some more.
-
The problem when dealing with DWM, is that many things have changed since Windows 8.1
See below how to get the correct refresh rate for Windows 10, and with older OS version…
On my ASUS main display the real refresh rate is 75.002
(pTimingInfo.rateRefresh.uiNumerator / (float)pTimingInfo.rateRefresh.uiDenominator)
void Animate (IN DWORD delay) {
DWM_TIMING_INFO pTimingInfo;
ClearMemory(&pTimingInfo, sizeof(pTimingInfo));
pTimingInfo.cbSize = sizeof(pTimingInfo);
// We must check the OS version, because many things have changed in DWM since Windows 8.1
HWND hWnd = gP.hMain;
OSVERSIONINFO osvi = { 0 };
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if (GetVersionEx(&osvi)) { // Check for VISTA and above
if ((osvi.dwPlatformId > 1) && (osvi.dwMajorVersion > 5)) {
hWnd = NULL;
}
}
HRESULT hr = DwmGetCompositionTimingInfo(hWnd, &pTimingInfo);
float rate = pTimingInfo.rateRefresh.uiNumerator / (float)pTimingInfo.rateRefresh.uiDenominator;
for (;;) {
//SetMutexOrSemaphoreOrCriticalSection(); // avoid gP.doneRender access conflicts
EnterCriticalSection(&gP.cs);
if (gP.doneRender) {
gP.doneRender = FALSE;
// experiment with Post/Send/BroadcastMessage whichever would work across threads
//PostMessage(gP.hMain, WM_TIMER, 0x1234, 0); // synthetic timer ID; use same in gP.hMain message loop
WndProc(gP.hMain, WM_TIMER, 0x1234, 0);
}
LeaveCriticalSection(&gP.cs);
//UnsetMutexOrSemaphoreOrCriticalSection();
timeBeginPeriod(1); // set wait resolution to 1 msec, else Sleep() will be too rough!
Sleep(12); // let loop run at ca. 83 FPS
timeEndPeriod(1); // restore default resolution
//MaybeDoEventsHere(); // let gP.hMain message loop do its work if messages are pending
myDoEvents(gP.hMain);
}
}
-
Patrice,
Below are my fixes. Everything works fine with the former WM_TIMER and FPS code except a few minor side effects:- Viewport doesn't get updated on app start.
- CPU/GPU usages and FPS aren't updated when there's no model loaded yet.
- When the model is rotated in the demo mode, the buttons on the lighting panel seem to respond to button-up, rather than button-down, events.
That's probably due to PeekMessage()/return 0 in case WM_TIMER. Can you suggest or figure out how we can fix those unwanted effects staying, at the same time, at the absolutely "lowest priority" in the message pump?
N.B. Make sure to add Dwmapi.lib to your linking stage.
-
The latest code does not work well by me.
The previous WM_TIMER must be kept unchanged, and avoid the use of IDC_TIMER into the SYNC Animate.
We must keep InitMainTimer for the purpose of GDImage compositing into the lighting panel (and further enhancements) and to update correctly the viewport at startup.
Try using directly gl_Drawscene rather than postmessage.
FPS doesn't work (wink wink)
Investigating…
PS: Did you check with W10 ?
-
The latest code does not work well by me.
This comes to me as quite a surprise.
The previous WM_TIMER must be kept unchanged, and avoid the use of IDC_TIMER into the SYNC Animate.
We must keep InitMainTimer for the purpose of GDImage compositing into the lighting panel (and further enhancements) and to update correctly the viewport at startup.
I seem to have all compositing in the lighting panel working correctly as-is except for no viewport update on app start. But we can split the timer's and "timer"'s IDs and handle them distinctly e.g. in case WM_TIMER.
Try using directly gl_Drawscene rather than postmessage.
Never! I need a CPU usage as low as possible. PostMessage+PeekMessage+return 0 is the smoothest way to lower the synthesized WM_TIMER priority to nil.
FPS doesn't work (wink wink)
No, it does for me in both Win 7 and 10! (wink-wink yourself :P )
PS: Did you check with W10 ?
Are you still having doubts on my account? ;) Have a look at my Windows 10 snapshot. It's a dual 1440x900 px monitor, Core 2 Duo setup with two 1MB VRAM nVidia geForces:
-
I have a version that is now working better by me…
Added
On the main display, in non-turbo mode, i have the same FPS than the value reported by DWM, aka 75 FPS,
and between 90 to 100 in turbo mode …
When running on the secondary display (iiyama ProLite XUB2490HS vertical frequency 76 Khz)
60 FPS in non-turbo mode, and 118 to 120 in turbo mode …
BTW, now i can always see correctly the FPS...
-
No problem Patrice,
Take your time. I am open for discussion, and I will accept any of your fixes if they work no worse than the genuine Windows timer but cover the entire range of possible monitor frequencies with correct FPS count (check against Fraps when in doubt) and low CPU usage. :)
On the main display, in non-turbo mode, i have the same FPS than the value reported by DWM, aka 75 FPS,
and between 90 to 100 in turbo mode …
When running on the secondary display (iiyama ProLite XUB2490HS vertical frequency 76 Khz)
60 FPS in non-turbo mode, and 118 to 120 in turbo mode …
THIS IS NOT CORRECT! You should see half the monitor rate at the normal default setting, and the exact monitor rate when in "turbo". In the test cases under Win 7 and 10 that I took the screenshots of, the FPS readings were 38FPS/75FPS and 30FPS/60FPS for the normal/"turbo" modes, respectively, using the code fixes I sent you -- with the existing FPS counter routines unchanged. If the same routines give you erroneous results, it means your "fixes" do not work, and OpenGL fails to VSYNC itself with the mash of your WM_TIMER messages (or redraws, to be more precise) coming indiscriminately from two interfering "timers".
... (iiyama ProLite XUB2490HS vertical frequency 76 Khz) ...
What's this? :o
See your system Advanced monitor settings to check what its permissible frame rates (a.k.a. vertical retrace signal frequency) are in Hertz, rather than Kilohertz...
-
Send me your compiled version to let me check it here, thank you.
-
Here you are.
-
Mike
Did you make any change to Mobj.h an Main.cpp into the EXE you sent me (compared to the one attached to post #39) ?
I think we must mimic the use of KillTimer / SetTimer exactly.
With new KillSync / SetSync
(see ProccessCommandline, and other places)
Using a new set of constant
#define SYNC_ON 0
#define SYNC_OFF 1
#define SYNC_BREAK 2
to be used like that
void Animate (IN DWORD delay) {
for (;;) {
if (gP.syncmode == SYNC_ON) {
EnterCriticalSection(&gP.cs); // avoid gP.doneRender access conflicts
if (gP.doneRender) {
gP.doneRender = FALSE;
gP.AniTick = GetTickCount();
PostMessage(gP.hMain, WM_TIMER, IDC_TIMER, 0);
}
LeaveCriticalSection(&gP.cs);
timeBeginPeriod(1); // set wait resolution to 1 msec, else Sleep() will be too rough!
Sleep(delay); // let loop run at monitor refresh rate plus a few extra FPS for reliable VSYNC
timeEndPeriod(1); // restore default resolution
} else if (gP.syncmode == SYNC_BREAK) {
break;
} else {
Sleep(0);
}
}
}
-
Did you make any change to Mobj.h an Main.cpp into the EXE you sent me (compared to the one attached to post #39) ?
None whatever except remming your "gasoline station" and unremming my "pseudo fraps". I'm appending my Main.cpp and mobj.h below just the way they were in my \ObjReader64 subfolder when I sent you the \Release zip.
Below are also the two screenshots I've just taken on my AMD/ATi box that has just one Philips 1680x1050 monitor but two possible resolutions: 59Hz and 75Hz.
-
I think we must mimic the use of KillTimer / SetTimer exactly.
With new KillSync / SetSync
(see ProccessCommandline, and other places)
I have absolutely no objections. I am ready to experiment as soon as you send me your new Main.cpp and mobj.h not to overlook some essential points in them that I might not be aware of.
We will also have to deal with main window drag/resize. They are a little rigid now. I think our worker thread loop is still a little too tight.
-
Patrice,
Look into my Main.cpp and mobj.h I appended above to be absolutely sure we start off with identical source code before further modifications are introduced.
-
Mike
Here is my reworked version, this one gives the priority to the user's interaction, to keep the application more responsive.
Thus the speed in turbo mode would be lower than the refresh rate, except if you have a better idea, but preserving usability was my first moto…
New:
case WM_SYNC_NOTIFY:
Added:
I forget to rem out SetProcessorAffinity
-
... this one gives the priority to the user's interaction ...
Patrice,
I understand your motives and in fact the window is now responsive as it used to be, but no, I don't entirely agree to this solution. It loads my quadcore CPU up to 60% (!) of which 50% (t-w-o c-o-r-e-s!!!) are 100% loaded by ObjReader alone!
We must find a way to unload the CPU (in the main message pump, or in the worker thread, or elsewhere) of excessive messages that are causing this horrible mess...
-
Patrice,
I regret to say that yours is not a solution. It keeps working even when the animation worker thread is not launched at all. :-[
All timing is still done by the old Windows timer rather than its replacement (the worker thread animation loop), and when the thread isn't running, its otherwise excessive, interfering and meaningless extra refreshes do not overload the renderer any more and the CPU usage drops to its normal ca. 30%, of which ObjReader's contribution is only ca. 25%.
A gl_DrawScene() redraw should normally occur only when there are no other messages pending in the message pump except one most recent WM_TIMER message. This is why the timer message's low priority is so important for the whole engine to work unstressfully on the CPU.
The Animate loop must replace the old Windows timer entirely. Everything that worked based on that timer must now work exclusively based on the Animate loop alone whose messages should have the exact same low priority as the WM_TIMER messages they now replace. The whole purpose of the Animate loop is to be able to do what the Windows timer did but potentially at a much higher frequency that the Windows timer is never able to deliver.
Bottom line is, the old timer must be eliminated from the new code entirely. ::)
-
Eureka ;)
Same memory footprint than with the older version, and exclusive use of the new SyncTimer.
-
Hi Patrice,
Looks very impressive! :)
I've downloaded the fixes and will come back later with my tests under 7, 8.1, 10 and AMD/ATi.
-
I have added a new menu "Timing info" command, and did further cleanup of the code + version #2.51.
-
Here is the cleanup version #2.51
Do a WinMerge to see the latest changes.
-
OK Patrice,
Here come my final fixes for your cleanup. They've been checked under Windows 7 and 10 for nVidia and ATi Radeon at 60 and 75 Hz monitor refresh rates for absolutely identical content and other settings. Note that AMD/ATi's implementation of OpenGL does not support our default half-rate VSYNC wglSwapIntervalEXT(2) = turbo OFF, so it always runs at full monitor rate (currently 60 or 75 Hertz/FPS) regardless of turbo switch setting. The CPU/GPU usage is different on different platforms yet is reasonably low in all test cases.
See my screenshots below. Your FPS counter may deviate and fluctuate slightly from Fraps. This is because i) its implementation is still non-standard, and ii) Fraps averages continuously a few dozens most recent readings and thus eliminates individual FPS spikes that your counter displays instantaneously. I swear the time will come when I finally lay my hands on it again and rework it to be industry standard...
-
Note that AMD/ATi's implementation of OpenGL does not support our default half-rate VSYNC wglSwapIntervalEXT(2) = turbo OFF, so it always runs at full monitor rate (currently 60 or 75 Hertz/FPS) regardless of turbo switch setting.
That's very easy to solve, we can just use the same thing i am doing in BassBox.
aka: Always running at wglSwapIntervalEXT(1), but using in gl_DrawScene something like this in turbo OFF mode:
static long nFlip;
nFlip = !nFlip
if (nFlip) { return; } // turbo OFF mode
-
Do you think we really should bother? The overall usage is remarkably low on my AMD/ATi Radeon platform, somewhere on the order of 15 to 20% (some 7 to 8% only for ObjReader proper) even on a dual core CPU, and for both 60Hz and 75Hz refresh rate.
-
Do you think we really should bother?
Good question ;D
On my gamer computer i had to restore the initial computation of 13 ms, because your last change ( -2) was too small, and produces a 77 / 78 FPS by me.
...
-
Patrice,
Before you change the gP.sync_delay msec code again, please check what your concurrent Fraps reading is like I did in my former screenshots (and preferably post your Tron screenshot here for me to see too). If the FPS and Fraps readings are comparable, then it isn't the gP.sync_delay msec code that yields too high rates but probably some voluntaristic setting in your nVidia Control Center that won't permit the user application to set VSYNC programmatically while the Control Center system-wide setting is in effect. Wherever possible, your nVidia Control Center policies should be set to "Application Defined"!
When wglSwapIntervalEXT(1) is called, the OpenGL viewport refresh rate cannot go higher than the monitor refresh rate (75Hz in your case) regardless of how fast our new "timer" ticks. FWIW it may tick at 7 - 2 = 5 ms (= 200 Hz/FPS --> that's for a 144Hz monitor) but your OpenGL refresh rate will still be 75FPS because your monitor delivers 75Hz only. That's what the whole VSYNC idea is all about.
-
Here is the screen shot.
Note: Fraps oscille between 75 and 76 hz
and ObjReader between 76 and 77 hz.
With this value
gP.sync_delay = (long)(1000.0f / gP.sync_rate) - 1;
i got a fixed 75 hz in Fraps.
-
Mike
I am switching everything to TURBO mode (aka the default setting).
And making the corresponding cleaning/optimisation of the code, i may even remove the matching overlay switch, moving at the same location the rotation icon that becomes a switch (see below).
-
Thank you, Patrice.
Yes, the FPS counter tends to display a ca. +1 FPS higher rate than Fraps for me too. I think I'll fix this nuisance as soon as I find what's causing it. It eludes me at the moment.
I changed my code to gP.sync_delay = (long)(1000.0f / gP.sync_rate) - 1; too to be consistent with yours. Let's wait and see if such a narrow margin of extra FPS proves sufficient for reliable VSYNC over time. (I hope your nVidia Control Center's Image Settings policy is set as in the snapshot below.)
BTW have you been able to make the lighting panel's scroll more responsive to the mouse wheel when in animation mode?
I am switching everything to TURBO mode (aka the default setting).
... i may even remove the matching overlay switch
OK let turbo be our default setting but please don't remove the half-rate option entirely. It may be useful for extremely high polygon counts on low-VRAM video cards.
-
Ok, i shall keep the TURBO On/Off, while switching the default mode to ON.
BTW have you been able to make the lighting panel's scroll more responsive to the mouse wheel when in animation mode?
Nope, i did try a few things but nothing perfect, however on my computer it mostly depend of the size of the model.
See attached my nVIDIA setting.
-
Thank you, the settings seem acceptable in this particular case.
BTW I think Qualité isn't very useful for you because you aren't playing games. Note that it overrides application-wide settings that you might try to change programmatically in your code. So when active, Qualité will not allow the application e.g. to switch off its VSYNC. Performances seems more reasonable because programming OpenGL, you're sometimes supposed to run benchmark tests, in which case the faster means the better.
-
Mike
I am using a G-SYNC config, thus using performance or quality makes no difference by me, as long as OR is concerned.
I have attached the latest code i am using, with TURBO being the default mode (this one runs my FPS very close to FRAPS)
Added:
FPS should be always drawn in textured mode, try with wireframe and/or point cloud.
Ideally the FPS should be drawn in composited mode, rather than inside gl_DrawScene.
-
Patrice,
FPS should be always drawn in textured mode, try with wireframe and/or point cloud.
Ah yes, that's a good catch! Please use this in mobj.h:
void printFPS(int x, char* str) {
static bool fpsInited = false;
char* s = str;
int i, j, idx = 0;
if (!fpsInited)
fpsInit(&fpsInited);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glTranslatef(0.0f, gP.glHeight - 1.0f, 0.0f);
glScalef(1.0f, -1.0f, 1.0f);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0.0, gP.glWidth, 0.0, gP.glHeight, -1.0, 1.0);
glMatrixMode(GL_TEXTURE);
glPushMatrix();
glScalef(1.0f / float(g_digitW * 10), 1.0f / float(g_digitH), 1.0f);
glMatrixMode(GL_MODELVIEW);
glPushAttrib(GL_ENABLE_BIT | GL_POLYGON_MODE);
glEnable(GL_BLEND);
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, gM.glFpsTexture);
x += 1;
while (*s) {
i = x + g_digitW;
idx = (*s++) - 48;
j = idx * g_digitW;//(idx << 5) / 2;
glBegin(GL_QUADS);
glTexCoord2i(j, 0); glVertex2i(x, g_digitH);
glTexCoord2i(j + g_digitW, 0); glVertex2i(i, g_digitH);
glTexCoord2i(j + g_digitW, g_digitH); glVertex2i(i, 0);
glTexCoord2i(j, g_digitH); glVertex2i(x, 0);
glEnd();
x += g_digitW;
}
glBindTexture(GL_TEXTURE_2D, 0);
glPopAttrib();
glMatrixMode(GL_TEXTURE);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
... this one runs my FPS very close to FRAPS ...
I hope you understand that you're using some obscure side effect of Sleep(1) because strictly speaking, without timeBegin|EndPeriod(1) your Sleep(1) is effectively the same as Sleep(USER_TIMER_MINIMUM), or Sleep(15.6).
Contrary to their deceptive names, the matching timeBeginPeriod() and timeEndPeriod() APIs set/reset the minimum resolution not of timers but rather of process wait state timeouts, like e.g. WaitForSingleObject() or WaitOnAddress(), and of the Sleep() API.
In fact, what you're doing is forcing the process to sleep some extra ticks in addition to the "timer" loop delay. But what I'd rather like to do is understand why such extra sleep is needed at all, because your funny 998+1+1 ticks jugglery seems logical (though somewhat weird) to me.
But OK, let's stick to your solution temporarily until the logically correct solution dawns on us sooner or later. :)
Ideally the FPS should be drawn in composited mode...
I'd rather see everything drawn in gl_DrawScene() than buried somewhere in your GDImage I have no access to, Patrice. ;) Ideally, the printFPS() routine should be reduced to a glCallList() call but that requires some experimentation to avoid yet more rather than less stress on the renderer.
-
BTW have you been able to make the lighting panel's scroll more responsive to the mouse wheel when in animation mode?
Please try this, and tell me what is the best value for you (see the red code)
void ScrollContent(IN HWND hWnd, IN long nScroll) {
go.mousewheel = -1;
long nStep = 0, nID = 0, nBottom = 0;
static long wasDelta;
long nSpeed = gP.AniTick - wasDelta;
long one = 1; if (nScroll < 0) { one = -1; }
if (nSpeed > 30) {
nStep = 1;
} else if (nSpeed) {
nStep = 8;
one *= 2;
} else {
if (gP.nGiration) { nStep = 16; } else { nStep = 8; }
one *= 4;
}
if (gP.nGiration || gP.nUseFPS) { one *= 2; }
ZOBJECT zObj;
About GDImage, did you looked at the GDplus (a limited GDImage subset) project ?
It should give you a better understanding on how the whole engine works (everything being based on sprite object).
The great strength of GDImage is that it can also work in zoom mode, just like with Google map (see the Gmap64 project http://www.objreader.com/index.php?topic=106.0).
And who knows, may be one day i will give you more for the posterity... 8)
-
if (gP.nGiration || gP.nUseFPS) { one *= 2; } seems to work pretty nicely for me, thanks! I've added it to my code.
About GDImage, did you looked at the GDplus (a limited GDImage subset) project ?
I did. That's a pretty solid piece of work, and I imagine GDImage must be a real piece of art. :)
And who knows, may be one day i will give you more for the posterity... 8)
Alas my friend, I am too old myself to be anyone's happy inheritor. ;D