6

I want to add dynamic video effects to the complete Windows desktop.

I want to be able to turn the screen grey, bevel the edges and add some scan lines to make it look like an old CRT screen, or make the screen glitch, the way they show hacked systems in movies that don't understand tech, etc.

The effects themselves are out of scope of this question. This question is how to apply them, i.e. how do I get the desktop image produced by Windows, apply my effects, and present the result on the same display.

I know several ways which might work.

  1. Hook into WinAPI calls which draw stuff.
  2. Create a fake secondary monitor making it the primary display device, grab video stream from it, apply my effects, and present on the real monitor
  3. Create a custom display driver which applies the effects.

All of them have downsides: complexity, driver signing requirement, complicated setup. Any better ways to achieve what I want?

Soonts
  • 20,079
  • 9
  • 57
  • 130
Buzby
  • 345
  • 1
  • 11
  • 3
    Try to create a WDDM driver (good luck). From the way you are asking it seems that you are not aware of the hell-level complexity details of what you want, so, again, good luck. – Michael Chourdakis Aug 02 '19 at 09:54
  • 3
    This may help you get very close to what you're looking for [Windows Magnifier GitHub](https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Magnification) – Serdalis Aug 02 '19 at 10:19
  • 1
    Could you add a semi transparrent window layer with the effects? May not give you amazing results but would be a damn sight simpler – Tom Aug 02 '19 at 11:14
  • @peter, yes, that was one example of use of such software and how portable I'd prefer it to be. Note that two of the three methods I've looked at to do this are not a "dump and run" situation, but also require a lot of fiddling. – Buzby Aug 02 '19 at 12:12
  • @michael, I have but scratched the surface of doing it as a driver which will require getting it signed if I ever want to make the software available to others to use easily on another system. I have never, at any point, envisioned it would be easy or someone else would have done it. – Buzby Aug 02 '19 at 12:14
  • @Tom, problem with using a transparent layer and drawing over the top is I won't get exactly what I am looking for. See the [Vintage terminal for Ubuntu](https://unix.stackexchange.com/questions/146609/vintage-terminal-for-ubuntu) as an example of how much I want to curve the screen when pretending to be a CRT. – Buzby Aug 02 '19 at 12:16
  • 2
    This question [is being discussed on Meta](https://meta.stackoverflow.com/q/388088/2751851). – duplode Aug 02 '19 at 21:04

2 Answers2

5

1 is very hard, too many different APIs.

2 The hard part is making a fake monitor. If you’ll instead buy a $10 device called “HDMI dummy plug” it will become relatively simple, with 100% documented API. Use desktop duplication API to get texture of monitor 1, apply whatever effect you want and show that on monitor 2. If you want good performance, you better implement the processing completely on GPU, e.g. render a quad with a custom pixel shader.

3 will work but very hard to do.

There’s another way. It’s tricky to implement and uses undocumented APIs, but quite reliable in my experience, at least it was so on Windows 7. Write a DLL that injects itself into dwm.exe. That’s a windows process “desktop windows manager”, it composes whatever is visible on desktop. After DLL inject, create a new thread, in that thread call D3D11CreateDeviceAndSwapChain, then use e.g. MinHook to intercept Present, and ideally also ResizeBuffers methods of IDXGISwapChain interface. If succeeded, dwm.exe will call functions from your DLL every time it presents a frame, or when desktop resolution changes. Then, in the present functions you can do your stuff, e.g. add another render pass implementing your effect, then call original implementations to actually present the result to desktop.

This is easy in theory but quite tricky to implement in practice, however. E.g. it’s hard to debug dwm.exe, you’ll have to rely on logging, or maybe use a virtual machine with remote debugger. Also this is not portable across windows versions. Another limitation, it won’t work for full-screen apps like videogames, they bypass dwm.exe. With videogames, will only work for “borderless full-screen window” in-game setting.

Update: another approach, much simpler. Create a topmost full screen window with per-pixel transparency. The OS supports them for decades, set WS_EX_LAYERED and WS_EX_TRANSPARENT extended style bits. You won’t be able to do grayscale because you can only overlay your stuff but not read what's underneath, but edges, scanlines, and glitches are totally doable. For best performance, use some GPU-centric API to render that window, e.g. Direct2D or D3D in C++. It’s also much easier to debug, either use 2 monitors, or position the transparent window so it occupies a rectangle in the corner leaving you enough screen space for the IDE. Here's an example (not mine).

Soonts
  • 20,079
  • 9
  • 57
  • 130
  • For the CRT type emulation, I'd want to bend the display a bit, like the edges of the [Vintage terminal for Ubuntu](https://unix.stackexchange.com/questions/146609/vintage-terminal-for-ubuntu) Will have a dig in the dwm.exe method. Not so worried about anything running full screen on its own. – Buzby Aug 02 '19 at 10:53
  • 1
    @Buzby Right, that effect is only doable with DWM or DD methods, overlaying a bitmap won’t work. I hacked dwm.exe for screen capture purpose on Win7, before DD was available. Some older versions of OBS studio were doing something similar as well. You don’t need to copy and share the texture, instead you’ll need to render one more pass before each present. If the render target texture is not readable by shaders i.e. doesn’t have `D3D11_BIND_SHADER_RESOURCE` flag, just create another texture and copy it there: VRAM bandwidth is very high, it’s much faster than system RAM. – Soonts Aug 02 '19 at 11:04
  • 2
    @Rob Posted on meta to discuss https://meta.stackoverflow.com/questions/388073/tag-reputation-for-close-voting You’re welcome to explain your decision there. – Soonts Aug 02 '19 at 15:29
5

For capturing and redrawing the desktop on a window, you can't go past the handy windows magnification tool.
What you will want to create, is a full screen version of it, with custom processing to make it look how you want.

Luckily, there is a GitHub containing working source for it

You'll notice there is a function in the fullscreen file:

//
// FUNCTION: SetColorGrayscaleState()
//
// PURPOSE: Either apply grayscale to all colors on the screen, or restore the original colors.
//
void SetColorGrayscaleState(_In_ BOOL fGrayscaleOn)
{
    // Apply the color matrix required to either invert the screen colors or to show the regular colors.
    PMAGCOLOREFFECT pEffect = (fGrayscaleOn ? &g_MagEffectGrayscale : &g_MagEffectIdentity);

    MagSetFullscreenColorEffect(pEffect);
}

Which gives you a pretty solid basis for working out how to affect the pixels being drawn back to the window.

To make this window click through-able, you need to create it using something like the following line of code:

HWND hWnd = CreateWindowEx(WS_EX_LAYERED|WS_EX_TRANSPARENT, cName, wTitle, NULL, 0, 0, 640, 480, NULL, 0, GetModuleHandle(NULL), 0);

The important part here is the WS_EX_LAYERED|WS_EX_TRANSPARENT flag combination.

From the microsoft docs:

NOT IMPORTANT. However, if the layered window has the WS_EX_TRANSPARENT extended window style, the shape of the layered window will be ignored and the mouse events will be passed to other windows underneath the layered window.

With this base, and the knowledge of how to build a clickthrough window, you should be able to make a program that changes the colours / adds some artifacts to a full screen window in front of the desktop.

All programs I have made this way have had lower FPS than your native desktop, but should still be fine for a cool looking program.

Serdalis
  • 10,296
  • 2
  • 38
  • 58
  • 2
    Also, it should be noted that this requires the application to run as admin (for obvious reasons). – Luaan Aug 02 '19 at 10:30
  • Thanks, I'll have to download it and compile it up. Would I be right in saying if I set the area to be magnified over a running video, and the output of the magnification to cover that same area that it won't show the magnified video? It would still be a helpful start in Option 2 in my first post though. – Buzby Aug 02 '19 at 12:20
  • 1
    @Buzby I think it ignores itself, though I may be mis-remembering. – Serdalis Aug 02 '19 at 12:33
  • Close but not quite. Plus side it passes through mouse clicks and ignores its own window when processing the layer below. Minus side, it only allows linear resizing and colour changes. I'd need to be able to read direct from the lower layer (the original image) and write to the upper layer (where it displays the magnified image). If, instead of using the full screen magnification function, I use an area, I get an HWND which I might be able to write on, but I'd still need the original image underneath to be able to do more complicated transforms. – Buzby Aug 05 '19 at 06:38
  • 1
    If you switch the render destination to an offscreen image and then write to the screen from that you should be able to do more complex transformations. Similar to double buffering. – Serdalis Aug 06 '19 at 14:28