Skip to content

Conversation

wravery
Copy link

@wravery wravery commented Jan 27, 2017

I based this off of the original Processing program, but it's much lighter weight since it can take advantage of some native DirectX APIs for the screen sampling. See ReadMe.md for more details.

Bill and others added 20 commits January 20, 2017 20:18
Only updated the Debug|Win32 target to use <SubSystem>Windows</SubSystem> and to link with the import libs we need. I also needed to update the Release target and x64 platform configurations. Now these settings are common to all of them.
Listen for "Ada\n" cookie on each COM port to find the one the Arduino is currently using.

When resources are lost due to a fullscreen mode change, release and reacquire them in the next update.
The COM port auto-detection was using the current settings for the COM port instead of configuring them to match. It turns out the COM port gets reset to defaults after a reboot, so the auto-detection breaks.

Now we query the current config, tweak it to match the parameters used by the Arduino, and try setting before reading from the port to get the ack. If the ack fails for the current port, reset the config to what it was before the probe.
I started out using a bunch of static global variables and functions to handle state while getting this working the first time. This more closely mirrored the original Processing program, but it's hard to maintain.
Startup took too long probing the COM ports one at a time. Now in the probe loop it should kick off an overlapped ReadFile call but not wait for the result. As soon as one COM port completes the read successfully and receives the proper cookie from the Arduino, we can break out of the loop and use that COM port from then on. COM ports that fail the probe should restore their prior mode settings in the ~port_resources() destructor.
We don't need special handling for the case where ReadFile returns synchronously, we can just add it to the pending list and check for completion the next time through the outer loop.

Also tidied up some comments.
CreateEvent with an event name will re-use a previously createdd event if one exists with that name. I was using the same name for all of my overlapped read wait events.
Getting ready to send a pull request, need to add a bit of documentation and tidy up the comments around the parts that an end user would need to edit.
I based this off of the original Processing program, but it's much lighter weight since it can take advantage of some native DirectX APIs for the screen sampling. See ReadMe.md for more details.
Same as before, but this time I'm trying to preserve the commits from my local repo.
When running this with games that start as an Administrator with UAC enabled, the UAC prompt shows up and dims the entire screen. While the screen is dimmed, the desktop duplication interface returns DXGI_ERROR_INVALID_CALL, which broke the update loop. The workaround was to lock and unlock the PC so AdaLight.exe would reacquire the duplication interface.

Now I'm handling the extra error much like DXGI_ERROR_ACCESS_LOST, and while I was at it I added handling for DXGI_ERROR_UNSUPPORTED to the MapDesktopSurface code path. If we're using the lighter-weight MapDesktopSurface API and the desktop surface is no longer available in system memory, it should fall back to AcquireNextFrame with a scratch surface in the next frame.
The previous fix for UAC prompts only worked with a breakpoint that paused execution before the next call to create_resources. If the timer tried to create the resources again while the UAC prompt was still up, it would fail to enumerate the displays and leave _displays empty but set the _acquiredResources flag. Now we bail out of create_resources if there are no displays, so it will try again on the next timer until the UAC prompt is dismissed.

I also added some debug-only trace output to say what the frame rate was. Previously I'd just set breakpoints in free_resources and inspect the member variable, this way I don't have to do that anymore.
The create_resources call is pretty expensive, it tends to peg the CPU when the timer fires while the UAC prompt is up.

I refactored the timer state code into another class (update_timer) and added throttling which will change the frequency to once ever 3 seconds (settings::throttleTimer) until we successfully acquire the resources again.
C++11 has std::thread, std::mutex, and std::condition_variable, it should be possible to make the update_timer class platform agnostic with all of those primitives.

By splitting the timer thread and the worker thread, I also cranked the frame rate up to almost 30 fps on my machine. The timer thread sets a condition_variable to trigger an update on the worker thread, then goes back to sleep and waits with a timeout for the timer period. The timer is not delayed by the time it take the worker thread to perform each update.
Rather than capturing raw this pointers, wrap the update_timer in std::shared_ptr and capture that in the thread lambdas. That also means that once the last reference to the shared_ptr goes away, we can free the entire update_timer. We just need to keep a static std::weak_ptr to it alive to reuse it as long as it's still alive.
@wravery
Copy link
Author

wravery commented Feb 18, 2017

@PaintYourDragon, just want to make sure you get a notification about this pull request in case you haven't seen it. LMK if you have any comments or suggestions.

wravery and others added 8 commits February 19, 2017 18:03
Accessing the screen_samples static instance from the UI thread triggered a race condition with the worker thread running in update_timer. Instead of synchronously trying to free and reacquire resources from the UI thread, we should signal the timer to stop/start, and the timer's onUpdate and onStop callback should take care of the resource reacquisition from the worker thread.
I still need to go and implement the actual reading and writing of a settings file.
I'm using the cpprestsdk JSON library to read and write the settings object to a JSON config file. Now that it can change the settings at runtime, I'm including a pre-built ZIP file with binaries and the default configuration pretty printed and commented. I updated the ReadMe.md file to explain how to use the pre-built binaries if the user doesn't want to use Visual Studio to rebuild their own version.
The hyperlinks for the ZIP file of prebuilt executables and the sample config file were pointing to the wrong relative directories.
Case sensitivity for the win.
Added note indicating that the Visual C++ 2015 Redistributable Packages must be installed on systems that don't have Visual Studio 2015 installed.
@NaturalBornCamper
Copy link

That's very nice work, friend.. thanks!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants